diff --git a/TODO b/TODO index 834d99b..8e8b692 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,7 @@ # mint - [ ] allow cancellation from hop 0 by attempted propagation to last node - [ ] transaction reference and metadata + [ ] allow cancellation from hop k mint.MemoMaxLength { + return nil, errors.Trace(errors.NewUserErrorf(nil, + 400, "memo_invalid", + "The memo you provided exceeds the maximum length (%d): %s.", + mint.MemoMaxLength, memo, + )) + } + + if len(memo) == 0 { + return nil, nil + } + return &memo, nil +} diff --git a/mint/model/schemas/mint.5.transactions.go b/mint/model/schemas/mint.5.transactions.go index 18caacb..e198753 100644 --- a/mint/model/schemas/mint.5.transactions.go +++ b/mint/model/schemas/mint.5.transactions.go @@ -20,6 +20,8 @@ CREATE TABLE IF NOT EXISTS transactions( lock VARCHAR(256) NOT NULL, -- lock = hex(scrypt(secret, id)) secret VARCHAR(256), -- lock secret + memo VARCHAR(1024), -- memo + PRIMARY KEY(owner, token) ); ` diff --git a/mint/model/transaction.go b/mint/model/transaction.go index 5e2169e..3682e64 100644 --- a/mint/model/transaction.go +++ b/mint/model/transaction.go @@ -35,6 +35,8 @@ type Transaction struct { Lock string Secret *string + + Memo *string } // NewTransactionResource generates a new resource. @@ -57,6 +59,7 @@ func NewTransactionResource( Path: []string(transaction.Path), Status: transaction.Status, Lock: transaction.Lock, + Memo: transaction.Memo, Operations: []mint.OperationResource{}, Crossings: []mint.CrossingResource{}, } @@ -84,6 +87,7 @@ func CreateCanonicalTransaction( destination string, path []string, status mint.TxStatus, + memo *string, ) (*Transaction, error) { tok := token.New("transaction") @@ -109,16 +113,18 @@ func CreateCanonicalTransaction( Lock: lock, Secret: &secret, + + Memo: memo, } ext := db.Ext(ctx, "mint") if _, err := sqlx.NamedExec(ext, ` INSERT INTO transactions (owner, token, created, propagation, base_asset, quote_asset, - amount, destination, path, status, lock, secret) + amount, destination, path, status, lock, secret, memo) VALUES (:owner, :token, :created, :propagation, :base_asset, :quote_asset, - :amount, :destination, :path, :status, :lock, :secret) + :amount, :destination, :path, :status, :lock, :secret, :memo) `, transaction); err != nil { switch err := err.(type) { case *pq.Error: @@ -150,6 +156,7 @@ func CreatePropagatedTransaction( path []string, status mint.TxStatus, lock string, + memo *string, ) (*Transaction, error) { transaction := Transaction{ Owner: owner, @@ -165,16 +172,17 @@ func CreatePropagatedTransaction( Status: status, Lock: lock, Secret: nil, + Memo: memo, } ext := db.Ext(ctx, "mint") if _, err := sqlx.NamedExec(ext, ` INSERT INTO transactions (owner, token, created, propagation, base_asset, quote_asset, - amount, destination, path, status, lock, secret) + amount, destination, path, status, lock, secret, memo) VALUES (:owner, :token, :created, :propagation, :base_asset, :quote_asset, - :amount, :destination, :path, :status, :lock, :secret) + :amount, :destination, :path, :status, :lock, :secret, :memo) `, transaction); err != nil { switch err := err.(type) { case *pq.Error: diff --git a/mint/protocol.go b/mint/protocol.go index 862d22d..0e7f4fc 100644 --- a/mint/protocol.go +++ b/mint/protocol.go @@ -11,6 +11,8 @@ const ( // TransactionExpiryMs is the time it takes to attempt to cancel a // transaction for this mint. Expressed in ms. TransactionExpiryMs int64 = 1000 * 60 * 60 + // MemoMaxLength is the maximal length of a transaction's memo string. + MemoMaxLength int64 = 1024 ) // PgType is the propagation type of an object. @@ -140,6 +142,8 @@ type TransactionResource struct { Lock string `json:"lock"` Secret *string `json:"secret"` + Memo *string `json:"memo"` + Operations []OperationResource `json:"operations"` Crossings []CrossingResource `json:"crossings"` } diff --git a/mint/test/functional/create_transaction_test.go b/mint/test/functional/create_transaction_test.go index 580f60b..80fb95b 100644 --- a/mint/test/functional/create_transaction_test.go +++ b/mint/test/functional/create_transaction_test.go @@ -70,6 +70,7 @@ func TestCreateTransactionWith2Offers( "pair": {fmt.Sprintf("%s/%s", a[0].Name, a[2].Name)}, "amount": {"10"}, "destination": {u[2].Address}, + "memo": {"test-20162017"}, "path[]": { o[1].ID, o[2].ID, @@ -111,6 +112,7 @@ func TestCreateTransactionWith2Offers( assert.Equal(t, mint.TxStReserved, tx0.Operations[0].Status) assert.Equal(t, tx0.ID, *tx0.Operations[0].Transaction) assert.Equal(t, int8(0), *tx0.Operations[0].TransactionHop) + assert.Equal(t, "test-20162017", *tx0.Memo) // Check transaction on m[1]. status, raw = m[1].Get(t, nil, fmt.Sprintf("/transactions/%s", tx0.ID)) @@ -162,6 +164,7 @@ func TestCreateTransactionWith2Offers( assert.Equal(t, mint.TxStReserved, tx1.Operations[0].Status) assert.Equal(t, tx1.ID, *tx1.Operations[0].Transaction) assert.Equal(t, int8(1), *tx1.Operations[0].TransactionHop) + assert.Equal(t, "test-20162017", *tx1.Memo) // Check transaction on m[2]. status, raw = m[2].Get(t, nil, fmt.Sprintf("/transactions/%s", tx0.ID)) @@ -213,6 +216,7 @@ func TestCreateTransactionWith2Offers( assert.Equal(t, mint.TxStReserved, tx2.Operations[0].Status) assert.Equal(t, tx2.ID, *tx2.Operations[0].Transaction) assert.Equal(t, int8(2), *tx2.Operations[0].TransactionHop) + assert.Equal(t, "test-20162017", *tx2.Memo) } func TestCreateTransactionWithInsufficientOfferAmount( @@ -351,6 +355,7 @@ func TestCreateTransactionWith1Offer( assert.Equal(t, big.NewInt(10), tx0.Operations[0].Amount) assert.Equal(t, u[1].Address, tx0.Operations[0].Destination) assert.Equal(t, u[0].Address, tx0.Operations[0].Source) + assert.Nil(t, tx0.Memo) // Check transaction on m[1]. status, raw = m[1].Get(t, nil, fmt.Sprintf("/transactions/%s", tx0.ID)) @@ -373,6 +378,7 @@ func TestCreateTransactionWith1Offer( assert.Equal(t, mint.TxStReserved, tx1.Operations[0].Status) assert.Equal(t, tx1.ID, *tx1.Operations[0].Transaction) assert.Equal(t, int8(1), *tx1.Operations[0].TransactionHop) + assert.Nil(t, tx1.Memo) } func TestCreateTransactionWithRemoteBaseAsset( diff --git a/mint/test/functional/settle_transaction_test.go b/mint/test/functional/settle_transaction_test.go index f5e942a..ee32a0e 100644 --- a/mint/test/functional/settle_transaction_test.go +++ b/mint/test/functional/settle_transaction_test.go @@ -268,6 +268,7 @@ func TestSettleTransactionWithRemoteBaseAsset( "pair": {fmt.Sprintf("%s/%s", a[1].Name, a[2].Name)}, "amount": {"10"}, "destination": {u[2].Address}, + "memo": {"test-20162017"}, "path[]": { o[2].ID, }, @@ -291,6 +292,7 @@ func TestSettleTransactionWithRemoteBaseAsset( assert.Equal(t, mint.TxStSettled, tx0.Status) assert.Equal(t, 0, len(tx0.Operations)) assert.Equal(t, 0, len(tx0.Crossings)) + assert.Equal(t, "test-20162017", *tx0.Memo) // Check transaction on m[1]. status, raw = m[1].Get(t, nil, fmt.Sprintf("/transactions/%s", tx0.ID)) @@ -303,6 +305,7 @@ func TestSettleTransactionWithRemoteBaseAsset( assert.Equal(t, mint.TxStSettled, tx1.Status) assert.Equal(t, 0, len(tx1.Crossings)) assert.Equal(t, 1, len(tx1.Operations)) + assert.Equal(t, "test-20162017", *tx1.Memo) assert.Equal(t, mint.TxStSettled, tx1.Operations[0].Status) @@ -317,6 +320,7 @@ func TestSettleTransactionWithRemoteBaseAsset( assert.Equal(t, mint.TxStSettled, tx2.Status) assert.Equal(t, 1, len(tx2.Crossings)) assert.Equal(t, 1, len(tx2.Operations)) + assert.Equal(t, "test-20162017", *tx2.Memo) assert.Equal(t, mint.TxStSettled, tx2.Crossings[0].Status) assert.Equal(t, mint.TxStSettled, tx2.Operations[0].Status)