diff --git a/manager/eris-mint/eris-mint.go b/manager/eris-mint/eris-mint.go index 4375a19920590a2c3e022d088248e31dad4423d4..809540879b1d262b843adadcd544dff5126e609e 100644 --- a/manager/eris-mint/eris-mint.go +++ b/manager/eris-mint/eris-mint.go @@ -159,7 +159,7 @@ func (app *ErisMint) CheckTx(txBytes []byte) (res tmsp.Result) { return tmsp.NewError(tmsp.CodeType_EncodingError, fmt.Sprintf("Encoding error: %v", err)) } - // TODO: make errors tmsp aware + // TODO: map ExecTx errors to sensible TMSP error codes err = sm.ExecTx(app.checkCache, *tx, false, nil) if err != nil { return tmsp.NewError(tmsp.CodeType_InternalError, fmt.Sprintf("Internal error: %v", err)) diff --git a/manager/eris-mint/state/execution.go b/manager/eris-mint/state/execution.go index 34d8afd049e7ae8b3d0879aca10f445965807d52..ceefbbd8368a2491e1a743f87b823fd40558935e 100644 --- a/manager/eris-mint/state/execution.go +++ b/manager/eris-mint/state/execution.go @@ -571,6 +571,7 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable if entry != nil { var expired bool + // if the entry already exists, and hasn't expired, we must be owner if entry.Expires > lastBlockHeight { // ensure we are owner diff --git a/rpc/tendermint/test/client_ws_test.go b/rpc/tendermint/test/client_ws_test.go index 69153da425e711d7ef9ab9ab67b1af01b9db777c..2f55ebf9ccb803378d319b2b268816c71df26c8a 100644 --- a/rpc/tendermint/test/client_ws_test.go +++ b/rpc/tendermint/test/client_ws_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/eris-ltd/eris-db/txs" + "github.com/stretchr/testify/assert" _ "github.com/tendermint/tendermint/config/tendermint_test" ) @@ -28,10 +29,10 @@ func TestWSNewBlock(t *testing.T) { unsubscribe(t, wsc, eid) wsc.Stop() }() - waitForEvent(t, wsc, eid, true, func() {}, - func(eid string, eventData txs.EventData) error { + waitForEvent(t, wsc, eid, func() {}, + func(eid string, eventData txs.EventData) (bool, error) { fmt.Println("Check: ", eventData.(txs.EventDataNewBlock).Block) - return nil + return true, nil }) } @@ -50,8 +51,8 @@ func TestWSBlockchainGrowth(t *testing.T) { // listen for NewBlock, ensure height increases by 1 var initBlockN int for i := 0; i < 2; i++ { - waitForEvent(t, wsc, eid, true, func() {}, - func(eid string, eventData txs.EventData) error { + waitForEvent(t, wsc, eid, func() {}, + func(eid string, eventData txs.EventData) (bool, error) { eventDataNewBlock, ok := eventData.(txs.EventDataNewBlock) if !ok { t.Fatalf("Was expecting EventDataNewBlock but got %v", eventData) @@ -61,11 +62,12 @@ func TestWSBlockchainGrowth(t *testing.T) { initBlockN = block.Height } else { if block.Header.Height != initBlockN+i { - return fmt.Errorf("Expected block %d, got block %d", i, block.Header.Height) + return true, fmt.Errorf("Expected block %d, got block %d", i, + block.Header.Height) } } - return nil + return true, nil }) } } @@ -85,11 +87,13 @@ func TestWSSend(t *testing.T) { unsubscribe(t, wsc, eidOutput) wsc.Stop() }() - waitForEvent(t, wsc, eidInput, true, func() { + waitForEvent(t, wsc, eidInput, func() { tx := makeDefaultSendTxSigned(t, wsTyp, toAddr, amt) broadcastTx(t, wsTyp, tx) }, unmarshalValidateSend(amt, toAddr)) - waitForEvent(t, wsc, eidOutput, true, func() {}, unmarshalValidateSend(amt, toAddr)) + + waitForEvent(t, wsc, eidOutput, func() {}, + unmarshalValidateSend(amt, toAddr)) } // ensure events are only fired once for a given transaction @@ -107,17 +111,20 @@ func TestWSDoubleFire(t *testing.T) { amt := int64(100) toAddr := user[1].Address // broadcast the transaction, wait to hear about it - waitForEvent(t, wsc, eid, true, func() { + waitForEvent(t, wsc, eid, func() { tx := makeDefaultSendTxSigned(t, wsTyp, toAddr, amt) broadcastTx(t, wsTyp, tx) - }, func(eid string, b txs.EventData) error { - return nil + }, func(eid string, b txs.EventData) (bool, error) { + return true, nil }) // but make sure we don't hear about it twice - waitForEvent(t, wsc, eid, false, func() { - }, func(eid string, b txs.EventData) error { - return nil - }) + err := waitForEvent(t, wsc, eid, + func() {}, + func(eid string, b txs.EventData) (bool, error) { + return false, nil + }) + assert.True(t, err.Timeout(), "We should have timed out waiting for second"+ + " %v event", eid) } // create a contract, wait for the event, and send it a msg, validate the return @@ -136,7 +143,7 @@ func TestWSCallWait(t *testing.T) { code, returnCode, returnVal := simpleContract() var contractAddr []byte // wait for the contract to be created - waitForEvent(t, wsc, eid1, true, func() { + waitForEvent(t, wsc, eid1, func() { tx := makeDefaultCallTx(t, wsTyp, nil, code, amt, gasLim, fee) receipt := broadcastTx(t, wsTyp, tx) contractAddr = receipt.ContractAddr @@ -151,7 +158,7 @@ func TestWSCallWait(t *testing.T) { }() // get the return value from a call data := []byte{0x1} - waitForEvent(t, wsc, eid2, true, func() { + waitForEvent(t, wsc, eid2, func() { tx := makeDefaultCallTx(t, wsTyp, contractAddr, data, amt, gasLim, fee) receipt := broadcastTx(t, wsTyp, tx) contractAddr = receipt.ContractAddr @@ -182,7 +189,7 @@ func TestWSCallNoWait(t *testing.T) { }() // get the return value from a call data := []byte{0x1} - waitForEvent(t, wsc, eid, true, func() { + waitForEvent(t, wsc, eid, func() { tx := makeDefaultCallTx(t, wsTyp, contractAddr, data, amt, gasLim, fee) broadcastTx(t, wsTyp, tx) }, unmarshalValidateTx(amt, returnVal)) @@ -219,12 +226,12 @@ func TestWSCallCall(t *testing.T) { // call contract2, which should call contract1, and wait for ev1 // let the contract get created first - waitForEvent(t, wsc, eid1, true, func() { - }, func(eid string, b txs.EventData) error { - return nil + waitForEvent(t, wsc, eid1, func() { + }, func(eid string, b txs.EventData) (bool, error) { + return true, nil }) // call it - waitForEvent(t, wsc, eid1, true, func() { + waitForEvent(t, wsc, eid1, func() { tx := makeDefaultCallTx(t, wsTyp, contractAddr2, nil, amt, gasLim, fee) broadcastTx(t, wsTyp, tx) *txid = txs.TxHash(chainID, tx) diff --git a/rpc/tendermint/test/tests.go b/rpc/tendermint/test/tests.go index 832534e9b0ec12f97f26470fa85f7900fadf5f78..f1eac5cba98557aeca1ad8964952e95089c48c83 100644 --- a/rpc/tendermint/test/tests.go +++ b/rpc/tendermint/test/tests.go @@ -8,14 +8,11 @@ import ( edbcli "github.com/eris-ltd/eris-db/rpc/tendermint/client" "github.com/eris-ltd/eris-db/txs" + "github.com/stretchr/testify/assert" tm_common "github.com/tendermint/go-common" "golang.org/x/crypto/ripemd160" ) -func doNothing(eventId string, eventData txs.EventData) error { - return nil -} - func testStatus(t *testing.T, typ string) { client := clients[typ] resp, err := edbcli.Status(client) @@ -105,7 +102,7 @@ func testGetStorage(t *testing.T, typ string) { } // allow it to get mined - waitForEvent(t, wsc, eid, true, func() {}, doNothing) + waitForEvent(t, wsc, eid, func() {}, doNothing) mempoolCount = 0 v := getStorage(t, typ, contractAddr, []byte{0x1}) @@ -165,7 +162,7 @@ func testCall(t *testing.T, typ string) { } // allow it to get mined - waitForEvent(t, wsc, eid, true, func() {}, doNothing) + waitForEvent(t, wsc, eid, func() {}, doNothing) mempoolCount = 0 // run a call through the contract @@ -175,7 +172,6 @@ func testCall(t *testing.T, typ string) { } func testNameReg(t *testing.T, typ string) { - client := clients[typ] wsc := newWSClient(t) txs.MinNameRegistrationPeriod = 1 @@ -183,86 +179,79 @@ func testNameReg(t *testing.T, typ string) { // register a new name, check if its there // since entries ought to be unique and these run against different clients, we append the typ name := "ye_old_domain_name_" + typ - data := "if not now, when" + const data = "if not now, when" fee := int64(1000) numDesiredBlocks := int64(2) amt := fee + numDesiredBlocks*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) - eid := txs.EventStringNameReg(name) - subscribe(t, wsc, eid) - tx := makeDefaultNameTx(t, typ, name, data, amt, fee) - broadcastTx(t, typ, tx) // verify the name by both using the event and by checking get_name - waitForEvent(t, wsc, eid, true, func() {}, func(eid string, b txs.EventData) error { // TODO: unmarshal thtxs.EventData _ = b // TODO - tx, err := unmarshalResponseNameReg([]byte{}) - if err != nil { - return err - } - if tx.Name != name { - t.Fatal(fmt.Sprintf("Err on received event tx.Name: Got %s, expected %s", tx.Name, name)) - } - if tx.Data != data { - t.Fatal(fmt.Sprintf("Err on received event tx.Data: Got %s, expected %s", tx.Data, data)) - } - return nil - }) + subscribeAndWaitForNext(t, wsc, txs.EventStringNameReg(name), + func() { + broadcastTxAndWaitForBlock(t, typ, wsc, tx) + }, + func(eid string, eventData txs.EventData) (bool, error) { + eventDataTx := asEventDataTx(t, eventData) + tx, ok := eventDataTx.Tx.(*txs.NameTx) + if !ok { + t.Fatalf("Could not convert %v to *NameTx", eventDataTx) + } + assert.Equal(t, name, tx.Name) + assert.Equal(t, data, tx.Data) + return true, nil + }) mempoolCount = 0 - entry := getNameRegEntry(t, typ, name) - if entry.Data != data { - t.Fatal(fmt.Sprintf("Err on entry.Data: Got %s, expected %s", entry.Data, data)) - } - if bytes.Compare(entry.Owner, user[0].Address) != 0 { - t.Fatal(fmt.Sprintf("Err on entry.Owner: Got %s, expected %s", entry.Owner, user[0].Address)) - } - - unsubscribe(t, wsc, eid) - // for the rest we just use new block event - // since we already tested the namereg event - eid = txs.EventStringNewBlock() - subscribe(t, wsc, eid) - defer func() { - unsubscribe(t, wsc, eid) - wsc.Stop() - }() + entry := getNameRegEntry(t, typ, name) + assert.Equal(t, data, entry.Data) + assert.Equal(t, user[0].Address, entry.Owner) // update the data as the owner, make sure still there - numDesiredBlocks = int64(2) - data = "these are amongst the things I wish to bestow upon the youth of generations come: a safe supply of honey, and a better money. For what else shall they need" - amt = fee + numDesiredBlocks*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) - tx = makeDefaultNameTx(t, typ, name, data, amt, fee) - broadcastTx(t, typ, tx) - // commit block - waitForEvent(t, wsc, eid, true, func() {}, doNothing) + numDesiredBlocks = int64(4) + const updatedData = "these are amongst the things I wish to bestow upon the youth of generations come: a safe supply of honey, and a better money. For what else shall they need" + amt = fee + numDesiredBlocks*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, updatedData) + tx = makeDefaultNameTx(t, typ, name, updatedData, amt, fee) + broadcastTxAndWaitForBlock(t, typ, wsc, tx) mempoolCount = 0 entry = getNameRegEntry(t, typ, name) - if entry.Data != data { - t.Fatal(fmt.Sprintf("Err on entry.Data: Got %s, expected %s", entry.Data, data)) - } + + assert.Equal(t, updatedData, entry.Data) // try to update as non owner, should fail - nonce := getNonce(t, typ, user[1].Address) - data2 := "this is not my beautiful house" - tx = txs.NewNameTxWithNonce(user[1].PubKey, name, data2, amt, fee, nonce+1) + //waitBlocks(t, wsc, 4) + tx = txs.NewNameTxWithNonce(user[1].PubKey, name, "never mind", amt, fee, + getNonce(t, typ, user[1].Address)+1) tx.Sign(chainID, user[1]) - _, err := edbcli.BroadcastTx(client, tx) - if err == nil { - t.Fatal("Expected error on NameTx") - } - // commit block - waitForEvent(t, wsc, eid, true, func() {}, doNothing) + _, err := broadcastTxAndWaitForBlock(t, typ, wsc, tx) - // now the entry should be expired, so we can update as non owner - _, err = edbcli.BroadcastTx(client, tx) - waitForEvent(t, wsc, eid, true, func() {}, doNothing) + assert.Error(t, err, "Expected error when updating someone else's unexpired"+ + " name registry entry") + //now the entry should be expired, so we can update as non owner + const data2 = "this is not my beautiful house" + tx = txs.NewNameTxWithNonce(user[1].PubKey, name, data2, amt, fee, + getNonce(t, typ, user[1].Address)+1) + tx.Sign(chainID, user[1]) + _, err = broadcastTxAndWaitForBlock(t, typ, wsc, tx) + assert.NoError(t, err, "Should be able to update a previously expired name"+ + " registry entry as a different address") mempoolCount = 0 entry = getNameRegEntry(t, typ, name) - if entry.Data != data2 { - t.Fatal(fmt.Sprintf("Error on entry.Data: Got %s, expected %s", entry.Data, data2)) - } - if bytes.Compare(entry.Owner, user[1].Address) != 0 { - t.Fatal(fmt.Sprintf("Err on entry.Owner: Got %s, expected %s", entry.Owner, user[1].Address)) + assert.Equal(t, data2, entry.Data) + assert.Equal(t, user[1].Address, entry.Owner) +} + +// ------ Test Utils ------- + +func asEventDataTx(t *testing.T, eventData txs.EventData) txs.EventDataTx { + eventDataTx, ok := eventData.(txs.EventDataTx) + if !ok { + t.Fatalf("Expected eventData to be EventDataTx was %v", eventData) } + return eventDataTx +} + +func doNothing(eventId string, eventData txs.EventData) (bool, error) { + // And ask waitForEvent to stop waiting + return true, nil } diff --git a/rpc/tendermint/test/ws_helpers.go b/rpc/tendermint/test/ws_helpers.go index 76c624109634c60669930b0d450829131e5bbfff..bd694259e390b1e011284176ec21a9a8594330a6 100644 --- a/rpc/tendermint/test/ws_helpers.go +++ b/rpc/tendermint/test/ws_helpers.go @@ -10,7 +10,9 @@ import ( ctypes "github.com/eris-ltd/eris-db/rpc/tendermint/core/types" "github.com/eris-ltd/eris-db/txs" "github.com/tendermint/tendermint/types" + tm_types "github.com/tendermint/tendermint/types" + edbcli "github.com/eris-ltd/eris-db/rpc/tendermint/client" "github.com/tendermint/go-events" client "github.com/tendermint/go-rpc/client" rpctypes "github.com/tendermint/go-rpc/types" @@ -47,14 +49,77 @@ func unsubscribe(t *testing.T, wsc *client.WSClient, eventid string) { } } -// wait for an event; do things that might trigger events, and check them when they are received -// the check function takes an event id and the byte slice read off the ws +// broadcast transaction and wait for new block +func broadcastTxAndWaitForBlock(t *testing.T, typ string, wsc *client.WSClient, + tx txs.Tx) (txs.Receipt, error) { + var rec txs.Receipt + var err error + initialHeight := -1 + runThenWaitForBlock(t, wsc, + func(block *tm_types.Block) bool { + if initialHeight < 0 { + initialHeight = block.Height + return false + } else { + return block.Height > initialHeight + } + }, + func() { + rec, err = edbcli.BroadcastTx(clients[typ], tx) + mempoolCount += 1 + }) + return rec, err +} + +func waitNBlocks(t *testing.T, wsc *client.WSClient, n int) { + i := 0 + runThenWaitForBlock(t, wsc, + func(block *tm_types.Block) bool { + i++ + return i <= n + }, + func() {}) +} + +func runThenWaitForBlock(t *testing.T, wsc *client.WSClient, + blockPredicate func(*tm_types.Block) bool, runner func()) { + subscribeAndWaitForNext(t, wsc, txs.EventStringNewBlock(), + runner, + func(event string, eventData txs.EventData) (bool, error) { + return blockPredicate(eventData.(txs.EventDataNewBlock).Block), nil + }) +} + +func subscribeAndWaitForNext(t *testing.T, wsc *client.WSClient, event string, + runner func(), + eventDataChecker func(string, txs.EventData) (bool, error)) { + subscribe(t, wsc, event) + waitForEvent(t, + wsc, + event, + runner, + eventDataChecker) + unsubscribe(t, wsc, event) +} + +// waitForEvent executes runner that is expected to trigger events. It then +// waits for any events on the supplies WSClient and checks the eventData with +// the eventDataChecker which is a function that is passed the event name +// and the EventData and returns the pair of stopWaiting, err. Where if +// stopWaiting is true waitForEvent will return or if stopWaiting is false +// waitForEvent will keep listening for new events. If an error is returned +// waitForEvent will fail the test. func waitForEvent(t *testing.T, wsc *client.WSClient, eventid string, - dieOnTimeout bool, f func(), check func(string, txs.EventData) error) { - // go routine to wait for webscoket msg + runner func(), + eventDataChecker func(string, txs.EventData) (bool, error)) waitForEventError { + + // go routine to wait for websocket msg goodCh := make(chan txs.EventData) errCh := make(chan error) + // do stuff (transactions) + runner() + // Read message go func() { var err error @@ -82,37 +147,40 @@ func waitForEvent(t *testing.T, wsc *client.WSClient, eventid string, } }() - // do stuff (transactions) - f() - // wait for an event or timeout timeout := time.NewTimer(timeoutSeconds * time.Second) - select { - case <-timeout.C: - if dieOnTimeout { - wsc.Stop() - t.Fatalf("%s event was not received in time", eventid) - } - // else that's great, we didn't hear the event - // and we shouldn't have - case eventData := <-goodCh: - if dieOnTimeout { - // message was received and expected + for { + select { + case <-timeout.C: + return waitForEventError{timeout: true} + case eventData := <-goodCh: // run the check - if err := check(eventid, eventData); err != nil { + stopWaiting, err := eventDataChecker(eventid, eventData) + if err != nil { t.Fatal(err) // Show the stack trace. } - } else { - wsc.Stop() - t.Fatalf("%s event was not expected", eventid) + if stopWaiting { + return waitForEventError{} + } + case err := <-errCh: + t.Fatal(err) } - case err := <-errCh: - t.Fatal(err) - panic(err) // Show the stack trace. - } } +type waitForEventError struct { + error + timeout bool +} + +func (err waitForEventError) Timeout() bool { + return err.timeout +} + +func acceptFirstBlock(_ *tm_types.Block) bool { + return true +} + //-------------------------------------------------------------------------------- func unmarshalResponseNewBlock(b []byte) (*types.Block, error) { @@ -148,68 +216,71 @@ func unmarshalResponseNameReg(b []byte) (*txs.NameTx, error) { return tx, nil } -func unmarshalValidateSend(amt int64, toAddr []byte) func(string, txs.EventData) error { - return func(eid string, eventData txs.EventData) error { +func unmarshalValidateSend(amt int64, + toAddr []byte) func(string, txs.EventData) (bool, error) { + return func(eid string, eventData txs.EventData) (bool, error) { var data = eventData.(txs.EventDataTx) if data.Exception != "" { - return fmt.Errorf(data.Exception) + return true, fmt.Errorf(data.Exception) } tx := data.Tx.(*txs.SendTx) if !bytes.Equal(tx.Inputs[0].Address, user[0].Address) { - return fmt.Errorf("Senders do not match up! Got %x, expected %x", tx.Inputs[0].Address, user[0].Address) + return true, fmt.Errorf("Senders do not match up! Got %x, expected %x", tx.Inputs[0].Address, user[0].Address) } if tx.Inputs[0].Amount != amt { - return fmt.Errorf("Amt does not match up! Got %d, expected %d", tx.Inputs[0].Amount, amt) + return true, fmt.Errorf("Amt does not match up! Got %d, expected %d", tx.Inputs[0].Amount, amt) } if !bytes.Equal(tx.Outputs[0].Address, toAddr) { - return fmt.Errorf("Receivers do not match up! Got %x, expected %x", tx.Outputs[0].Address, user[0].Address) + return true, fmt.Errorf("Receivers do not match up! Got %x, expected %x", tx.Outputs[0].Address, user[0].Address) } - return nil + return true, nil } } -func unmarshalValidateTx(amt int64, returnCode []byte) func(string, txs.EventData) error { - return func(eid string, eventData txs.EventData) error { +func unmarshalValidateTx(amt int64, + returnCode []byte) func(string, txs.EventData) (bool, error) { + return func(eid string, eventData txs.EventData) (bool, error) { var data = eventData.(txs.EventDataTx) if data.Exception != "" { - return fmt.Errorf(data.Exception) + return true, fmt.Errorf(data.Exception) } tx := data.Tx.(*txs.CallTx) if !bytes.Equal(tx.Input.Address, user[0].Address) { - return fmt.Errorf("Senders do not match up! Got %x, expected %x", + return true, fmt.Errorf("Senders do not match up! Got %x, expected %x", tx.Input.Address, user[0].Address) } if tx.Input.Amount != amt { - return fmt.Errorf("Amt does not match up! Got %d, expected %d", + return true, fmt.Errorf("Amt does not match up! Got %d, expected %d", tx.Input.Amount, amt) } ret := data.Return if !bytes.Equal(ret, returnCode) { - return fmt.Errorf("Tx did not return correctly. Got %x, expected %x", ret, returnCode) + return true, fmt.Errorf("Tx did not return correctly. Got %x, expected %x", ret, returnCode) } - return nil + return true, nil } } -func unmarshalValidateCall(origin, returnCode []byte, txid *[]byte) func(string, txs.EventData) error { - return func(eid string, eventData txs.EventData) error { +func unmarshalValidateCall(origin, + returnCode []byte, txid *[]byte) func(string, txs.EventData) (bool, error) { + return func(eid string, eventData txs.EventData) (bool, error) { var data = eventData.(txs.EventDataCall) if data.Exception != "" { - return fmt.Errorf(data.Exception) + return true, fmt.Errorf(data.Exception) } if !bytes.Equal(data.Origin, origin) { - return fmt.Errorf("Origin does not match up! Got %x, expected %x", + return true, fmt.Errorf("Origin does not match up! Got %x, expected %x", data.Origin, origin) } ret := data.Return if !bytes.Equal(ret, returnCode) { - return fmt.Errorf("Call did not return correctly. Got %x, expected %x", ret, returnCode) + return true, fmt.Errorf("Call did not return correctly. Got %x, expected %x", ret, returnCode) } if !bytes.Equal(data.TxID, *txid) { - return fmt.Errorf("TxIDs do not match up! Got %x, expected %x", + return true, fmt.Errorf("TxIDs do not match up! Got %x, expected %x", data.TxID, *txid) } - return nil + return true, nil } }