diff --git a/consensus/types/consensus_state.go b/consensus/types/consensus_state.go index 80f8fc876bbeb522badddaf35317bd926fc81dbc..a22febd4fcd8ea4d37023b3a4ac00e84583ae7e5 100644 --- a/consensus/types/consensus_state.go +++ b/consensus/types/consensus_state.go @@ -1,6 +1,8 @@ package types import ( + "time" + tendermint_consensus "github.com/tendermint/tendermint/consensus" tendermint_types "github.com/tendermint/tendermint/types" ) @@ -10,19 +12,19 @@ type ConsensusState struct { Height int `json:"height"` Round int `json:"round"` Step uint8 `json:"step"` - StartTime string `json:"start_time"` - CommitTime string `json:"commit_time"` + StartTime time.Time `json:"start_time"` + CommitTime time.Time `json:"commit_time"` Validators []Validator `json:"validators"` Proposal *tendermint_types.Proposal `json:"proposal"` } func FromRoundState(rs *tendermint_consensus.RoundState) *ConsensusState { cs := &ConsensusState{ - CommitTime: rs.CommitTime.String(), + StartTime: rs.StartTime, + CommitTime: rs.CommitTime, Height: rs.Height, Proposal: rs.Proposal, Round: rs.Round, - StartTime: rs.StartTime.String(), Step: uint8(rs.Step), Validators: FromTendermintValidators(rs.Validators.Validators), } diff --git a/manager/eris-mint/pipe.go b/manager/eris-mint/pipe.go index b806b8c8983915fa86554e9b19139cefb8d296b6..fe07891316f8961262c01d48c9fad34aa4798017 100644 --- a/manager/eris-mint/pipe.go +++ b/manager/eris-mint/pipe.go @@ -641,8 +641,17 @@ func (pipe *erisMintPipe) ListValidators() (*rpc_tm_types.ResultListValidators, } func (pipe *erisMintPipe) DumpConsensusState() (*rpc_tm_types.ResultDumpConsensusState, error) { - return &rpc_tm_types.ResultDumpConsensusState{ + statesMap := pipe.consensusEngine.PeerConsensusStates() + peerStates := make([]*rpc_tm_types.ResultPeerConsensusState, len(statesMap)) + for key, peerState := range statesMap { + peerStates = append(peerStates, &rpc_tm_types.ResultPeerConsensusState{ + PeerKey: key, + PeerConsensusState: peerState, + }) + } + dump := rpc_tm_types.ResultDumpConsensusState{ ConsensusState: pipe.consensusEngine.ConsensusState(), - PeerConsensusStates: pipe.consensusEngine.PeerConsensusStates(), - }, nil + PeerConsensusStates: peerStates, + } + return &dump, nil } diff --git a/rpc/tendermint/client/client.go b/rpc/tendermint/client/client.go index 53869372ce75d5b0242094887f470ad5d7fd11a6..3108c3f0325befcbfc1791e0697a0030940cd5f8 100644 --- a/rpc/tendermint/client/client.go +++ b/rpc/tendermint/client/client.go @@ -126,6 +126,15 @@ func BlockchainInfo(client rpcclient.Client, minHeight, return res.(*rpc_types.ResultBlockchainInfo), err } +func GetBlock(client rpcclient.Client, height int) (*rpc_types.ResultGetBlock, error) { + res, err := performCall(client, "get_block", + "height", height) + if err != nil { + return nil, err + } + return res.(*rpc_types.ResultGetBlock), err +} + func ListUnconfirmedTxs(client rpcclient.Client) (*rpc_types.ResultListUnconfirmedTxs, error) { res, err := performCall(client, "list_unconfirmed_txs") if err != nil { @@ -134,6 +143,22 @@ func ListUnconfirmedTxs(client rpcclient.Client) (*rpc_types.ResultListUnconfirm return res.(*rpc_types.ResultListUnconfirmedTxs), err } +func ListValidators(client rpcclient.Client) (*rpc_types.ResultListValidators, error) { + res, err := performCall(client, "list_validators") + if err != nil { + return nil, err + } + return res.(*rpc_types.ResultListValidators), err +} + +func DumpConsensusState(client rpcclient.Client) (*rpc_types.ResultDumpConsensusState, error) { + res, err := performCall(client, "dump_consensus_state") + if err != nil { + return nil, err + } + return res.(*rpc_types.ResultDumpConsensusState), err +} + func performCall(client rpcclient.Client, method string, paramKeyVals ...interface{}) (res rpc_types.ErisDBResult, err error) { paramsMap, paramsSlice, err := mapAndValues(paramKeyVals...) diff --git a/rpc/tendermint/core/routes.go b/rpc/tendermint/core/routes.go index 4d215aa85f74cc166e853215511642d66ff0d1cf..a9dfa887eecdc7d9d50bf54c1ceefef099500bbb 100644 --- a/rpc/tendermint/core/routes.go +++ b/rpc/tendermint/core/routes.go @@ -40,8 +40,8 @@ func (tmRoutes *TendermintRoutes) GetRoutes() map[string]*rpc.RPCFunc { "broadcast_tx": rpc.NewRPCFunc(tmRoutes.BroadcastTxResult, "tx"), "blockchain": rpc.NewRPCFunc(tmRoutes.BlockchainInfo, "minHeight,maxHeight"), "get_block": rpc.NewRPCFunc(tmRoutes.GetBlock, "height"), - "list_validators": rpc.NewRPCFunc(tmRoutes.ListValidators, ""), "list_unconfirmed_txs": rpc.NewRPCFunc(tmRoutes.ListUnconfirmedTxs, ""), + "list_validators": rpc.NewRPCFunc(tmRoutes.ListValidators, ""), "dump_consensus_state": rpc.NewRPCFunc(tmRoutes.DumpConsensusState, ""), "unsafe/gen_priv_account": rpc.NewRPCFunc(tmRoutes.GenPrivAccountResult, ""), "unsafe/sign_tx": rpc.NewRPCFunc(tmRoutes.SignTxResult, "tx,privAccounts"), @@ -237,10 +237,6 @@ func (tmRoutes *TendermintRoutes) ListValidators() (ctypes.ErisDBResult, error) } } func (tmRoutes *TendermintRoutes) DumpConsensusState() (ctypes.ErisDBResult, error) { - r, err := tmRoutes.tendermintPipe.DumpConsensusState() - if err != nil { - return nil, err - } else { - return r, nil - } + return tmRoutes.tendermintPipe.DumpConsensusState() } + diff --git a/rpc/tendermint/core/types/responses.go b/rpc/tendermint/core/types/responses.go index 41aee0262d21f10f29922b7d1f02ae81f95f0118..a9cf010e939fcc207b1bfd8d2f5d1ea84162ab53 100644 --- a/rpc/tendermint/core/types/responses.go +++ b/rpc/tendermint/core/types/responses.go @@ -1,4 +1,4 @@ -package core_types +package types import ( acm "github.com/eris-ltd/eris-db/account" @@ -83,7 +83,12 @@ type ResultListValidators struct { type ResultDumpConsensusState struct { ConsensusState *consensus_types.ConsensusState `json:"consensus_state"` - PeerConsensusStates map[string]string `json:"peer_consensus_states"` + PeerConsensusStates []*ResultPeerConsensusState `json:"peer_consensus_states"` +} + +type ResultPeerConsensusState struct { + PeerKey string `json:"peer_key"` + PeerConsensusState string `json:"peer_consensus_state"` } type ResultListNames struct { @@ -152,6 +157,7 @@ const ( ResultTypeEvent = byte(0x13) // so websockets can respond to rpc functions ResultTypeSubscribe = byte(0x14) ResultTypeUnsubscribe = byte(0x15) + ResultTypePeerConsensusState = byte(0x16) ) type ErisDBResult interface { @@ -170,6 +176,7 @@ func ConcreteTypes() []wire.ConcreteType { {&ResultNetInfo{}, ResultTypeNetInfo}, {&ResultListValidators{}, ResultTypeListValidators}, {&ResultDumpConsensusState{}, ResultTypeDumpConsensusState}, + {&ResultDumpConsensusState{}, ResultTypePeerConsensusState}, {&ResultListNames{}, ResultTypeListNames}, {&ResultGenPrivAccount{}, ResultTypeGenPrivAccount}, {&ResultGetAccount{}, ResultTypeGetAccount}, diff --git a/rpc/tendermint/core/types/responses_test.go b/rpc/tendermint/core/types/responses_test.go new file mode 100644 index 0000000000000000000000000000000000000000..bf009eab11b57c3dd3df384f8cf903c3ae2f01a4 --- /dev/null +++ b/rpc/tendermint/core/types/responses_test.go @@ -0,0 +1,34 @@ +package types + +import ( + "testing" + + "time" + + consensus_types "github.com/eris-ltd/eris-db/consensus/types" + "github.com/tendermint/go-wire" + tendermint_types "github.com/tendermint/tendermint/types" +) + +func TestResultDumpConsensusState(t *testing.T) { + result := ResultDumpConsensusState{ + ConsensusState: &consensus_types.ConsensusState{ + Height: 3, + Round: 1, + Step: uint8(1), + StartTime: time.Now().Add(-time.Second * 100), + CommitTime: time.Now().Add(-time.Second * 10), + Validators: []consensus_types.Validator{ + &consensus_types.TendermintValidator{}, + }, + Proposal: &tendermint_types.Proposal{}, + }, + PeerConsensusStates: []*ResultPeerConsensusState{ + { + PeerKey: "Foo", + PeerConsensusState: "Bar", + }, + }, + } + wire.JSONBytes(result) +} diff --git a/rpc/tendermint/test/rpc_client_test.go b/rpc/tendermint/test/rpc_client_test.go index 2e25c02f04be718dc249f8c71c95451f6c67cfc4..7183f2df8f1effbafe76705e344fd1658c01852a 100644 --- a/rpc/tendermint/test/rpc_client_test.go +++ b/rpc/tendermint/test/rpc_client_test.go @@ -12,6 +12,7 @@ import ( "time" + consensus_types "github.com/eris-ltd/eris-db/consensus/types" edbcli "github.com/eris-ltd/eris-db/rpc/tendermint/client" "github.com/eris-ltd/eris-db/txs" "github.com/stretchr/testify/assert" @@ -37,9 +38,7 @@ func testWithAllClients(t *testing.T, func TestStatus(t *testing.T) { testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { resp, err := edbcli.Status(client) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) fmt.Println(resp) if resp.NodeInfo.Network != chainID { t.Fatal(fmt.Errorf("ChainID mismatch: got %s expected %s", @@ -49,7 +48,7 @@ func TestStatus(t *testing.T) { } func TestBroadcastTx(t *testing.T) { - wsc := newWSClient(t) + wsc := newWSClient() testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { // Avoid duplicate Tx in mempool amt := hashString(clientName) % 1000 @@ -57,9 +56,7 @@ func TestBroadcastTx(t *testing.T) { tx := makeDefaultSendTxSigned(t, client, toAddr, amt) //receipt := broadcastTx(t, client, tx) receipt, err := broadcastTxAndWaitForBlock(t, client, wsc, tx) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) if receipt.CreatesContract > 0 { t.Fatal("This tx does not create a contract") } @@ -70,9 +67,7 @@ func TestBroadcastTx(t *testing.T) { buf := new(bytes.Buffer) hasher := ripemd160.New() tx.WriteSignBytes(chainID, buf, n, errp) - if *errp != nil { - t.Fatal(*errp) - } + assert.NoError(t, *errp) txSignBytes := buf.Bytes() hasher.Write(txSignBytes) txHashExpected := hasher.Sum(nil) @@ -104,8 +99,8 @@ func TestGetStorage(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } + wsc := newWSClient() testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { - wsc := newWSClient(t) eid := txs.EventStringNewBlock() subscribe(t, wsc, eid) defer func() { @@ -118,9 +113,7 @@ func TestGetStorage(t *testing.T) { // Call with nil address will create a contract tx := makeDefaultCallTx(t, client, nil, code, amt, gasLim, fee) receipt, err := broadcastTxAndWaitForBlock(t, client, wsc, tx) - if err != nil { - t.Fatalf("Problem broadcasting transaction: %v", err) - } + assert.NoError(t, err) assert.Equal(t, uint8(1), receipt.CreatesContract, "This transaction should"+ " create a contract") assert.NotEqual(t, 0, len(receipt.TxHash), "Receipt should contain a"+ @@ -166,8 +159,8 @@ func TestCallContract(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } + wsc := newWSClient() testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { - wsc := newWSClient(t) eid := txs.EventStringNewBlock() subscribe(t, wsc, eid) defer func() { @@ -180,6 +173,7 @@ func TestCallContract(t *testing.T) { code, _, _ := simpleContract() tx := makeDefaultCallTx(t, client, nil, code, amt, gasLim, fee) receipt, err := broadcastTxAndWaitForBlock(t, client, wsc, tx) + assert.NoError(t, err) if err != nil { t.Fatalf("Problem broadcasting transaction: %v", err) } @@ -202,8 +196,8 @@ func TestNameReg(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } + wsc := newWSClient() testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { - wsc := newWSClient(t) txs.MinNameRegistrationPeriod = 1 @@ -283,8 +277,8 @@ func TestNameReg(t *testing.T) { } func TestBlockchainInfo(t *testing.T) { + wsc := newWSClient() testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { - wsc := newWSClient(t) nBlocks := 4 waitNBlocks(t, wsc, nBlocks) @@ -307,10 +301,7 @@ func TestBlockchainInfo(t *testing.T) { } resp, err = edbcli.BlockchainInfo(client, 1, 2) - if err != nil { - t.Fatalf("Failed to get blockchain info: %v", err) - } - + assert.NoError(t, err) assert.Equal(t, 2, len(resp.BlockMetas), "Should see 2 BlockMetas after extracting 2 blocks") }) @@ -320,32 +311,31 @@ func TestListUnconfirmedTxs(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } - wsc := newWSClient(t) + wsc := newWSClient() testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { amt, gasLim, fee := int64(1100), int64(1000), int64(1000) code := []byte{0x60, 0x5, 0x60, 0x1, 0x55} // Call with nil address will create a contract tx := makeDefaultCallTx(t, client, []byte{}, code, amt, gasLim, fee) - txChan := make(chan []txs.Tx) + + // We want to catch the Tx in mempool before it gets reaped by tendermint + // consensus. We should be able to do this almost always if we broadcast our + // transaction immediately after a block has been committed. There is about + // 1 second between blocks, and we will have the lock on Reap + // So we wait for a block here + waitNBlocks(t, wsc, 1) + go func() { for { resp, err := edbcli.ListUnconfirmedTxs(client) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) if resp.N > 0 { txChan <- resp.Txs } } }() - // We want to catch the Tx in mempool before it gets reaped by tendermint - // consensus. We should be able to do this almost always if we broadcast our - // transaction immediately after a block has been committed. There is about - // 1 second between blocks, and we will have the lock on Reap - // So we wait for a block here - waitNBlocks(t, wsc, 1) runThenWaitForBlock(t, wsc, nextBlockPredicateFn(), func() { broadcastTx(t, client, tx) select { @@ -362,6 +352,47 @@ func TestListUnconfirmedTxs(t *testing.T) { }) } +func TestGetBlock(t *testing.T) { + wsc := newWSClient() + testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { + waitNBlocks(t, wsc, 3) + resp, err := edbcli.GetBlock(client, 2) + assert.NoError(t, err) + assert.Equal(t, 2, resp.Block.Height) + assert.Equal(t, 2, resp.BlockMeta.Header.Height) + }) +} + +func TestListValidators(t *testing.T) { + wsc := newWSClient() + testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { + waitNBlocks(t, wsc, 3) + resp, err := edbcli.ListValidators(client) + assert.NoError(t, err) + assert.Len(t, resp.BondedValidators, 1) + validator := resp.BondedValidators[0].(*consensus_types.TendermintValidator) + assert.Equal(t, genesisDoc.Validators[0].PubKey, validator.PubKey) + }) +} + +func TestDumpConsensusState(t *testing.T) { + wsc := newWSClient() + testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { + waitNBlocks(t, wsc, 3) + resp, err := edbcli.DumpConsensusState(client) + assert.NoError(t, err) + startTime := resp.ConsensusState.StartTime + // TODO: uncomment lines involving commitTime when + // https://github.com/tendermint/tendermint/issues/277 is fixed in Tendermint + //commitTime := resp.ConsensusState.CommitTime + assert.NotZero(t, startTime) + //assert.NotZero(t, commitTime) + //assert.True(t, commitTime.Unix() > startTime.Unix(), + // "Commit time %v should be later than start time %v", commitTime, startTime) + assert.Equal(t, uint8(1), resp.ConsensusState.Step) + }) +} + func asEventDataTx(t *testing.T, eventData txs.EventData) txs.EventDataTx { eventDataTx, ok := eventData.(txs.EventDataTx) if !ok { diff --git a/rpc/tendermint/test/shared.go b/rpc/tendermint/test/shared.go index 0b9254f7a1aeb99f2cab905e1be55df91465900e..16dbf7abaf869e0cbc54460657ff7f078b8b6bc0 100644 --- a/rpc/tendermint/test/shared.go +++ b/rpc/tendermint/test/shared.go @@ -21,6 +21,7 @@ import ( "path" + state_types "github.com/eris-ltd/eris-db/manager/eris-mint/state/types" "github.com/spf13/viper" tm_common "github.com/tendermint/go-common" "github.com/tendermint/tendermint/types" @@ -33,14 +34,13 @@ var ( mempoolCount = 0 chainID string websocketAddr string + genesisDoc *state_types.GenesisDoc websocketEndpoint string - - user = makeUsers(5) // make keys - jsonRpcClient rpcclient.Client - httpClient rpcclient.Client - clients map[string]rpcclient.Client - - testCore *core.Core + user = makeUsers(5) // make keys + jsonRpcClient rpcclient.Client + httpClient rpcclient.Client + clients map[string]rpcclient.Client + testCore *core.Core ) // initialize config and create new node @@ -49,6 +49,7 @@ func initGlobalVariables(ffs *fixtures.FileFixtures) error { rootWorkDir = ffs.AddDir("rootWorkDir") rootDataDir := ffs.AddDir("rootDataDir") genesisFile := ffs.AddFile("rootWorkDir/genesis.json", defaultGenesis) + genesisDoc = state_types.GenesisDocFromJSON([]byte(defaultGenesis)) if ffs.Error != nil { return ffs.Error diff --git a/rpc/tendermint/test/websocket_client_test.go b/rpc/tendermint/test/websocket_client_test.go index f10ae1e68687c49da1f712a8a7614c83c85713f4..60e665bdce2a2ecfdf4791deec6c75672446523c 100644 --- a/rpc/tendermint/test/websocket_client_test.go +++ b/rpc/tendermint/test/websocket_client_test.go @@ -20,13 +20,13 @@ import ( // make a simple connection to the server func TestWSConnect(t *testing.T) { - wsc := newWSClient(t) + wsc := newWSClient() wsc.Stop() } // receive a new block message func TestWSNewBlock(t *testing.T) { - wsc := newWSClient(t) + wsc := newWSClient() eid := txs.EventStringNewBlock() subId := subscribeAndGetSubscriptionId(t, wsc, eid) defer func() { @@ -45,7 +45,7 @@ func TestWSBlockchainGrowth(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } - wsc := newWSClient(t) + wsc := newWSClient() eid := txs.EventStringNewBlock() subId := subscribeAndGetSubscriptionId(t, wsc, eid) defer func() { @@ -78,10 +78,9 @@ func TestWSBlockchainGrowth(t *testing.T) { // send a transaction and validate the events from listening for both sender and receiver func TestWSSend(t *testing.T) { + wsc := newWSClient() toAddr := user[1].Address amt := int64(100) - - wsc := newWSClient(t) eidInput := txs.EventStringAccInput(user[0].Address) eidOutput := txs.EventStringAccOutput(toAddr) subIdInput := subscribeAndGetSubscriptionId(t, wsc, eidInput) @@ -105,7 +104,7 @@ func TestWSDoubleFire(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } - wsc := newWSClient(t) + wsc := newWSClient() eid := txs.EventStringAccInput(user[0].Address) subId := subscribeAndGetSubscriptionId(t, wsc, eid) defer func() { @@ -136,7 +135,7 @@ func TestWSCallWait(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } - wsc := newWSClient(t) + wsc := newWSClient() eid1 := txs.EventStringAccInput(user[0].Address) subId1 := subscribeAndGetSubscriptionId(t, wsc, eid1) defer func() { @@ -175,7 +174,7 @@ func TestWSCallNoWait(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } - wsc := newWSClient(t) + wsc := newWSClient() amt, gasLim, fee := int64(10000), int64(1000), int64(1000) code, _, returnVal := simpleContract() @@ -204,7 +203,7 @@ func TestWSCallCall(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } - wsc := newWSClient(t) + wsc := newWSClient() amt, gasLim, fee := int64(10000), int64(1000), int64(1000) code, _, returnVal := simpleContract() txid := new([]byte) @@ -243,8 +242,8 @@ func TestWSCallCall(t *testing.T) { } func TestSubscribe(t *testing.T) { + wsc := newWSClient() var subId string - wsc := newWSClient(t) subscribe(t, wsc, txs.EventStringNewBlock()) timeout := time.NewTimer(timeoutSeconds * time.Second) diff --git a/rpc/tendermint/test/websocket_helpers.go b/rpc/tendermint/test/websocket_helpers.go index b135fa1d6200c8e8d4c58d3d24ebce8f3613402d..b5a7f79f002d54601677142adc8fd82d991df8ba 100644 --- a/rpc/tendermint/test/websocket_helpers.go +++ b/rpc/tendermint/test/websocket_helpers.go @@ -24,10 +24,10 @@ const ( type blockPredicate func(block *tm_types.Block) bool // create a new connection -func newWSClient(t *testing.T) *rpcclient.WSClient { +func newWSClient() *rpcclient.WSClient { wsc := rpcclient.NewWSClient(websocketAddr, websocketEndpoint) if _, err := wsc.Start(); err != nil { - t.Fatal(err) + panic(err) } return wsc } diff --git a/test/testdata/testdata/testdata.go b/test/testdata/testdata/testdata.go index f42cdccdcf595cc1133d647b247ddc0c00c54d25..89b24a470acdaa7f407dc630d97af61ca138502f 100644 --- a/test/testdata/testdata/testdata.go +++ b/test/testdata/testdata/testdata.go @@ -272,8 +272,8 @@ var testDataJson = ` "height": 1, "round": 0, "step": 1, - "start_time": "", - "commit_time": "0001-01-01 00:00:00 +0000 UTC", + "start_time": "2016-09-18T10:03:55.100Z", + "commit_time": "2016-09-18T10:04:00.100Z", "validators": [ [ 1,