Skip to content

Commit

Permalink
Add metadata (#33)
Browse files Browse the repository at this point in the history
- Add support for arbitrary JSON metadata on transactions & accounts

Co-authored-by: Azorlogh <[email protected]>
  • Loading branch information
altitude and Azorlogh authored Aug 3, 2021
1 parent a4e420e commit 86d4e21
Show file tree
Hide file tree
Showing 21 changed files with 768 additions and 101 deletions.
56 changes: 54 additions & 2 deletions api/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,15 @@ func NewHttpAPI(lc fx.Lifecycle, resolver *ledger.Resolver) *HttpAPI {

err := l.(*ledger.Ledger).Commit([]core.Transaction{t})

c.JSON(200, gin.H{
res := gin.H{
"ok": err == nil,
})
}

if err != nil {
res["err"] = err.Error()
}

c.JSON(200, res)
})

r.POST("/:ledger/script", func(c *gin.Context) {
Expand Down Expand Up @@ -175,6 +181,52 @@ func NewHttpAPI(lc fx.Lifecycle, resolver *ledger.Resolver) *HttpAPI {
c.JSON(200, res)
})

r.POST("/:ledger/transactions/:id/metadata", func(c *gin.Context) {
l, _ := c.Get("ledger")

var m core.Metadata
c.ShouldBind(&m)

err := l.(*ledger.Ledger).SaveMeta(
"transaction",
c.Param("id"),
m,
)

res := gin.H{
"ok": err == nil,
}

if err != nil {
res["err"] = err.Error()
}

c.JSON(200, res)
})

r.POST("/:ledger/accounts/:id/metadata", func(c *gin.Context) {
l, _ := c.Get("ledger")

var m core.Metadata
c.ShouldBind(&m)

err := l.(*ledger.Ledger).SaveMeta(
"account",
c.Param("id"),
m,
)

res := gin.H{
"ok": err == nil,
}

if err != nil {
res["err"] = err.Error()
}

c.JSON(200, res)
})

h := &HttpAPI{
engine: r,
addr: viper.GetString("server.http.bind_address"),
Expand Down
1 change: 1 addition & 0 deletions core/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ type Account struct {
Type string `json:"type,omitempty"`
Balances map[string]int64 `json:"balances,omitempty"`
Volumes map[string]map[string]int64 `json:"volumes,omitempty"`
Metadata Metadata `json:"metadata"`
}
4 changes: 3 additions & 1 deletion core/metadata.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
package core

type Metadata map[string]interface{}
import "encoding/json"

type Metadata map[string]json.RawMessage
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ go 1.16
require (
github.com/gin-contrib/cors v1.3.1
github.com/gin-gonic/gin v1.7.1
github.com/google/go-cmp v0.5.6 // indirect
github.com/huandu/go-sqlbuilder v1.12.1
github.com/jackc/pgx/v4 v4.11.0
github.com/mattn/go-sqlite3 v1.14.7
github.com/numary/machine v0.0.0-20210726090747-5853ebec28f0
github.com/numary/machine v0.0.0-20210803145033-12db2afffb8d
github.com/pkg/errors v0.8.1
github.com/spf13/cobra v1.1.3
github.com/spf13/viper v1.8.1
Expand Down
8 changes: 6 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -415,8 +415,12 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/numary/ledger v0.0.0-20210702172952-a5bd30e551d0/go.mod h1:u2K28z9TDYd6id1qeD2uv7JDlajuRZ0fvOnCeDZmDxk=
github.com/numary/machine v0.0.0-20210702091459-23a82555adbf/go.mod h1:WAFvefAGYNjdDmPtDoZ305F58QDtUJyB0QWN3vzSZao=
github.com/numary/machine v0.0.0-20210726090747-5853ebec28f0 h1:TM2r/u2KwZBipmd6s5q/mzlzWfNCFwEadH9xyYbvZAw=
github.com/numary/machine v0.0.0-20210726090747-5853ebec28f0/go.mod h1:azMZte4ywWM0ISNMgMII4IS9XJ0N3ZAB7TfQ2Ay3dL0=
github.com/numary/machine v0.0.0-20210729165957-aa92270b0f57 h1:hMGEr+3Ld+hhJCdIG9OUCh+igSIMvKLqx0r2UEf964Q=
github.com/numary/machine v0.0.0-20210729165957-aa92270b0f57/go.mod h1:KulcZIlMidEjXmuFSGNckmk0pKr4HFKFYy3bB+ksWSQ=
github.com/numary/machine v0.0.0-20210730150440-46c0133da7be h1:Zn0JK8STaFzc2O1Vw7IcitVQLE+buvybu99J8zaIe9k=
github.com/numary/machine v0.0.0-20210730150440-46c0133da7be/go.mod h1:KulcZIlMidEjXmuFSGNckmk0pKr4HFKFYy3bB+ksWSQ=
github.com/numary/machine v0.0.0-20210803145033-12db2afffb8d h1:Z6nbKlnn/kCZjMXEh6Upscg+7V0+ZhxJFJZLCR1ctAY=
github.com/numary/machine v0.0.0-20210803145033-12db2afffb8d/go.mod h1:KulcZIlMidEjXmuFSGNckmk0pKr4HFKFYy3bB+ksWSQ=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
Expand Down
58 changes: 40 additions & 18 deletions ledger/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"

"github.com/numary/ledger/core"
machine "github.com/numary/machine/core"
"github.com/numary/machine/script/compiler"
"github.com/numary/machine/vm"
)
Expand All @@ -18,40 +19,61 @@ func (l *Ledger) Execute(script core.Script) error {
if err != nil {
return fmt.Errorf("compile error: %v", err)
}

m := vm.NewMachine(p)

err = m.SetVarsFromJSON(script.Vars)
if err != nil {
return fmt.Errorf("error while setting variables: %v", err)
return fmt.Errorf("could not set variables: %v", err)
}

needed_balances, err := m.GetNeededBalances()
if err != nil {
return err
{
ch, err := m.ResolveResources()
if err != nil {
return fmt.Errorf("could not resolve program resources: %v", err)
}
for req := range ch {
if req.Error != nil {
return fmt.Errorf("could not resolve program resources: %v", req.Error)
}
account, err := l.GetAccount(req.Account)
if err != nil {
return fmt.Errorf("could not get account %q: %v", req.Account, err)
}
meta := account.Metadata
entry, ok := meta[req.Key]
if !ok {
return fmt.Errorf("missing key %v in metadata for account %v", req.Key, req.Account)
}
value, err := machine.NewValueFromTypedJSON(entry)
if err != nil {
return fmt.Errorf("invalid format for metadata at key %v for account %v: %v", req.Key, req.Account, err)
}
req.Response <- *value
}
}

balances := map[string]map[string]uint64{}

for account_address, needed_assets := range needed_balances {
account, err := l.GetAccount(account_address)
{
ch, err := m.ResolveBalances()
if err != nil {
return fmt.Errorf("invalid account address: %v\n", err)
return fmt.Errorf("could not resolve balances: %v", err)
}
balances[account_address] = map[string]uint64{}
for asset := range needed_assets {
amt := account.Balances[asset]
for req := range ch {
if req.Error != nil {
return fmt.Errorf("could not resolve balances: %v", err)
}
account, err := l.GetAccount(req.Account)
if err != nil {
return fmt.Errorf("could not get account %q: %v", req.Account, err)
}
amt := account.Balances[req.Asset]
if amt < 0 {
amt = 0
}
balances[account_address][asset] = uint64(amt)
req.Response <- uint64(amt)
}
}

err = m.SetBalances(balances)
if err != nil {
return err
}

c, err := m.Execute()
if err != nil {
return fmt.Errorf("script failed: %v", err)
Expand Down
108 changes: 94 additions & 14 deletions ledger/executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,23 @@ import (
"github.com/numary/ledger/core"
)

func assertBalance(t *testing.T, l *Ledger, account string, asset string, amount int64) {
user, err := l.GetAccount(account)
if err != nil {
t.Error(err)
return
}
if b := user.Balances[asset]; b != amount {
t.Fatalf(
"wrong %v balance for account %v, expected: %d got: %d",
asset,
account,
amount,
b,
)
}
}

func TestTransactionInvalidScript(t *testing.T) {
with(func(l *Ledger) {
script := core.Script{
Expand Down Expand Up @@ -60,20 +77,7 @@ func TestSend(t *testing.T) {
return
}

user, err := l.GetAccount("user:001")

if err != nil {
t.Error(err)
return
}

if b := user.Balances["USD/2"]; b != 99 {
t.Error(fmt.Sprintf(
"wrong USD/2 balance for account user:001, expected: %d got: %d",
99,
b,
))
}
assertBalance(t, l, "user:001", "USD/2", 99)
})
}

Expand Down Expand Up @@ -190,3 +194,79 @@ func TestNotEnoughFunds(t *testing.T) {
}
})
}

func TestMetadata(t *testing.T) {
with(func(l *Ledger) {
defer l.Close()

tx := core.Transaction{
Postings: []core.Posting{
{
Source: "world",
Destination: "sales:042",
Amount: 100,
Asset: "COIN",
},
},
}

err := l.Commit([]core.Transaction{tx})

l.SaveMeta("account", "sales:042", core.Metadata{
"seller": json.RawMessage(`{
"type": "account",
"value": "users:053"
}`),
})

l.SaveMeta("account", "users:053", core.Metadata{
"commission": json.RawMessage(`{
"type": "portion",
"value": "15.5%"
}`),
})

if err != nil {
t.Error(err)
return
}

plain := `
vars {
account $sale
account $seller = meta($sale, "seller")
portion $commission = meta($seller, "commission")
}
send [COIN *] (
source = $sale
destination = {
remaining to $seller
$commission to @platform
}
)
`
if err != nil {
t.Fatalf("did not expect error: %v", err)
}

script := core.Script{
Plain: plain,
Vars: map[string]json.RawMessage{
"sale": json.RawMessage(`"sales:042"`),
},
}

err = l.Execute(script)

if err != nil {
t.Fatalf("execution error: %v", err)
}

assertBalance(t, l, "sales:042", "COIN", 0)

assertBalance(t, l, "users:053", "COIN", 85)

assertBalance(t, l, "platform", "COIN", 15)
})
}
10 changes: 10 additions & 0 deletions ledger/ledger.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,5 +214,15 @@ func (l *Ledger) GetAccount(address string) (core.Account, error) {

account.Volumes = volumes

meta, err := l.store.GetMeta("account", address)
if err != nil {
return account, err
}
account.Metadata = meta

return account, nil
}

func (l *Ledger) SaveMeta(ty string, id string, m core.Metadata) error {
return l.store.SaveMeta(ty, id, m)
}
Loading

0 comments on commit 86d4e21

Please sign in to comment.