Skip to content

Commit a01e8f6

Browse files
authored
Merge pull request #4 from numary/dev/machine-02
Script execution
2 parents b55680c + e85732a commit a01e8f6

File tree

9 files changed

+291
-80
lines changed

9 files changed

+291
-80
lines changed

api/http.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,25 @@ func NewHttpAPI(lc fx.Lifecycle, resolver *ledger.Resolver) *HttpAPI {
9595
})
9696
})
9797

98+
r.POST("/:ledger/script", func(c *gin.Context) {
99+
l, _ := c.Get("ledger")
100+
101+
var script core.Script
102+
c.ShouldBind(&script)
103+
104+
err := l.(*ledger.Ledger).Execute(script)
105+
106+
res := gin.H{
107+
"ok": err == nil,
108+
}
109+
110+
if err != nil {
111+
res["err"] = err.Error()
112+
}
113+
114+
c.JSON(200, res)
115+
})
116+
98117
r.GET("/:ledger/accounts", func(c *gin.Context) {
99118
l, _ := c.Get("ledger")
100119

core/script.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
package core
22

3-
type Script struct {
4-
Plain string `json:"plain"`
5-
AST AST `json:"ast"`
6-
Compiled []byte `json:"bytecode"`
7-
}
3+
import "encoding/json"
84

9-
type AST struct {
5+
type Script struct {
6+
Plain string `json:"plain"`
7+
Vars map[string]json.RawMessage `json:"vars"`
108
}

core/transaction.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ type Transaction struct {
2020
Timestamp string `json:"timestamp"`
2121
Hash string `json:"hash"`
2222
Metadata Metadata `json:"metadata"`
23-
Script string `json:"script,omitempty"`
2423
}
2524

2625
func (t *Transaction) AppendPosting(p Posting) {

go.mod

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@ require (
77
github.com/gin-gonic/gin v1.7.1
88
github.com/huandu/go-sqlbuilder v1.12.1
99
github.com/jackc/pgx/v4 v4.11.0
10-
github.com/kelseyhightower/envconfig v1.4.0
1110
github.com/mattn/go-sqlite3 v1.14.7
12-
github.com/numary/machine v0.0.0-20210702091459-23a82555adbf
11+
github.com/numary/machine v0.0.0-20210719124351-aad80bd74032
1312
github.com/pkg/errors v0.8.1
1413
github.com/spf13/cobra v1.1.3
1514
github.com/spf13/viper v1.8.1

go.sum

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,9 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
204204
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
205205
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
206206
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
207-
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
208207
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
208+
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
209+
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
209210
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
210211
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
211212
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -341,8 +342,6 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
341342
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
342343
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
343344
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
344-
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
345-
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
346345
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
347346
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
348347
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -411,8 +410,12 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
411410
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
412411
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
413412
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
414-
github.com/numary/machine v0.0.0-20210702091459-23a82555adbf h1:edD04W2rv475M6PBiQ6avKdzpsE5GQZ/GRSG8y/p1Lk=
413+
github.com/numary/ledger v0.0.0-20210702172952-a5bd30e551d0/go.mod h1:u2K28z9TDYd6id1qeD2uv7JDlajuRZ0fvOnCeDZmDxk=
415414
github.com/numary/machine v0.0.0-20210702091459-23a82555adbf/go.mod h1:WAFvefAGYNjdDmPtDoZ305F58QDtUJyB0QWN3vzSZao=
415+
github.com/numary/machine v0.0.0-20210715161621-302dfd46d8db h1:TVGE+0BIHcVXNPMfsxGeD6TCyNwLXhoBJOxyr8zbXuk=
416+
github.com/numary/machine v0.0.0-20210715161621-302dfd46d8db/go.mod h1:dvm8ZK8ywVDLDCKX8RyVT5bnD5/9A2RRmK8ZdJ9OvSM=
417+
github.com/numary/machine v0.0.0-20210719124351-aad80bd74032 h1:/0PgX+JYoyHa776kR98XXqLGvqHWPiDTCQAYT0siY2c=
418+
github.com/numary/machine v0.0.0-20210719124351-aad80bd74032/go.mod h1:dvm8ZK8ywVDLDCKX8RyVT5bnD5/9A2RRmK8ZdJ9OvSM=
416419
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
417420
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
418421
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=

ledger/executor.go

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,69 @@
11
package ledger
22

3-
type Executor struct {
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
"github.com/numary/ledger/core"
8+
"github.com/numary/machine/script/compiler"
9+
"github.com/numary/machine/vm"
10+
)
11+
12+
func (l *Ledger) Execute(script core.Script) error {
13+
if script.Plain == "" {
14+
return errors.New("no script to execute")
15+
}
16+
17+
p, err := compiler.Compile(script.Plain)
18+
if err != nil {
19+
return fmt.Errorf("compile error: %v", err)
20+
}
21+
m := vm.NewMachine(p)
22+
23+
err = m.SetVarsFromJSON(script.Vars)
24+
if err != nil {
25+
return fmt.Errorf("error while setting variables: %v", err)
26+
}
27+
28+
needed_balances, err := m.GetNeededBalances()
29+
if err != nil {
30+
return err
31+
}
32+
33+
balances := map[string]map[string]uint64{}
34+
35+
for account_address, needed_assets := range needed_balances {
36+
account, err := l.GetAccount(account_address)
37+
if err != nil {
38+
return fmt.Errorf("invalid account address: %v\n", err)
39+
}
40+
balances[account_address] = map[string]uint64{}
41+
for asset := range needed_assets {
42+
amt := account.Balances[asset]
43+
if amt < 0 {
44+
amt = 0
45+
}
46+
balances[account_address][asset] = uint64(amt)
47+
}
48+
}
49+
50+
err = m.SetBalances(balances)
51+
if err != nil {
52+
return err
53+
}
54+
55+
c, err := m.Execute()
56+
if err != nil {
57+
return fmt.Errorf("script failed: %v", err)
58+
}
59+
if c == vm.EXIT_FAIL {
60+
return errors.New("script exited with error code EXIT_FAIL")
61+
}
62+
63+
t := core.Transaction{
64+
Postings: m.Postings,
65+
}
66+
67+
err = l.Commit([]core.Transaction{t})
68+
return err
469
}

ledger/executor_test.go

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package ledger
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"fmt"
7+
"testing"
8+
9+
"github.com/numary/ledger/core"
10+
)
11+
12+
func TestTransactionInvalidScript(t *testing.T) {
13+
with(func(l *Ledger) {
14+
script := core.Script{
15+
Plain: "this is not a valid script",
16+
}
17+
18+
err := l.Execute(script)
19+
20+
if err == nil {
21+
t.Error(errors.New(
22+
"script was invalid yet the transaction was commited",
23+
))
24+
}
25+
l.Close()
26+
})
27+
}
28+
29+
func TestTransactionFail(t *testing.T) {
30+
with(func(l *Ledger) {
31+
script := core.Script{
32+
Plain: "fail",
33+
}
34+
35+
err := l.Execute(script)
36+
37+
if err == nil {
38+
t.Error(errors.New(
39+
"script failed yet the transaction was commited",
40+
))
41+
}
42+
l.Close()
43+
})
44+
}
45+
46+
func TestSend(t *testing.T) {
47+
with(func(l *Ledger) {
48+
defer l.Close()
49+
script := core.Script{
50+
Plain: `send [USD/2 99] (
51+
source=@world
52+
destination=@user:001
53+
)`,
54+
}
55+
56+
err := l.Execute(script)
57+
58+
if err != nil {
59+
t.Error(err)
60+
return
61+
}
62+
63+
user, err := l.GetAccount("user:001")
64+
65+
if err != nil {
66+
t.Error(err)
67+
return
68+
}
69+
70+
if b := user.Balances["USD/2"]; b != 99 {
71+
t.Error(fmt.Sprintf(
72+
"wrong USD/2 balance for account user:001, expected: %d got: %d",
73+
99,
74+
b,
75+
))
76+
}
77+
})
78+
}
79+
80+
func TestVariables(t *testing.T) {
81+
with(func(l *Ledger) {
82+
defer l.Close()
83+
84+
var script core.Script
85+
json.Unmarshal(
86+
[]byte(`{
87+
"plain": "vars {\naccount $dest\n}\nsend [CAD/2 42] (\n source=@world \n destination=$dest \n)",
88+
"vars": {
89+
"dest": "user:042"
90+
}
91+
}`),
92+
&script)
93+
94+
err := l.Execute(script)
95+
96+
if err != nil {
97+
t.Error(err)
98+
return
99+
}
100+
101+
user, err := l.GetAccount("user:042")
102+
103+
if err != nil {
104+
t.Error(err)
105+
return
106+
}
107+
108+
if b := user.Balances["CAD/2"]; b != 42 {
109+
t.Error(fmt.Sprintf(
110+
"wrong CAD/2 balance for account user:042, expected: %d got: %d",
111+
42,
112+
b,
113+
))
114+
}
115+
})
116+
}
117+
118+
func TestEnoughFunds(t *testing.T) {
119+
with(func(l *Ledger) {
120+
defer l.Close()
121+
122+
tx := core.Transaction{
123+
Postings: []core.Posting{
124+
{
125+
Source: "world",
126+
Destination: "user:001",
127+
Amount: 100,
128+
Asset: "COIN",
129+
},
130+
},
131+
}
132+
133+
err := l.Commit([]core.Transaction{tx})
134+
135+
if err != nil {
136+
t.Error(err)
137+
return
138+
}
139+
140+
var script core.Script
141+
json.Unmarshal(
142+
[]byte(`{
143+
"plain": "send [COIN 95] (\n source=@user:001 \n destination=@world \n)"
144+
}`),
145+
&script)
146+
147+
err = l.Execute(script)
148+
149+
if err != nil {
150+
t.Error(err)
151+
return
152+
}
153+
})
154+
}
155+
156+
func TestNotEnoughFunds(t *testing.T) {
157+
with(func(l *Ledger) {
158+
defer l.Close()
159+
160+
tx := core.Transaction{
161+
Postings: []core.Posting{
162+
{
163+
Source: "world",
164+
Destination: "user:002",
165+
Amount: 100,
166+
Asset: "COIN",
167+
},
168+
},
169+
}
170+
171+
err := l.Commit([]core.Transaction{tx})
172+
173+
if err != nil {
174+
t.Error(err)
175+
return
176+
}
177+
178+
var script core.Script
179+
json.Unmarshal(
180+
[]byte(`{
181+
"plain": "send [COIN 105] (\n source=@user:002 \n destination=@world \n)"
182+
}`),
183+
&script)
184+
185+
err = l.Execute(script)
186+
187+
if err == nil {
188+
t.Error("error wasn't supposed to be nil")
189+
return
190+
}
191+
})
192+
}

0 commit comments

Comments
 (0)