diff --git a/consensus/tendermint/tendermint.go b/consensus/tendermint/tendermint.go index a18e35311bc60c7ccc1051d2d30bd3143617dc25..65a58d48603e00ab83bdb63237c64d17c0715e17 100644 --- a/consensus/tendermint/tendermint.go +++ b/consensus/tendermint/tendermint.go @@ -27,6 +27,7 @@ import ( crypto "github.com/tendermint/go-crypto" p2p "github.com/tendermint/go-p2p" + tendermint_consensus "github.com/tendermint/tendermint/consensus" node "github.com/tendermint/tendermint/node" proxy "github.com/tendermint/tendermint/proxy" tendermint_types "github.com/tendermint/tendermint/types" @@ -40,6 +41,8 @@ import ( // files "github.com/eris-ltd/eris-db/files" blockchain_types "github.com/eris-ltd/eris-db/blockchain/types" consensus_types "github.com/eris-ltd/eris-db/consensus/types" + "github.com/eris-ltd/eris-db/txs" + "github.com/tendermint/go-wire" ) type Tendermint struct { @@ -222,6 +225,44 @@ func (tendermint *Tendermint) BroadcastTransaction(transaction []byte, return tendermint.tmintNode.MempoolReactor().BroadcastTx(transaction, callback) } +func (tendermint *Tendermint) ListUnconfirmedTxs( + maxTxs int) ([]txs.Tx, error) { + tendermintTxs := tendermint.tmintNode.MempoolReactor().Mempool.Reap(maxTxs) + transactions := make([]txs.Tx, len(tendermintTxs)) + for i, txBytes := range tendermintTxs { + tx, err := txs.DecodeTx(txBytes) + if err != nil { + return nil, err + } + transactions[i] = tx + } + return transactions, nil +} + +func (tendermint *Tendermint) ListValidators() []consensus_types.Validator { + return consensus_types.FromTendermintValidators(tendermint.tmintNode. + ConsensusState().Validators.Validators) +} + +func (tendermint *Tendermint) ConsensusState() *consensus_types.ConsensusState { + return consensus_types.FromRoundState(tendermint.tmintNode.ConsensusState(). + GetRoundState()) +} + +func (tendermint *Tendermint) PeerConsensusStates() map[string]string { + peers := tendermint.tmintNode.Switch().Peers().List() + peerConsensusStates := make(map[string]string, + len(peers)) + for _, peer := range peers { + peerState := peer.Data.Get(tendermint_types.PeerStateKey).(*tendermint_consensus.PeerState) + peerRoundState := peerState.GetRoundState() + // TODO: implement a proper mapping, this is a nasty way of marshalling + // to JSON + peerConsensusStates[peer.Key] = string(wire.JSONBytes(peerRoundState)) + } + return peerConsensusStates +} + //------------------------------------------------------------------------------ // Helper functions diff --git a/consensus/types/consensus_engine.go b/consensus/types/consensus_engine.go index 893ea85ecdbbd0504862236215bda7dcc5e09d55..8600bb3e827091cb22f99be2956449fad0f76783 100644 --- a/consensus/types/consensus_engine.go +++ b/consensus/types/consensus_engine.go @@ -2,6 +2,7 @@ package types import ( "github.com/eris-ltd/eris-db/event" + "github.com/eris-ltd/eris-db/txs" "github.com/tendermint/go-crypto" "github.com/tendermint/go-p2p" tmsp_types "github.com/tendermint/tmsp/types" @@ -24,4 +25,13 @@ type ConsensusEngine interface { // Events // For consensus events like NewBlock Events() event.EventEmitter + + // List pending transactions in the mempool, passing 0 for maxTxs gets an + // unbounded number of transactions + ListUnconfirmedTxs(maxTxs int) ([]txs.Tx, error) + ListValidators() []Validator + ConsensusState() *ConsensusState + // TODO: Consider creating a real type for PeerRoundState, but at the looks + // quite coupled to tendermint + PeerConsensusStates() map[string]string } diff --git a/consensus/types/consensus_state.go b/consensus/types/consensus_state.go new file mode 100644 index 0000000000000000000000000000000000000000..80f8fc876bbeb522badddaf35317bd926fc81dbc --- /dev/null +++ b/consensus/types/consensus_state.go @@ -0,0 +1,30 @@ +package types + +import ( + tendermint_consensus "github.com/tendermint/tendermint/consensus" + tendermint_types "github.com/tendermint/tendermint/types" +) + +// ConsensusState +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"` + Validators []Validator `json:"validators"` + Proposal *tendermint_types.Proposal `json:"proposal"` +} + +func FromRoundState(rs *tendermint_consensus.RoundState) *ConsensusState { + cs := &ConsensusState{ + CommitTime: rs.CommitTime.String(), + Height: rs.Height, + Proposal: rs.Proposal, + Round: rs.Round, + StartTime: rs.StartTime.String(), + Step: uint8(rs.Step), + Validators: FromTendermintValidators(rs.Validators.Validators), + } + return cs +} diff --git a/consensus/types/validator.go b/consensus/types/validator.go new file mode 100644 index 0000000000000000000000000000000000000000..f5567bbbb0692daea3593d2b6feaf081cbbf793b --- /dev/null +++ b/consensus/types/validator.go @@ -0,0 +1,37 @@ +package types + +import ( + "github.com/tendermint/go-wire" + tendermint_types "github.com/tendermint/tendermint/types" +) + +var _ = wire.RegisterInterface( + struct{ Validator }{}, + wire.ConcreteType{&TendermintValidator{}, byte(0x01)}, +) + +type Validator interface { + AssertIsValidator() +} + +// Anticipating moving to our own definition of Validator, or at least +// augmenting Tendermint's. +type TendermintValidator struct { + *tendermint_types.Validator `json:"validator"` +} + +func (validator *TendermintValidator) AssertIsValidator() { + +} + +var _ Validator = (*TendermintValidator)(nil) + +func FromTendermintValidators(tmValidators []*tendermint_types.Validator) []Validator { + validators := make([]Validator, len(tmValidators)) + for i, tmValidator := range tmValidators { + // This embedding could be replaced by a mapping once if we want to describe + // a more general notion of validator + validators[i] = &TendermintValidator{tmValidator} + } + return validators +} diff --git a/core/types/types.go b/core/types/types.go index 738bfb2fc2cf9f86ee64788023b6248d72cb22ab..274900ee723b91a8d6c06fec7a3a58294c3fc183 100644 --- a/core/types/types.go +++ b/core/types/types.go @@ -21,14 +21,12 @@ package types import ( "github.com/tendermint/go-p2p" // NodeInfo (drop this!) - tendermint_consensus "github.com/tendermint/tendermint/consensus" "github.com/tendermint/tendermint/types" account "github.com/eris-ltd/eris-db/account" ) type ( - // *********************************** Address *********************************** // Accounts @@ -81,17 +79,6 @@ type ( // *********************************** Consensus *********************************** - // ConsensusState - ConsensusState struct { - Height int `json:"height"` - Round int `json:"round"` - Step uint8 `json:"step"` - StartTime string `json:"start_time"` - CommitTime string `json:"commit_time"` - Validators []*types.Validator `json:"validators"` - Proposal *types.Proposal `json:"proposal"` - } - // Validators ValidatorList struct { BlockHeight int `json:"block_height"` @@ -159,19 +146,6 @@ type ( } ) -func FromRoundState(rs *tendermint_consensus.RoundState) *ConsensusState { - cs := &ConsensusState{ - CommitTime: rs.CommitTime.String(), - Height: rs.Height, - Proposal: rs.Proposal, - Round: rs.Round, - StartTime: rs.StartTime.String(), - Step: uint8(rs.Step), - Validators: rs.Validators.Validators, - } - return cs -} - //------------------------------------------------------------------------------ // copied in from NameReg diff --git a/definitions/tendermint_pipe.go b/definitions/tendermint_pipe.go index bffa94ad935ba507e82f97f9c87cde93abbe480b..37973ce4dcaffc918d477a19d809ae1a7d808175 100644 --- a/definitions/tendermint_pipe.go +++ b/definitions/tendermint_pipe.go @@ -69,4 +69,10 @@ type TendermintPipe interface { // Blockchain BlockchainInfo(minHeight, maxHeight, maxBlockLookback int) (*rpc_tm_types.ResultBlockchainInfo, error) + ListUnconfirmedTxs(maxTxs int) (*rpc_tm_types.ResultListUnconfirmedTxs, error) + GetBlock(height int) (*rpc_tm_types.ResultGetBlock, error) + + // Consensus + ListValidators() (*rpc_tm_types.ResultListValidators, error) + DumpConsensusState() (*rpc_tm_types.ResultDumpConsensusState, error) } diff --git a/manager/eris-mint/eris-mint.go b/manager/eris-mint/eris-mint.go index 809540879b1d262b843adadcd544dff5126e609e..55d7374d175569234d9254301204f180e2502229 100644 --- a/manager/eris-mint/eris-mint.go +++ b/manager/eris-mint/eris-mint.go @@ -124,8 +124,7 @@ func (app *ErisMint) SetOption(key string, value string) (log string) { } // Implements manager/types.Application -func (app *ErisMint) AppendTx(txBytes []byte) (res tmsp.Result) { - +func (app *ErisMint) AppendTx(txBytes []byte) tmsp.Result { app.nTxs += 1 // XXX: if we had tx ids we could cache the decoded txs on CheckTx @@ -149,7 +148,7 @@ func (app *ErisMint) AppendTx(txBytes []byte) (res tmsp.Result) { } // Implements manager/types.Application -func (app *ErisMint) CheckTx(txBytes []byte) (res tmsp.Result) { +func (app *ErisMint) CheckTx(txBytes []byte) tmsp.Result { var n int var err error tx := new(txs.Tx) diff --git a/manager/eris-mint/pipe.go b/manager/eris-mint/pipe.go index 6339abc2daab3c055b5c1a226223d61036797957..b05ad45e737fbcafa20648029358d97dbb04509c 100644 --- a/manager/eris-mint/pipe.go +++ b/manager/eris-mint/pipe.go @@ -47,8 +47,8 @@ import ( ) type erisMintPipe struct { - erisMintState *state.State - erisMint *ErisMint + erisMintState *state.State + erisMint *ErisMint // Pipe implementations accounts *accounts blockchain blockchain_types.Blockchain @@ -58,8 +58,8 @@ type erisMintPipe struct { network *network transactor *transactor // Genesis cache - genesisDoc *state_types.GenesisDoc - genesisState *state.State + genesisDoc *state_types.GenesisDoc + genesisState *state.State } // NOTE [ben] Compiler check to ensure erisMintPipe successfully implements @@ -112,11 +112,11 @@ func NewErisMintPipe(moduleConfig *config.ModuleConfig, genesisDoc: genesisDoc, genesisState: nil, // TODO: What network-level information do we need? - network: newNetwork(), + network: newNetwork(), // consensus and blockchain should both be loaded into the pipe by a higher // authority - this is a sort of dependency injection pattern - consensusEngine: nil, - blockchain: nil, + consensusEngine: nil, + blockchain: nil, }, nil } @@ -522,26 +522,34 @@ func (pipe *erisMintPipe) ListNames() (*rpc_tm_types.ResultListNames, error) { return &rpc_tm_types.ResultListNames{blockHeight, names}, nil } -// Memory pool -// NOTE: txs must be signed -func (pipe *erisMintPipe) BroadcastTxAsync(tx txs.Tx) ( - *rpc_tm_types.ResultBroadcastTx, error) { - err := pipe.consensusEngine.BroadcastTransaction(txs.EncodeTx(tx), nil) +func (pipe *erisMintPipe) broadcastTx(tx txs.Tx, + callback func(res *tmsp_types.Response)) (*rpc_tm_types.ResultBroadcastTx, error) { + + txBytes, err := txs.EncodeTx(tx) if err != nil { - return nil, fmt.Errorf("Error broadcasting txs: %v", err) + return nil, fmt.Errorf("Error encoding transaction: %v", err) + } + err = pipe.consensusEngine.BroadcastTransaction(txBytes, callback) + if err != nil { + return nil, fmt.Errorf("Error broadcasting transaction: %v", err) } return &rpc_tm_types.ResultBroadcastTx{}, nil } -func (pipe *erisMintPipe) BroadcastTxSync(tx txs.Tx) (*rpc_tm_types.ResultBroadcastTx, - error) { +// Memory pool +// NOTE: txs must be signed +func (pipe *erisMintPipe) BroadcastTxAsync(tx txs.Tx) (*rpc_tm_types.ResultBroadcastTx, error) { + return pipe.broadcastTx(tx, nil) +} + +func (pipe *erisMintPipe) BroadcastTxSync(tx txs.Tx) (*rpc_tm_types.ResultBroadcastTx, error) { responseChannel := make(chan *tmsp_types.Response, 1) - err := pipe.consensusEngine.BroadcastTransaction(txs.EncodeTx(tx), + _, err := pipe.broadcastTx(tx, func(res *tmsp_types.Response) { responseChannel <- res }) if err != nil { - return nil, fmt.Errorf("Error broadcasting txs: %v", err) + return nil, err } // NOTE: [ben] This Response is set in /consensus/tendermint/local_client.go // a call to Application, here implemented by ErisMint, over local callback, @@ -574,6 +582,18 @@ func (pipe *erisMintPipe) BroadcastTxSync(tx txs.Tx) (*rpc_tm_types.ResultBroadc } } +func (pipe *erisMintPipe) ListUnconfirmedTxs(maxTxs int) (*rpc_tm_types.ResultListUnconfirmedTxs, error) { + // Get all transactions for now + transactions, err := pipe.consensusEngine.ListUnconfirmedTxs(maxTxs) + if err != nil { + return nil, err + } + return &rpc_tm_types.ResultListUnconfirmedTxs{ + N: len(transactions), + Txs: transactions, + }, nil +} + // Returns the current blockchain height and metadata for a range of blocks // between minHeight and maxHeight. Only returns maxBlockLookback block metadata // from the top of the range of blocks. @@ -599,5 +619,34 @@ func (pipe *erisMintPipe) BlockchainInfo(minHeight, maxHeight, blockMetas = append(blockMetas, blockMeta) } - return &rpc_tm_types.ResultBlockchainInfo{latestHeight, blockMetas}, nil + return &rpc_tm_types.ResultBlockchainInfo{ + LastHeight: latestHeight, + BlockMetas: blockMetas, + }, nil +} + +func (pipe *erisMintPipe) GetBlock(height int) (*rpc_tm_types.ResultGetBlock, error) { + return &rpc_tm_types.ResultGetBlock{ + Block: pipe.blockchain.Block(height), + BlockMeta: pipe.blockchain.BlockMeta(height), + }, nil +} + +func (pipe *erisMintPipe) ListValidators() (*rpc_tm_types.ResultListValidators, error) { + validators := pipe.consensusEngine.ListValidators() + consensusState := pipe.consensusEngine.ConsensusState() + // TODO: when we reintroduce support for bonding and unbonding update this + // to reflect the mutable bonding state + return &rpc_tm_types.ResultListValidators{ + BlockHeight: consensusState.Height, + BondedValidators: validators, + UnbondingValidators: nil, + }, nil +} + +func (pipe *erisMintPipe) DumpConsensusState() (*rpc_tm_types.ResultDumpConsensusState, error) { + return &rpc_tm_types.ResultDumpConsensusState{ + ConsensusState: pipe.consensusEngine.ConsensusState(), + PeerConsensusStates: pipe.consensusEngine.PeerConsensusStates(), + }, nil } diff --git a/rpc/tendermint/client/client.go b/rpc/tendermint/client/client.go index 0758eb20f487ea230557acf905fe6a55738b6a3b..6be71caa662d34786d9e0ec7841e6652ca7f8725 100644 --- a/rpc/tendermint/client/client.go +++ b/rpc/tendermint/client/client.go @@ -137,6 +137,9 @@ func performCall(client rpcclient.Client, method string, _, err = cli.Call(method, paramsSlice, &res) case *rpcclient.ClientURI: _, err = cli.Call(method, paramsMap, &res) + default: + panic(fmt.Errorf("peformCall called against an unknown rpcclient.Client %v", + cli)) } return diff --git a/rpc/tendermint/core/routes.go b/rpc/tendermint/core/routes.go index 714c1066d290c7f5cddf148e1064ae9eaf488cd0..8696ba8388d3d336fe5f71aaa2831d19f8d7eef2 100644 --- a/rpc/tendermint/core/routes.go +++ b/rpc/tendermint/core/routes.go @@ -38,15 +38,13 @@ func (tmRoutes *TendermintRoutes) GetRoutes() map[string]*rpc.RPCFunc { "get_name": rpc.NewRPCFunc(tmRoutes.GetNameResult, "name"), "list_names": rpc.NewRPCFunc(tmRoutes.ListNamesResult, ""), "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, ""), + "dump_consensus_state": rpc.NewRPCFunc(tmRoutes.DumpConsensusState, ""), "unsafe/gen_priv_account": rpc.NewRPCFunc(tmRoutes.GenPrivAccountResult, ""), "unsafe/sign_tx": rpc.NewRPCFunc(tmRoutes.SignTxResult, "tx,privAccounts"), - - // TODO: hookup - "blockchain": rpc.NewRPCFunc(tmRoutes.BlockchainInfo, "minHeight,maxHeight"), - // "get_block": rpc.NewRPCFunc(GetBlock, "height"), - // "list_validators": rpc.NewRPCFunc(ListValidators, ""), - // "dump_consensus_state": rpc.NewRPCFunc(DumpConsensusState, ""), - // "list_unconfirmed_txs": rpc.NewRPCFunc(ListUnconfirmedTxs, ""), // TODO: [Silas] do we also carry forward "consensus_state" as in v0? } return routes @@ -211,5 +209,38 @@ func (tmRoutes *TendermintRoutes) BlockchainInfo(minHeight, } else { return r, nil } +} +func (tmRoutes *TendermintRoutes) ListUnconfirmedTxs() (ctypes.ErisDBResult, error) { + // Get all Txs for now + r, err := tmRoutes.tendermintPipe.ListUnconfirmedTxs(0) + if err != nil { + return nil, err + } else { + return r, nil + } +} +func (tmRoutes *TendermintRoutes) GetBlock(height int) (ctypes.ErisDBResult, error) { + r, err := tmRoutes.tendermintPipe.GetBlock(height) + if err != nil { + return nil, err + } else { + return r, nil + } +} +func (tmRoutes *TendermintRoutes) ListValidators() (ctypes.ErisDBResult, error) { + r, err := tmRoutes.tendermintPipe.ListValidators() + if err != nil { + return nil, err + } else { + return r, nil + } +} +func (tmRoutes *TendermintRoutes) DumpConsensusState() (ctypes.ErisDBResult, error) { + r, err := tmRoutes.tendermintPipe.DumpConsensusState() + if err != nil { + return nil, err + } else { + return r, nil + } } diff --git a/rpc/tendermint/core/types/responses.go b/rpc/tendermint/core/types/responses.go index cacb5f5ff0beb38fc2fc7ec60807a8d9cb079571..41aee0262d21f10f29922b7d1f02ae81f95f0118 100644 --- a/rpc/tendermint/core/types/responses.go +++ b/rpc/tendermint/core/types/responses.go @@ -5,7 +5,7 @@ import ( core_types "github.com/eris-ltd/eris-db/core/types" stypes "github.com/eris-ltd/eris-db/manager/eris-mint/state/types" "github.com/eris-ltd/eris-db/txs" - "github.com/tendermint/tendermint/types" + tendermint_types "github.com/tendermint/tendermint/types" consensus_types "github.com/eris-ltd/eris-db/consensus/types" "github.com/tendermint/go-crypto" @@ -42,13 +42,13 @@ type StorageItem struct { } type ResultBlockchainInfo struct { - LastHeight int `json:"last_height"` - BlockMetas []*types.BlockMeta `json:"block_metas"` + LastHeight int `json:"last_height"` + BlockMetas []*tendermint_types.BlockMeta `json:"block_metas"` } type ResultGetBlock struct { - BlockMeta *types.BlockMeta `json:"block_meta"` - Block *types.Block `json:"block"` + BlockMeta *tendermint_types.BlockMeta `json:"block_meta"` + Block *tendermint_types.Block `json:"block"` } type ResultStatus struct { @@ -76,14 +76,14 @@ type ResultNetInfo struct { } type ResultListValidators struct { - BlockHeight int `json:"block_height"` - BondedValidators []*types.Validator `json:"bonded_validators"` - UnbondingValidators []*types.Validator `json:"unbonding_validators"` + BlockHeight int `json:"block_height"` + BondedValidators []consensus_types.Validator `json:"bonded_validators"` + UnbondingValidators []consensus_types.Validator `json:"unbonding_validators"` } type ResultDumpConsensusState struct { - RoundState string `json:"round_state"` - PeerRoundStates []string `json:"peer_round_states"` + ConsensusState *consensus_types.ConsensusState `json:"consensus_state"` + PeerConsensusStates map[string]string `json:"peer_consensus_states"` } type ResultListNames struct { @@ -158,28 +158,30 @@ type ErisDBResult interface { rpctypes.Result } -// for wire.readReflect -var _ = wire.RegisterInterface( - struct{ ErisDBResult }{}, - wire.ConcreteType{&ResultGetStorage{}, ResultTypeGetStorage}, - wire.ConcreteType{&ResultCall{}, ResultTypeCall}, - wire.ConcreteType{&ResultListAccounts{}, ResultTypeListAccounts}, - wire.ConcreteType{&ResultDumpStorage{}, ResultTypeDumpStorage}, - wire.ConcreteType{&ResultBlockchainInfo{}, ResultTypeBlockchainInfo}, - wire.ConcreteType{&ResultGetBlock{}, ResultTypeGetBlock}, - wire.ConcreteType{&ResultStatus{}, ResultTypeStatus}, - wire.ConcreteType{&ResultNetInfo{}, ResultTypeNetInfo}, - wire.ConcreteType{&ResultListValidators{}, ResultTypeListValidators}, - wire.ConcreteType{&ResultDumpConsensusState{}, ResultTypeDumpConsensusState}, - wire.ConcreteType{&ResultListNames{}, ResultTypeListNames}, - wire.ConcreteType{&ResultGenPrivAccount{}, ResultTypeGenPrivAccount}, - wire.ConcreteType{&ResultGetAccount{}, ResultTypeGetAccount}, - wire.ConcreteType{&ResultBroadcastTx{}, ResultTypeBroadcastTx}, - wire.ConcreteType{&ResultListUnconfirmedTxs{}, ResultTypeListUnconfirmedTxs}, - wire.ConcreteType{&ResultGetName{}, ResultTypeGetName}, - wire.ConcreteType{&ResultGenesis{}, ResultTypeGenesis}, - wire.ConcreteType{&ResultSignTx{}, ResultTypeSignTx}, - wire.ConcreteType{&ResultEvent{}, ResultTypeEvent}, - wire.ConcreteType{&ResultSubscribe{}, ResultTypeSubscribe}, - wire.ConcreteType{&ResultUnsubscribe{}, ResultTypeUnsubscribe}, -) +func ConcreteTypes() []wire.ConcreteType { + return []wire.ConcreteType{ + {&ResultGetStorage{}, ResultTypeGetStorage}, + {&ResultCall{}, ResultTypeCall}, + {&ResultListAccounts{}, ResultTypeListAccounts}, + {&ResultDumpStorage{}, ResultTypeDumpStorage}, + {&ResultBlockchainInfo{}, ResultTypeBlockchainInfo}, + {&ResultGetBlock{}, ResultTypeGetBlock}, + {&ResultStatus{}, ResultTypeStatus}, + {&ResultNetInfo{}, ResultTypeNetInfo}, + {&ResultListValidators{}, ResultTypeListValidators}, + {&ResultDumpConsensusState{}, ResultTypeDumpConsensusState}, + {&ResultListNames{}, ResultTypeListNames}, + {&ResultGenPrivAccount{}, ResultTypeGenPrivAccount}, + {&ResultGetAccount{}, ResultTypeGetAccount}, + {&ResultBroadcastTx{}, ResultTypeBroadcastTx}, + {&ResultListUnconfirmedTxs{}, ResultTypeListUnconfirmedTxs}, + {&ResultGetName{}, ResultTypeGetName}, + {&ResultGenesis{}, ResultTypeGenesis}, + {&ResultSignTx{}, ResultTypeSignTx}, + {&ResultEvent{}, ResultTypeEvent}, + {&ResultSubscribe{}, ResultTypeSubscribe}, + {&ResultUnsubscribe{}, ResultTypeUnsubscribe}, + } +} + +var _ = wire.RegisterInterface(struct{ ErisDBResult }{}, ConcreteTypes()...) diff --git a/rpc/tendermint/test/client_rpc_test.go b/rpc/tendermint/test/client_rpc_test.go deleted file mode 100644 index 1a0a725df29d089b1180435a74e9df380f243a96..0000000000000000000000000000000000000000 --- a/rpc/tendermint/test/client_rpc_test.go +++ /dev/null @@ -1,118 +0,0 @@ -// +build integration - -// Space above here matters -package test - -import ( - "testing" - - // ctypes "github.com/eris-ltd/eris-db/rpc/core/types" - _ "github.com/tendermint/tendermint/config/tendermint_test" -) - -// When run with `-test.short` we only run: -// TestHTTPStatus, TestHTTPBroadcast, TestJSONStatus, TestJSONBroadcast, TestWSConnect, TestWSSend - -//-------------------------------------------------------------------------------- -func TestHTTPStatus(t *testing.T) { - testStatus(t, "HTTP") -} - -func TestHTTPGetAccount(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - testGetAccount(t, "HTTP") -} - -func TestHTTPBroadcastTx(t *testing.T) { - testBroadcastTx(t, "HTTP") -} - -func TestHTTPGetStorage(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - testGetStorage(t, "HTTP") -} - -func TestHTTPCallCode(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - testCallCode(t, "HTTP") -} - -func TestHTTPCallContract(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - testCall(t, "HTTP") -} - -func TestHTTPNameReg(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - testNameReg(t, "HTTP") -} - -func TestHTTPBlockchainInfo(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - testBlockchainInfo(t, "HTTP") -} - -//-------------------------------------------------------------------------------- -// Test the JSONRPC client - -func TestJSONStatus(t *testing.T) { - testStatus(t, "JSONRPC") -} - -func TestJSONGetAccount(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - testGetAccount(t, "JSONRPC") -} - -func TestJSONBroadcastTx(t *testing.T) { - testBroadcastTx(t, "JSONRPC") -} - -func TestJSONGetStorage(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - testGetStorage(t, "JSONRPC") -} - -func TestJSONCallCode(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - testCallCode(t, "JSONRPC") -} - -func TestJSONCallContract(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - testCall(t, "JSONRPC") -} - -func TestJSONNameReg(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - testNameReg(t, "JSONRPC") -} - -func TestJSONBlockchainInfo(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - testBlockchainInfo(t, "JSONRPC") -} diff --git a/rpc/tendermint/test/common.go b/rpc/tendermint/test/common.go index 211d3549991fccb6d68a0754f254609311a0591a..76909182df98801eab3e08db5b5c4bbfd03617cf 100644 --- a/rpc/tendermint/test/common.go +++ b/rpc/tendermint/test/common.go @@ -4,10 +4,10 @@ package test import ( - "github.com/eris-ltd/eris-db/test/fixtures" - rpc_core "github.com/eris-ltd/eris-db/rpc/tendermint/core" - "testing" "fmt" + + rpc_core "github.com/eris-ltd/eris-db/rpc/tendermint/core" + "github.com/eris-ltd/eris-db/test/fixtures" ) // Needs to be referenced by a *_test.go file to be picked up @@ -26,7 +26,7 @@ func TestWrapper(runner func() int) int { // start a node ready := make(chan error) server := make(chan *rpc_core.TendermintWebsocketServer) - defer func(){ + defer func() { // Shutdown -- make sure we don't hit a race on ffs.RemoveAll tmServer := <-server tmServer.Shutdown() @@ -48,9 +48,9 @@ func TestWrapper(runner func() int) int { // inconsequential, so feel free to insert your own code if you want to use it // as an application entry point for delve debugging. func DebugMain() { - t := &testing.T{} + //t := &testing.T{} TestWrapper(func() int { - testNameReg(t, "JSONRPC") + //testNameReg(t, "JSONRPC") return 0 }) } diff --git a/rpc/tendermint/test/rpc_client_test.go b/rpc/tendermint/test/rpc_client_test.go new file mode 100644 index 0000000000000000000000000000000000000000..6c3b5887cd5e535bdd70520c3ecd637f6784f5f6 --- /dev/null +++ b/rpc/tendermint/test/rpc_client_test.go @@ -0,0 +1,329 @@ +// +build integration + +// Space above here matters +package test + +import ( + "bytes" + "fmt" + "testing" + + "golang.org/x/crypto/ripemd160" + + 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" + rpcclient "github.com/tendermint/go-rpc/client" + _ "github.com/tendermint/tendermint/config/tendermint_test" +) + +// When run with `-test.short` we only run: +// TestHTTPStatus, TestHTTPBroadcast, TestJSONStatus, TestJSONBroadcast, TestWSConnect, TestWSSend + +// Note: the reason that we have tests implemented in tests.go is I believe +// due to weirdness with go-wire's interface registration, and those global +// registrations not being available within a *_test.go runtime context. +func testWithAllClients(t *testing.T, + testFunction func(*testing.T, string, rpcclient.Client)) { + for clientName, client := range clients { + fmt.Printf("\x1b[47m\x1b[31mMARMOT DEBUG: Loop \x1b[0m\n") + testFunction(t, clientName, client) + } +} + +//-------------------------------------------------------------------------------- +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) + } + fmt.Println(resp) + if resp.NodeInfo.Network != chainID { + t.Fatal(fmt.Errorf("ChainID mismatch: got %s expected %s", + resp.NodeInfo.Network, chainID)) + } + }) +} + +func TestBroadcastTx(t *testing.T) { + wsc := newWSClient(t) + testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { + // Avoid duplicate Tx in mempool + amt := hashString(clientName) % 1000 + toAddr := user[1].Address + tx := makeDefaultSendTxSigned(t, client, toAddr, amt) + //receipt := broadcastTx(t, client, tx) + receipt, err := broadcastTxAndWaitForBlock(t, client,wsc, tx) + if err != nil { + t.Fatal(err) + } + if receipt.CreatesContract > 0 { + t.Fatal("This tx does not create a contract") + } + if len(receipt.TxHash) == 0 { + t.Fatal("Failed to compute tx hash") + } + n, errp := new(int), new(error) + buf := new(bytes.Buffer) + hasher := ripemd160.New() + tx.WriteSignBytes(chainID, buf, n, errp) + if *errp != nil { + t.Fatal(*errp) + } + txSignBytes := buf.Bytes() + hasher.Write(txSignBytes) + txHashExpected := hasher.Sum(nil) + if bytes.Compare(receipt.TxHash, txHashExpected) != 0 { + t.Fatalf("The receipt hash '%x' does not equal the ripemd160 hash of the "+ + "transaction signed bytes calculated in the test: '%x'", + receipt.TxHash, txHashExpected) + } + }) +} + +func TestGetAccount(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { + acc := getAccount(t, client, user[0].Address) + if acc == nil { + t.Fatal("Account was nil") + } + if bytes.Compare(acc.Address, user[0].Address) != 0 { + t.Fatalf("Failed to get correct account. Got %x, expected %x", acc.Address, user[0].Address) + } + }) +} + +func TestGetStorage(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { + wsc := newWSClient(t) + eid := txs.EventStringNewBlock() + subscribe(t, wsc, eid) + defer func() { + unsubscribe(t, wsc, eid) + wsc.Stop() + }() + + 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, nil, code, amt, gasLim, fee) + receipt, err := broadcastTxAndWaitForBlock(t, client, wsc, tx) + if err != nil { + t.Fatalf("Problem broadcasting transaction: %v", 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"+ + " transaction hash") + contractAddr := receipt.ContractAddr + assert.NotEqual(t, 0, len(contractAddr), "Transactions claims to have"+ + " created a contract but the contract address is empty") + + v := getStorage(t, client, contractAddr, []byte{0x1}) + got := tm_common.LeftPadWord256(v) + expected := tm_common.LeftPadWord256([]byte{0x5}) + if got.Compare(expected) != 0 { + t.Fatalf("Wrong storage value. Got %x, expected %x", got.Bytes(), + expected.Bytes()) + } + }) +} + +func TestCallCode(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + + testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { + // add two integers and return the result + code := []byte{0x60, 0x5, 0x60, 0x6, 0x1, 0x60, 0x0, 0x52, 0x60, 0x20, 0x60, + 0x0, 0xf3} + data := []byte{} + expected := []byte{0xb} + callCode(t, client, user[0].PubKey.Address(), code, data, expected) + + // pass two ints as calldata, add, and return the result + code = []byte{0x60, 0x0, 0x35, 0x60, 0x20, 0x35, 0x1, 0x60, 0x0, 0x52, 0x60, + 0x20, 0x60, 0x0, 0xf3} + data = append(tm_common.LeftPadWord256([]byte{0x5}).Bytes(), + tm_common.LeftPadWord256([]byte{0x6}).Bytes()...) + expected = []byte{0xb} + callCode(t, client, user[0].PubKey.Address(), code, data, expected) + }) +} + +func TestCallContract(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { + wsc := newWSClient(t) + eid := txs.EventStringNewBlock() + subscribe(t, wsc, eid) + defer func() { + unsubscribe(t, wsc, eid) + wsc.Stop() + }() + + // create the contract + amt, gasLim, fee := int64(6969), int64(1000), int64(1000) + code, _, _ := simpleContract() + 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.Equal(t, uint8(1), receipt.CreatesContract, "This transaction should"+ + " create a contract") + assert.NotEqual(t, 0, len(receipt.TxHash), "Receipt should contain a"+ + " transaction hash") + contractAddr := receipt.ContractAddr + assert.NotEqual(t, 0, len(contractAddr), "Transactions claims to have"+ + " created a contract but the contract address is empty") + + // run a call through the contract + data := []byte{} + expected := []byte{0xb} + callContract(t, client, user[0].PubKey.Address(), contractAddr, data, expected) + }) +} + +func TestNameReg(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { + wsc := newWSClient(t) + + txs.MinNameRegistrationPeriod = 1 + + // register a new name, check if its there + // since entries ought to be unique and these run against different clients, we append the client + name := "ye_old_domain_name_" + clientName + const data = "if not now, when" + fee := int64(1000) + numDesiredBlocks := int64(2) + amt := fee + numDesiredBlocks*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) + + tx := makeDefaultNameTx(t, client, name, data, amt, fee) + // verify the name by both using the event and by checking get_name + subscribeAndWaitForNext(t, wsc, txs.EventStringNameReg(name), + func() { + broadcastTxAndWaitForBlock(t, client, 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, client, 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(5) + 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, client, name, updatedData, amt, fee) + broadcastTxAndWaitForBlock(t, client, wsc, tx) + mempoolCount = 0 + entry = getNameRegEntry(t, client, name) + + assert.Equal(t, updatedData, entry.Data) + + // try to update as non owner, should fail + tx = txs.NewNameTxWithNonce(user[1].PubKey, name, "never mind", amt, fee, + getNonce(t, client, user[1].Address)+1) + tx.Sign(chainID, user[1]) + + _, err := broadcastTxAndWaitForBlock(t, client, wsc, tx) + assert.Error(t, err, "Expected error when updating someone else's unexpired"+ + " name registry entry") + if err != nil { + assert.Contains(t, err.Error(), "permission denied", "Error should be "+ + "permission denied") + } + + // Wait a couple of blocks to make sure name registration expires + waitNBlocks(t, wsc, 3) + + //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, client, user[1].Address)+1) + tx.Sign(chainID, user[1]) + _, err = broadcastTxAndWaitForBlock(t, client, 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, client, name) + assert.Equal(t, data2, entry.Data) + assert.Equal(t, user[1].Address, entry.Owner) + }) +} + +func TestBlockchainInfo(t *testing.T) { + testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { + wsc := newWSClient(t) + nBlocks := 4 + waitNBlocks(t, wsc, nBlocks) + + resp, err := edbcli.BlockchainInfo(client, 0, 0) + if err != nil { + t.Fatalf("Failed to get blockchain info: %v", err) + } + //TODO: [Silas] reintroduce this when Tendermint changes logic to fire + // NewBlock after saving a block + // see https://github.com/tendermint/tendermint/issues/273 + //assert.Equal(t, 4, resp.LastHeight, "Last height should be 4 after waiting for first 4 blocks") + assert.True(t, nBlocks <= len(resp.BlockMetas), + "Should see at least 4 BlockMetas after waiting for first 4 blocks") + + lastBlockHash := resp.BlockMetas[nBlocks-1].Hash + for i := nBlocks - 2; i >= 0; i-- { + assert.Equal(t, lastBlockHash, resp.BlockMetas[i].Header.LastBlockHash, + "Blockchain should be a hash tree!") + lastBlockHash = resp.BlockMetas[i].Hash + } + + resp, err = edbcli.BlockchainInfo(client, 1, 2) + if err != nil { + t.Fatalf("Failed to get blockchain info: %v", err) + } + + assert.Equal(t, 2, len(resp.BlockMetas), + "Should see 2 BlockMetas after extracting 2 blocks") + }) +} + +func TestListUnconfirmedTxs(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } +} + +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 +} diff --git a/rpc/tendermint/test/shared.go b/rpc/tendermint/test/shared.go index d705d27857ecc5db7223f5ecddf5cb3d31a91c5b..0b9254f7a1aeb99f2cab905e1be55df91465900e 100644 --- a/rpc/tendermint/test/shared.go +++ b/rpc/tendermint/test/shared.go @@ -2,6 +2,7 @@ package test import ( "bytes" + "hash/fnv" "strconv" "testing" @@ -34,8 +35,10 @@ var ( websocketAddr string websocketEndpoint string - user = makeUsers(5) // make keys - clients map[string]rpcclient.Client + user = makeUsers(5) // make keys + jsonRpcClient rpcclient.Client + httpClient rpcclient.Client + clients map[string]rpcclient.Client testCore *core.Core ) @@ -85,9 +88,12 @@ func initGlobalVariables(ffs *fixtures.FileFixtures) error { return err } + jsonRpcClient = rpcclient.NewClientJSONRPC(rpcAddr) + httpClient = rpcclient.NewClientURI(rpcAddr) + clients = map[string]rpcclient.Client{ - "JSONRPC": rpcclient.NewClientURI(rpcAddr), - "HTTP": rpcclient.NewClientJSONRPC(rpcAddr), + "JSONRPC": jsonRpcClient, + "HTTP": httpClient, } return nil } @@ -125,34 +131,34 @@ func saveNewPriv() { //------------------------------------------------------------------------------- // some default transaction functions -func makeDefaultSendTx(t *testing.T, typ string, addr []byte, +func makeDefaultSendTx(t *testing.T, client rpcclient.Client, addr []byte, amt int64) *txs.SendTx { - nonce := getNonce(t, typ, user[0].Address) + nonce := getNonce(t, client, user[0].Address) tx := txs.NewSendTx() tx.AddInputWithNonce(user[0].PubKey, amt, nonce+1) tx.AddOutput(addr, amt) return tx } -func makeDefaultSendTxSigned(t *testing.T, typ string, addr []byte, +func makeDefaultSendTxSigned(t *testing.T, client rpcclient.Client, addr []byte, amt int64) *txs.SendTx { - tx := makeDefaultSendTx(t, typ, addr, amt) + tx := makeDefaultSendTx(t, client, addr, amt) tx.SignInput(chainID, 0, user[0]) return tx } -func makeDefaultCallTx(t *testing.T, typ string, addr, code []byte, amt, gasLim, +func makeDefaultCallTx(t *testing.T, client rpcclient.Client, addr, code []byte, amt, gasLim, fee int64) *txs.CallTx { - nonce := getNonce(t, typ, user[0].Address) + nonce := getNonce(t, client, user[0].Address) tx := txs.NewCallTxWithNonce(user[0].PubKey, addr, code, amt, gasLim, fee, nonce+1) tx.Sign(chainID, user[0]) return tx } -func makeDefaultNameTx(t *testing.T, typ string, name, value string, amt, +func makeDefaultNameTx(t *testing.T, client rpcclient.Client, name, value string, amt, fee int64) *txs.NameTx { - nonce := getNonce(t, typ, user[0].Address) + nonce := getNonce(t, client, user[0].Address) tx := txs.NewNameTxWithNonce(user[0].PubKey, name, value, amt, fee, nonce+1) tx.Sign(chainID, user[0]) return tx @@ -162,8 +168,7 @@ func makeDefaultNameTx(t *testing.T, typ string, name, value string, amt, // rpc call wrappers (fail on err) // get an account's nonce -func getNonce(t *testing.T, typ string, addr []byte) int { - client := clients[typ] +func getNonce(t *testing.T, client rpcclient.Client, addr []byte) int { ac, err := edbcli.GetAccount(client, addr) if err != nil { t.Fatal(err) @@ -175,8 +180,7 @@ func getNonce(t *testing.T, typ string, addr []byte) int { } // get the account -func getAccount(t *testing.T, typ string, addr []byte) *acm.Account { - client := clients[typ] +func getAccount(t *testing.T, client rpcclient.Client, addr []byte) *acm.Account { ac, err := edbcli.GetAccount(client, addr) if err != nil { t.Fatal(err) @@ -185,9 +189,8 @@ func getAccount(t *testing.T, typ string, addr []byte) *acm.Account { } // sign transaction -func signTx(t *testing.T, typ string, tx txs.Tx, +func signTx(t *testing.T, client rpcclient.Client, tx txs.Tx, privAcc *acm.PrivAccount) txs.Tx { - client := clients[typ] signedTx, err := edbcli.SignTx(client, tx, []*acm.PrivAccount{privAcc}) if err != nil { t.Fatal(err) @@ -196,8 +199,7 @@ func signTx(t *testing.T, typ string, tx txs.Tx, } // broadcast transaction -func broadcastTx(t *testing.T, typ string, tx txs.Tx) txs.Receipt { - client := clients[typ] +func broadcastTx(t *testing.T, client rpcclient.Client, tx txs.Tx) txs.Receipt { rec, err := edbcli.BroadcastTx(client, tx) if err != nil { t.Fatal(err) @@ -216,8 +218,7 @@ func dumpStorage(t *testing.T, addr []byte) *rpc_types.ResultDumpStorage { return resp } -func getStorage(t *testing.T, typ string, addr, key []byte) []byte { - client := clients[typ] +func getStorage(t *testing.T, client rpcclient.Client, addr, key []byte) []byte { resp, err := edbcli.GetStorage(client, addr, key) if err != nil { t.Fatal(err) @@ -252,8 +253,7 @@ func callContract(t *testing.T, client rpcclient.Client, fromAddress, toAddress, } // get the namereg entry -func getNameRegEntry(t *testing.T, typ string, name string) *core_types.NameRegEntry { - client := clients[typ] +func getNameRegEntry(t *testing.T, client rpcclient.Client, name string) *core_types.NameRegEntry { entry, err := edbcli.GetName(client, name) if err != nil { t.Fatal(err) @@ -261,29 +261,21 @@ func getNameRegEntry(t *testing.T, typ string, name string) *core_types.NameRegE return entry } -//-------------------------------------------------------------------------------- -// utility verification function - -func checkTx(t *testing.T, fromAddr []byte, priv *acm.PrivAccount, - tx *txs.SendTx) { - if bytes.Compare(tx.Inputs[0].Address, fromAddr) != 0 { - t.Fatal("Tx input addresses don't match!") - } - - signBytes := acm.SignBytes(chainID, tx) - in := tx.Inputs[0] //(*types.SendTx).Inputs[0] - - if err := in.ValidateBasic(); err != nil { - t.Fatal(err) - } - // Check signatures - // acc := getAccount(t, byteAddr) - // NOTE: using the acc here instead of the in fails; it is nil. - if !in.PubKey.VerifyBytes(signBytes, in.Signature) { - t.Fatal(txs.ErrTxInvalidSignature) +// Returns a positive int64 hash of text (consumers want int64 instead of uint64) +func hashString(text string) int64 { + hasher := fnv.New64() + hasher.Write([]byte(text)) + value := int64(hasher.Sum64()) + // Flip the sign if we wrapped + if value < 0 { + return -value } + return value } +//-------------------------------------------------------------------------------- +// utility verification function + // simple contract returns 5 + 6 = 0xb func simpleContract() ([]byte, []byte, []byte) { // this is the code we want to run when the contract is called diff --git a/rpc/tendermint/test/tests.go b/rpc/tendermint/test/tests.go deleted file mode 100644 index 43769b4b0f6f972c151b41c35ed2269ab9ad7a40..0000000000000000000000000000000000000000 --- a/rpc/tendermint/test/tests.go +++ /dev/null @@ -1,342 +0,0 @@ -package test - -import ( - "bytes" - "fmt" - "testing" - - edbcli "github.com/eris-ltd/eris-db/rpc/tendermint/client" - core_types "github.com/eris-ltd/eris-db/rpc/tendermint/core/types" - "github.com/eris-ltd/eris-db/txs" - "github.com/stretchr/testify/assert" - - "time" - - tm_common "github.com/tendermint/go-common" - "golang.org/x/crypto/ripemd160" -) - -func testStatus(t *testing.T, typ string) { - client := clients[typ] - resp, err := edbcli.Status(client) - if err != nil { - t.Fatal(err) - } - fmt.Println(resp) - if resp.NodeInfo.Network != chainID { - t.Fatal(fmt.Errorf("ChainID mismatch: got %s expected %s", - resp.NodeInfo.Network, chainID)) - } -} - -func testGetAccount(t *testing.T, typ string) { - acc := getAccount(t, typ, user[0].Address) - if acc == nil { - t.Fatal("Account was nil") - } - if bytes.Compare(acc.Address, user[0].Address) != 0 { - t.Fatalf("Failed to get correct account. Got %x, expected %x", acc.Address, user[0].Address) - } -} - -func testOneSignTx(t *testing.T, typ string, addr []byte, amt int64) { - tx := makeDefaultSendTx(t, typ, addr, amt) - tx2 := signTx(t, typ, tx, user[0]) - tx2hash := txs.TxHash(chainID, tx2) - tx.SignInput(chainID, 0, user[0]) - txhash := txs.TxHash(chainID, tx) - if bytes.Compare(txhash, tx2hash) != 0 { - t.Fatal("Got different signatures for signing via rpc vs tx_utils") - } - - tx_ := signTx(t, typ, tx, user[0]) - tx = tx_.(*txs.SendTx) - checkTx(t, user[0].Address, user[0], tx) -} - -func testBroadcastTx(t *testing.T, typ string) { - amt := int64(100) - toAddr := user[1].Address - tx := makeDefaultSendTxSigned(t, typ, toAddr, amt) - receipt := broadcastTx(t, typ, tx) - if receipt.CreatesContract > 0 { - t.Fatal("This tx does not create a contract") - } - if len(receipt.TxHash) == 0 { - t.Fatal("Failed to compute tx hash") - } - n, err := new(int), new(error) - buf := new(bytes.Buffer) - hasher := ripemd160.New() - tx.WriteSignBytes(chainID, buf, n, err) - // [Silas] Currently tx.TxHash uses go-wire, we we drop that we can drop the prefix here - goWireBytes := append([]byte{0x01, 0xcf}, buf.Bytes()...) - hasher.Write(goWireBytes) - txHashExpected := hasher.Sum(nil) - if bytes.Compare(receipt.TxHash, txHashExpected) != 0 { - t.Fatalf("The receipt hash '%x' does not equal the ripemd160 hash of the "+ - "transaction signed bytes calculated in the test: '%x'", - receipt.TxHash, txHashExpected) - } -} - -func testGetStorage(t *testing.T, typ string) { - wsc := newWSClient(t) - eid := txs.EventStringNewBlock() - subscribe(t, wsc, eid) - defer func() { - unsubscribe(t, wsc, eid) - wsc.Stop() - }() - - 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, typ, nil, code, amt, gasLim, fee) - receipt, err := broadcastTxAndWaitForBlock(t, typ, wsc, tx) - if err != nil { - t.Fatalf("Problem broadcasting transaction: %v", 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"+ - " transaction hash") - contractAddr := receipt.ContractAddr - assert.NotEqual(t, 0, len(contractAddr), "Transactions claims to have"+ - " created a contract but the contract address is empty") - - v := getStorage(t, typ, contractAddr, []byte{0x1}) - got := tm_common.LeftPadWord256(v) - expected := tm_common.LeftPadWord256([]byte{0x5}) - if got.Compare(expected) != 0 { - t.Fatalf("Wrong storage value. Got %x, expected %x", got.Bytes(), - expected.Bytes()) - } -} - -func testCallCode(t *testing.T, typ string) { - client := clients[typ] - - // add two integers and return the result - code := []byte{0x60, 0x5, 0x60, 0x6, 0x1, 0x60, 0x0, 0x52, 0x60, 0x20, 0x60, - 0x0, 0xf3} - data := []byte{} - expected := []byte{0xb} - callCode(t, client, user[0].PubKey.Address(), code, data, expected) - - // pass two ints as calldata, add, and return the result - code = []byte{0x60, 0x0, 0x35, 0x60, 0x20, 0x35, 0x1, 0x60, 0x0, 0x52, 0x60, - 0x20, 0x60, 0x0, 0xf3} - data = append(tm_common.LeftPadWord256([]byte{0x5}).Bytes(), - tm_common.LeftPadWord256([]byte{0x6}).Bytes()...) - expected = []byte{0xb} - callCode(t, client, user[0].PubKey.Address(), code, data, expected) -} - -func testCall(t *testing.T, typ string) { - wsc := newWSClient(t) - eid := txs.EventStringNewBlock() - subscribe(t, wsc, eid) - defer func() { - unsubscribe(t, wsc, eid) - wsc.Stop() - }() - - client := clients[typ] - - // create the contract - amt, gasLim, fee := int64(6969), int64(1000), int64(1000) - code, _, _ := simpleContract() - tx := makeDefaultCallTx(t, typ, nil, code, amt, gasLim, fee) - receipt, err := broadcastTxAndWaitForBlock(t, typ, wsc, tx) - if err != nil { - t.Fatalf("Problem broadcasting transaction: %v", 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"+ - " transaction hash") - contractAddr := receipt.ContractAddr - assert.NotEqual(t, 0, len(contractAddr), "Transactions claims to have"+ - " created a contract but the contract address is empty") - - // run a call through the contract - data := []byte{} - expected := []byte{0xb} - callContract(t, client, user[0].PubKey.Address(), contractAddr, data, expected) -} - -func testNameReg(t *testing.T, typ string) { - wsc := newWSClient(t) - - txs.MinNameRegistrationPeriod = 1 - - // 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 - const data = "if not now, when" - fee := int64(1000) - numDesiredBlocks := int64(2) - amt := fee + numDesiredBlocks*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) - - tx := makeDefaultNameTx(t, typ, name, data, amt, fee) - // verify the name by both using the event and by checking get_name - 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) - 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(5) - 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) - - assert.Equal(t, updatedData, entry.Data) - - // try to update as non owner, should fail - tx = txs.NewNameTxWithNonce(user[1].PubKey, name, "never mind", amt, fee, - getNonce(t, typ, user[1].Address)+1) - tx.Sign(chainID, user[1]) - - _, err := broadcastTxAndWaitForBlock(t, typ, wsc, tx) - assert.Error(t, err, "Expected error when updating someone else's unexpired"+ - " name registry entry") - assert.Contains(t, err.Error(), "permission denied", "Error should be " + - "permission denied") - - // Wait a couple of blocks to make sure name registration expires - waitNBlocks(t, wsc, 3) - - //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) - 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(_ string, _ txs.EventData) (bool, error) { - // And ask waitForEvent to stop waiting - return true, nil -} - -func testSubscribe(t *testing.T) { - var subId string - wsc := newWSClient(t) - subscribe(t, wsc, txs.EventStringNewBlock()) - - timeout := time.NewTimer(timeoutSeconds * time.Second) -Subscribe: - for { - select { - case <-timeout.C: - t.Fatal("Timed out waiting for subscription result") - - case bs := <-wsc.ResultsCh: - resultSubscribe, ok := readResult(t, bs).(*core_types.ResultSubscribe) - if ok { - assert.Equal(t, txs.EventStringNewBlock(), resultSubscribe.Event) - subId = resultSubscribe.SubscriptionId - break Subscribe - } - } - } - - seenBlock := false - timeout = time.NewTimer(timeoutSeconds * time.Second) - for { - select { - case <-timeout.C: - if !seenBlock { - t.Fatal("Timed out without seeing a NewBlock event") - } - return - - case bs := <-wsc.ResultsCh: - resultEvent, ok := readResult(t, bs).(*core_types.ResultEvent) - if ok { - _, ok := resultEvent.Data.(txs.EventDataNewBlock) - if ok { - if seenBlock { - // There's a mild race here, but when we enter we've just seen a block - // so we should be able to unsubscribe before we see another block - t.Fatal("Continued to see NewBlock event after unsubscribing") - } else { - seenBlock = true - unsubscribe(t, wsc, subId) - } - } - } - } - } -} - -func testBlockchainInfo(t *testing.T, typ string) { - client := clients[typ] - wsc := newWSClient(t) - nBlocks := 4 - waitNBlocks(t, wsc, nBlocks) - - resp, err := edbcli.BlockchainInfo(client, 0, 0) - if err != nil { - t.Fatalf("Failed to get blockchain info: %v", err) - } - //TODO: [Silas] reintroduce this when Tendermint changes logic to fire - // NewBlock after saving a block - // see https://github.com/tendermint/tendermint/issues/273 - //assert.Equal(t, 4, resp.LastHeight, "Last height should be 4 after waiting for first 4 blocks") - assert.True(t, nBlocks <= len(resp.BlockMetas), - "Should see at least 4 BlockMetas after waiting for first 4 blocks") - - lastBlockHash := resp.BlockMetas[nBlocks-1].Hash - for i := nBlocks - 2; i >= 0; i-- { - assert.Equal(t, lastBlockHash, resp.BlockMetas[i].Header.LastBlockHash, - "Blockchain should be a hash tree!") - lastBlockHash = resp.BlockMetas[i].Hash - } - - resp, err = edbcli.BlockchainInfo(client, 1, 2) - if err != nil { - t.Fatalf("Failed to get blockchain info: %v", err) - } - - assert.Equal(t, 2, len(resp.BlockMetas), - "Should see 2 BlockMetas after extracting 2 blocks") - - fmt.Printf("%v\n", resp) -} diff --git a/rpc/tendermint/test/client_ws_test.go b/rpc/tendermint/test/websocket_client_test.go similarity index 72% rename from rpc/tendermint/test/client_ws_test.go rename to rpc/tendermint/test/websocket_client_test.go index 76c61fde59a4bf39d5973a3981c14333f3433e37..f10ae1e68687c49da1f712a8a7614c83c85713f4 100644 --- a/rpc/tendermint/test/client_ws_test.go +++ b/rpc/tendermint/test/websocket_client_test.go @@ -7,13 +7,14 @@ import ( "fmt" "testing" + "time" + + core_types "github.com/eris-ltd/eris-db/rpc/tendermint/core/types" "github.com/eris-ltd/eris-db/txs" "github.com/stretchr/testify/assert" _ "github.com/tendermint/tendermint/config/tendermint_test" ) -var wsTyp = "JSONRPC" - //-------------------------------------------------------------------------------- // Test the websocket service @@ -91,8 +92,8 @@ func TestWSSend(t *testing.T) { wsc.Stop() }() waitForEvent(t, wsc, eidInput, func() { - tx := makeDefaultSendTxSigned(t, wsTyp, toAddr, amt) - broadcastTx(t, wsTyp, tx) + tx := makeDefaultSendTxSigned(t, jsonRpcClient, toAddr, amt) + broadcastTx(t, jsonRpcClient, tx) }, unmarshalValidateSend(amt, toAddr)) waitForEvent(t, wsc, eidOutput, func() {}, @@ -115,8 +116,8 @@ func TestWSDoubleFire(t *testing.T) { toAddr := user[1].Address // broadcast the transaction, wait to hear about it waitForEvent(t, wsc, eid, func() { - tx := makeDefaultSendTxSigned(t, wsTyp, toAddr, amt) - broadcastTx(t, wsTyp, tx) + tx := makeDefaultSendTxSigned(t, jsonRpcClient, toAddr, amt) + broadcastTx(t, jsonRpcClient, tx) }, func(eid string, b txs.EventData) (bool, error) { return true, nil }) @@ -147,8 +148,8 @@ func TestWSCallWait(t *testing.T) { var contractAddr []byte // wait for the contract to be created waitForEvent(t, wsc, eid1, func() { - tx := makeDefaultCallTx(t, wsTyp, nil, code, amt, gasLim, fee) - receipt := broadcastTx(t, wsTyp, tx) + tx := makeDefaultCallTx(t, jsonRpcClient, nil, code, amt, gasLim, fee) + receipt := broadcastTx(t, jsonRpcClient, tx) contractAddr = receipt.ContractAddr }, unmarshalValidateTx(amt, returnCode)) @@ -162,8 +163,8 @@ func TestWSCallWait(t *testing.T) { // get the return value from a call data := []byte{0x1} waitForEvent(t, wsc, eid2, func() { - tx := makeDefaultCallTx(t, wsTyp, contractAddr, data, amt, gasLim, fee) - receipt := broadcastTx(t, wsTyp, tx) + tx := makeDefaultCallTx(t, jsonRpcClient, contractAddr, data, amt, gasLim, fee) + receipt := broadcastTx(t, jsonRpcClient, tx) contractAddr = receipt.ContractAddr }, unmarshalValidateTx(amt, returnVal)) } @@ -178,8 +179,8 @@ func TestWSCallNoWait(t *testing.T) { amt, gasLim, fee := int64(10000), int64(1000), int64(1000) code, _, returnVal := simpleContract() - tx := makeDefaultCallTx(t, wsTyp, nil, code, amt, gasLim, fee) - receipt := broadcastTx(t, wsTyp, tx) + tx := makeDefaultCallTx(t, jsonRpcClient, nil, code, amt, gasLim, fee) + receipt := broadcastTx(t, jsonRpcClient, tx) contractAddr := receipt.ContractAddr // susbscribe to the new contract @@ -193,8 +194,8 @@ func TestWSCallNoWait(t *testing.T) { // get the return value from a call data := []byte{0x1} waitForEvent(t, wsc, eid, func() { - tx := makeDefaultCallTx(t, wsTyp, contractAddr, data, amt, gasLim, fee) - broadcastTx(t, wsTyp, tx) + tx := makeDefaultCallTx(t, jsonRpcClient, contractAddr, data, amt, gasLim, fee) + broadcastTx(t, jsonRpcClient, tx) }, unmarshalValidateTx(amt, returnVal)) } @@ -209,13 +210,13 @@ func TestWSCallCall(t *testing.T) { txid := new([]byte) // deploy the two contracts - tx := makeDefaultCallTx(t, wsTyp, nil, code, amt, gasLim, fee) - receipt := broadcastTx(t, wsTyp, tx) + tx := makeDefaultCallTx(t, jsonRpcClient, nil, code, amt, gasLim, fee) + receipt := broadcastTx(t, jsonRpcClient, tx) contractAddr1 := receipt.ContractAddr code, _, _ = simpleCallContract(contractAddr1) - tx = makeDefaultCallTx(t, wsTyp, nil, code, amt, gasLim, fee) - receipt = broadcastTx(t, wsTyp, tx) + tx = makeDefaultCallTx(t, jsonRpcClient, nil, code, amt, gasLim, fee) + receipt = broadcastTx(t, jsonRpcClient, tx) contractAddr2 := receipt.ContractAddr // subscribe to the new contracts @@ -235,12 +236,59 @@ func TestWSCallCall(t *testing.T) { }) // call it waitForEvent(t, wsc, eid, func() { - tx := makeDefaultCallTx(t, wsTyp, contractAddr2, nil, amt, gasLim, fee) - broadcastTx(t, wsTyp, tx) + tx := makeDefaultCallTx(t, jsonRpcClient, contractAddr2, nil, amt, gasLim, fee) + broadcastTx(t, jsonRpcClient, tx) *txid = txs.TxHash(chainID, tx) }, unmarshalValidateCall(user[0].Address, returnVal, txid)) } func TestSubscribe(t *testing.T) { - testSubscribe(t) + var subId string + wsc := newWSClient(t) + subscribe(t, wsc, txs.EventStringNewBlock()) + + timeout := time.NewTimer(timeoutSeconds * time.Second) +Subscribe: + for { + select { + case <-timeout.C: + t.Fatal("Timed out waiting for subscription result") + + case bs := <-wsc.ResultsCh: + resultSubscribe, ok := readResult(t, bs).(*core_types.ResultSubscribe) + if ok { + assert.Equal(t, txs.EventStringNewBlock(), resultSubscribe.Event) + subId = resultSubscribe.SubscriptionId + break Subscribe + } + } + } + + seenBlock := false + timeout = time.NewTimer(timeoutSeconds * time.Second) + for { + select { + case <-timeout.C: + if !seenBlock { + t.Fatal("Timed out without seeing a NewBlock event") + } + return + + case bs := <-wsc.ResultsCh: + resultEvent, ok := readResult(t, bs).(*core_types.ResultEvent) + if ok { + _, ok := resultEvent.Data.(txs.EventDataNewBlock) + if ok { + if seenBlock { + // There's a mild race here, but when we enter we've just seen a block + // so we should be able to unsubscribe before we see another block + t.Fatal("Continued to see NewBlock event after unsubscribing") + } else { + seenBlock = true + unsubscribe(t, wsc, subId) + } + } + } + } + } } diff --git a/rpc/tendermint/test/ws_helpers.go b/rpc/tendermint/test/websocket_helpers.go similarity index 75% rename from rpc/tendermint/test/ws_helpers.go rename to rpc/tendermint/test/websocket_helpers.go index 1c71a72b812193f75004e8886e562b090c2f6d83..ef34569c31c100fdd4ba9712fef7d86ab7d7286b 100644 --- a/rpc/tendermint/test/ws_helpers.go +++ b/rpc/tendermint/test/websocket_helpers.go @@ -2,20 +2,16 @@ package test import ( "bytes" - "encoding/json" "fmt" "testing" "time" 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" + rpcclient "github.com/tendermint/go-rpc/client" "github.com/tendermint/go-wire" ) @@ -27,8 +23,8 @@ const ( // Utilities for testing the websocket service // create a new connection -func newWSClient(t *testing.T) *client.WSClient { - wsc := client.NewWSClient(websocketAddr, websocketEndpoint) +func newWSClient(t *testing.T) *rpcclient.WSClient { + wsc := rpcclient.NewWSClient(websocketAddr, websocketEndpoint) if _, err := wsc.Start(); err != nil { t.Fatal(err) } @@ -36,13 +32,13 @@ func newWSClient(t *testing.T) *client.WSClient { } // subscribe to an event -func subscribe(t *testing.T, wsc *client.WSClient, eventId string) { +func subscribe(t *testing.T, wsc *rpcclient.WSClient, eventId string) { if err := wsc.Subscribe(eventId); err != nil { t.Fatal(err) } } -func subscribeAndGetSubscriptionId(t *testing.T, wsc *client.WSClient, +func subscribeAndGetSubscriptionId(t *testing.T, wsc *rpcclient.WSClient, eventId string) string { if err := wsc.Subscribe(eventId); err != nil { t.Fatal(err) @@ -63,14 +59,14 @@ func subscribeAndGetSubscriptionId(t *testing.T, wsc *client.WSClient, } // unsubscribe from an event -func unsubscribe(t *testing.T, wsc *client.WSClient, subscriptionId string) { +func unsubscribe(t *testing.T, wsc *rpcclient.WSClient, subscriptionId string) { if err := wsc.Unsubscribe(subscriptionId); err != nil { t.Fatal(err) } } // broadcast transaction and wait for new block -func broadcastTxAndWaitForBlock(t *testing.T, typ string, wsc *client.WSClient, +func broadcastTxAndWaitForBlock(t *testing.T, client rpcclient.Client, wsc *rpcclient.WSClient, tx txs.Tx) (txs.Receipt, error) { var rec txs.Receipt var err error @@ -86,17 +82,17 @@ func broadcastTxAndWaitForBlock(t *testing.T, typ string, wsc *client.WSClient, // state updates, so we have to wait for the block after the block we // want in order for the Tx to be genuinely final. // This should be addressed by: https://github.com/tendermint/tendermint/pull/265 - return block.Height > initialHeight + 1 + return block.Height > initialHeight+1 } }, func() { - rec, err = edbcli.BroadcastTx(clients[typ], tx) + rec, err = edbcli.BroadcastTx(client, tx) mempoolCount += 1 }) return rec, err } -func waitNBlocks(t *testing.T, wsc *client.WSClient, n int) { +func waitNBlocks(t *testing.T, wsc *rpcclient.WSClient, n int) { i := 0 runThenWaitForBlock(t, wsc, func(block *tm_types.Block) bool { @@ -106,7 +102,7 @@ func waitNBlocks(t *testing.T, wsc *client.WSClient, n int) { func() {}) } -func runThenWaitForBlock(t *testing.T, wsc *client.WSClient, +func runThenWaitForBlock(t *testing.T, wsc *rpcclient.WSClient, blockPredicate func(*tm_types.Block) bool, runner func()) { subscribeAndWaitForNext(t, wsc, txs.EventStringNewBlock(), runner, @@ -115,7 +111,7 @@ func runThenWaitForBlock(t *testing.T, wsc *client.WSClient, }) } -func subscribeAndWaitForNext(t *testing.T, wsc *client.WSClient, event string, +func subscribeAndWaitForNext(t *testing.T, wsc *rpcclient.WSClient, event string, runner func(), eventDataChecker func(string, txs.EventData) (bool, error)) { subId := subscribeAndGetSubscriptionId(t, wsc, event) @@ -134,7 +130,7 @@ func subscribeAndWaitForNext(t *testing.T, wsc *client.WSClient, event string, // 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, +func waitForEvent(t *testing.T, wsc *rpcclient.WSClient, eventid string, runner func(), eventDataChecker func(string, txs.EventData) (bool, error)) waitForEventResult { @@ -207,45 +203,8 @@ func (err waitForEventResult) Timeout() bool { return err.timeout } -func acceptFirstBlock(_ *tm_types.Block) bool { - return true -} - //-------------------------------------------------------------------------------- -func unmarshalResponseNewBlock(b []byte) (*types.Block, error) { - // unmarshall and assert somethings - var response rpctypes.RPCResponse - var err error - wire.ReadJSON(&response, b, &err) - if err != nil { - return nil, err - } - if response.Error != "" { - return nil, fmt.Errorf(response.Error) - } - // TODO - //block := response.Result.(*ctypes.ResultEvent).Data.(types.EventDataNewBlock).Block - // return block, nil - return nil, nil -} - -func unmarshalResponseNameReg(b []byte) (*txs.NameTx, error) { - // unmarshall and assert somethings - var response rpctypes.RPCResponse - var err error - wire.ReadJSON(&response, b, &err) - if err != nil { - return nil, err - } - if response.Error != "" { - return nil, fmt.Errorf(response.Error) - } - _, val := UnmarshalEvent(*response.Result) - tx := txs.DecodeTx(val.(types.EventDataTx).Tx).(*txs.NameTx) - return tx, nil -} - func unmarshalValidateSend(amt int64, toAddr []byte) func(string, txs.EventData) (bool, error) { return func(eid string, eventData txs.EventData) (bool, error) { @@ -314,22 +273,6 @@ func unmarshalValidateCall(origin, } } -// Unmarshal a json event -func UnmarshalEvent(b json.RawMessage) (string, events.EventData) { - var err error - result := new(ctypes.ErisDBResult) - wire.ReadJSONPtr(result, b, &err) - if err != nil { - panic(err) - } - event, ok := (*result).(*ctypes.ResultEvent) - if !ok { - return "", nil // TODO: handle non-event messages (ie. return from subscribe/unsubscribe) - // fmt.Errorf("Result is not type *ctypes.ResultEvent. Got %v", reflect.TypeOf(*result)) - } - return event.Event, event.Data -} - func readResult(t *testing.T, bs []byte) ctypes.ErisDBResult { var err error result := new(ctypes.ErisDBResult) diff --git a/rpc/v0/methods.go b/rpc/v0/methods.go index b3aac0e9d5ea0a8a9d53dd18c283151d3e0900c8..8e888e6b3dd69d026c4c2c5e638e06e05c16220a 100644 --- a/rpc/v0/methods.go +++ b/rpc/v0/methods.go @@ -256,17 +256,11 @@ func (erisDbMethods *ErisDbMethods) Block(request *rpc.RPCRequest, requester int // *************************************** Consensus ************************************ func (erisDbMethods *ErisDbMethods) ConsensusState(request *rpc.RPCRequest, requester interface{}) (interface{}, int, error) { - // TODO: [Silas] erisDbMethods has not implemented this since the refactor - // core_types.FromRoundState() will do it, but only if we have access to - // Tendermint's RonudState.. - state := &core_types.ConsensusState{} - return state, 0, nil + return erisDbMethods.pipe.Consensus().ConsensusState(), 0, nil } func (erisDbMethods *ErisDbMethods) Validators(request *rpc.RPCRequest, requester interface{}) (interface{}, int, error) { - // TODO: [Silas] erisDbMethods has not implemented this since the refactor - validators := &core_types.ValidatorList{} - return validators, 0, nil + return erisDbMethods.pipe.Consensus().ListValidators(), 0, nil } // *************************************** Net ************************************ diff --git a/rpc/v0/restServer.go b/rpc/v0/restServer.go index d6ad13bd29589c3ebbff44d86696446ace251dce..42c925a3634e349bcdbd81f855ecd3c51b0fd381 100644 --- a/rpc/v0/restServer.go +++ b/rpc/v0/restServer.go @@ -221,17 +221,13 @@ func (restServer *RestServer) handleBlock(c *gin.Context) { // ********************************* Consensus ********************************* func (restServer *RestServer) handleConsensusState(c *gin.Context) { - // TODO: [Silas] erisDbMethods has not implemented this since the refactor - // core_types.FromRoundState() will do it, but only if we have access to - // Tendermint's RonudState.. - cs := &core_types.ConsensusState{} + cs := restServer.pipe.Consensus().ConsensusState() c.Writer.WriteHeader(200) restServer.codec.Encode(cs, c.Writer) } func (restServer *RestServer) handleValidatorList(c *gin.Context) { - // TODO: [Silas] erisDbMethods has not implemented this since the refactor - vl := &core_types.ValidatorList{} + vl := restServer.pipe.Consensus().ListValidators() c.Writer.WriteHeader(200) restServer.codec.Encode(vl, c.Writer) } diff --git a/test/mock/pipe.go b/test/mock/pipe.go index 78ba9d38783445c94f85e70f6871ca71dffd91c6..b8394ad9ad9e73db7cd0aa46d04bf86f40456f40 100644 --- a/test/mock/pipe.go +++ b/test/mock/pipe.go @@ -208,6 +208,23 @@ func (cons *consensusEngine) Events() event.EventEmitter { return nil } +func (cons *consensusEngine) ListUnconfirmedTxs(maxTxs int) ([]txs.Tx, error) { + return nil, nil + +} + +func (cons *consensusEngine) ListValidators() []consensus_types.Validator { + return nil +} + +func (cons *consensusEngine) ConsensusState() *consensus_types.ConsensusState { + return &consensus_types.ConsensusState{} +} + +func (cons *consensusEngine) PeerConsensusStates() map[string]string { + return map[string]string{} +} + // Events type eventer struct { testData *td.TestData diff --git a/test/testdata/testdata/testdata.go b/test/testdata/testdata/testdata.go index 56894ecb5a55601ba90f8d542e458dfce66f7442..f42cdccdcf595cc1133d647b247ddc0c00c54d25 100644 --- a/test/testdata/testdata/testdata.go +++ b/test/testdata/testdata/testdata.go @@ -2,16 +2,22 @@ package testdata import ( account "github.com/eris-ltd/eris-db/account" + consensus_types "github.com/eris-ltd/eris-db/consensus/types" core_types "github.com/eris-ltd/eris-db/core/types" event "github.com/eris-ltd/eris-db/event" stypes "github.com/eris-ltd/eris-db/manager/eris-mint/state/types" rpc_v0 "github.com/eris-ltd/eris-db/rpc/v0" transaction "github.com/eris-ltd/eris-db/txs" - mintTypes "github.com/tendermint/tendermint/types" ) -var testDataJson = `{ +// TODO: [Silas] This would really be much better as a composite literal in go +// where the compiler/type system/IDE would make it easier to maintain +// not entirely straightforward to convert it, but shouldn't be that hard either +// with recursive use of fmt.Printf("%#v", subStruct) on the decoded in-memory +// object +var testDataJson = ` +{ "chain_data": { "priv_validator": { "address": "37236DF251AB70022B1DA351F08A20FB52443E37", @@ -53,7 +59,10 @@ var testDataJson = `{ ], "validators": [ { - "pub_key": [1, "CB3688B7561D488A2A4834E1AEE9398BEF94844D8BDBBCA980C11E3654A45906"], + "pub_key": [ + 1, + "CB3688B7561D488A2A4834E1AEE9398BEF94844D8BDBBCA980C11E3654A45906" + ], "amount": 5000000000, "unbond_to": [ { @@ -92,20 +101,20 @@ var testDataJson = `{ "output": { "accounts": [ { - "address": "0000000000000000000000000000000000000000", - "pub_key": null, - "sequence": 0, - "balance": 1337, - "code": "", - "storage_root": "", - "permissions": { - "base": { - "perms": 2302, - "set": 16383 - }, - "roles": [] - } - }, + "address": "0000000000000000000000000000000000000000", + "pub_key": null, + "sequence": 0, + "balance": 1337, + "code": "", + "storage_root": "", + "permissions": { + "base": { + "perms": 2302, + "set": 16383 + }, + "roles": [] + } + }, { "address": "0000000000000000000000000000000000000002", "pub_key": null, @@ -243,7 +252,9 @@ var testDataJson = `{ "output": {} }, "GetBlock": { - "input": {"height": 0}, + "input": { + "height": 0 + }, "output": null }, "GetBlocks": { @@ -264,15 +275,23 @@ var testDataJson = `{ "start_time": "", "commit_time": "0001-01-01 00:00:00 +0000 UTC", "validators": [ - { - "address": "37236DF251AB70022B1DA351F08A20FB52443E37", - "pub_key": [1, "CB3688B7561D488A2A4834E1AEE9398BEF94844D8BDBBCA980C11E3654A45906"], - "bond_height": 0, - "unbond_height": 0, - "last_commit_height": 0, - "voting_power": 5000000000, - "accum": 0 - } + [ + 1, + { + "validator": { + "address": "37236DF251AB70022B1DA351F08A20FB52443E37", + "pub_key": [ + 1, + "CB3688B7561D488A2A4834E1AEE9398BEF94844D8BDBBCA980C11E3654A45906" + ], + "bond_height": 0, + "unbond_height": 0, + "last_commit_height": 0, + "voting_power": 5000000000, + "accum": 0 + } + } + ] ], "proposal": null } @@ -283,7 +302,10 @@ var testDataJson = `{ "bonded_validators": [ { "address": "37236DF251AB70022B1DA351F08A20FB52443E37", - "pub_key": [1, "CB3688B7561D488A2A4834E1AEE9398BEF94844D8BDBBCA980C11E3654A45906"], + "pub_key": [ + 1, + "CB3688B7561D488A2A4834E1AEE9398BEF94844D8BDBBCA980C11E3654A45906" + ], "bond_height": 0, "unbond_height": 0, "last_commit_height": 0, @@ -327,7 +349,9 @@ var testDataJson = `{ "output": [] }, "GetPeer": { - "input": {"address": "127.0.0.1:30000"}, + "input": { + "address": "127.0.0.1:30000" + }, "output": { "is_outbound": false, "node_info": null @@ -420,7 +444,11 @@ var testDataJson = `{ } }, "Call": { - "input": {"address": "9FC1ECFCAE2A554D4D1A000D0D80F748E66359E3", "from": "DEADBEEF", "data": ""}, + "input": { + "address": "9FC1ECFCAE2A554D4D1A000D0D80F748E66359E3", + "from": "DEADBEEF", + "data": "" + }, "output": { "return": "6000357c01000000000000000000000000000000000000000000000000000000009004806337f428411461004557806340c10f191461005a578063d0679d341461006e57005b610050600435610244565b8060005260206000f35b610068600435602435610082565b60006000f35b61007c600435602435610123565b60006000f35b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156100dd576100e2565b61011f565b80600160005060008473ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828282505401925050819055505b5050565b80600160005060003373ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600050541061015e57610163565b610240565b80600160005060003373ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282825054039250508190555080600160005060008473ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828282505401925050819055507f93eb3c629eb575edaf0252e4f9fc0c5ccada50496f8c1d32f0f93a65a8257eb560003373ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281526020016000a15b5050565b6000600160005060008373ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060005054905061027d565b91905056", "gas_used": 0 @@ -484,7 +512,8 @@ var testDataJson = `{ "name": "testKey", "owner": "37236DF251AB70022B1DA351F08A20FB52443E37", "data": "testData", - "expires": 250 } + "expires": 250 + } }, "GetNameRegEntries": { "input": { @@ -492,15 +521,18 @@ var testDataJson = `{ }, "output": { "block_height": 1, - "names":[ { - "name": "testKey", - "owner": "37236DF251AB70022B1DA351F08A20FB52443E37", - "data": "testData", - "expires": 250 - } ] + "names": [ + { + "name": "testKey", + "owner": "37236DF251AB70022B1DA351F08A20FB52443E37", + "data": "testData", + "expires": 250 + } + ] } } -}` +} +` var serverDuration uint = 100 @@ -565,7 +597,7 @@ type ( } GetConsensusStateData struct { - Output *core_types.ConsensusState `json:"output"` + Output *consensus_types.ConsensusState `json:"output"` } GetValidatorsData struct { diff --git a/txs/tx.go b/txs/tx.go index 9957d570060d6c37ae266e8fc3b75adcb63e7796..e0069c5b78af512b225075696bd5e089e2c2b71b 100644 --- a/txs/tx.go +++ b/txs/tx.go @@ -14,7 +14,7 @@ import ( "github.com/tendermint/go-wire" "github.com/tendermint/go-crypto" - "github.com/tendermint/tendermint/types" // votes for dupeout .. + tendermint_types "github.com/tendermint/tendermint/types" // votes for dupeout .. ) var ( @@ -341,8 +341,8 @@ func (tx *RebondTx) String() string { type DupeoutTx struct { Address []byte `json:"address"` - VoteA types.Vote `json:"vote_a"` - VoteB types.Vote `json:"vote_b"` + VoteA tendermint_types.Vote `json:"vote_a"` + VoteB tendermint_types.Vote `json:"vote_b"` } func (tx *DupeoutTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { @@ -378,12 +378,6 @@ func (tx *PermissionsTx) String() string { //----------------------------------------------------------------------------- func TxHash(chainID string, tx Tx) []byte { - signBytes := acm.SignBytes(chainID, tx) - return wire.BinaryRipemd160(signBytes) -} - -// [Silas] Leaving this implementation here for when we transition -func TxHashFuture(chainID string, tx Tx) []byte { signBytes := acm.SignBytes(chainID, tx) hasher := ripemd160.New() hasher.Write(signBytes) @@ -393,34 +387,28 @@ func TxHashFuture(chainID string, tx Tx) []byte { //----------------------------------------------------------------------------- -func EncodeTx(tx Tx) []byte { - wrapTx := struct { - Tx Tx `json:"unwrap"` - }{tx} - return wire.BinaryBytes(wrapTx) +func EncodeTx(tx Tx) ([]byte, error) { + var n int + var err error + buf := new(bytes.Buffer) + wire.WriteBinary(struct{ Tx }{tx}, buf, &n, &err) + if err != nil { + return nil, err + } + return buf.Bytes(), nil } -//func EncodeTx(tx txs.Tx) []byte { -// buf := new(bytes.Buffer) -// var n int -// var err error -// wire.WriteBinary(struct{ types.Tx }{tx}, buf, &n, &err) -// if err != nil { -// return err -// } -//} - // panic on err -func DecodeTx(txBytes []byte) Tx { +func DecodeTx(txBytes []byte) (Tx, error) { var n int var err error tx := new(Tx) buf := bytes.NewBuffer(txBytes) wire.ReadBinaryPtr(tx, buf, len(txBytes), &n, &err) if err != nil { - panic(err) + return nil, err } - return *tx + return *tx, nil } func GenerateReceipt(chainId string, tx Tx) Receipt { diff --git a/txs/tx_test.go b/txs/tx_test.go index 5a73d351cab29b7fefc51c425e6f385a05abf3aa..1fecefa56aa3cde6bfff8e45e6f10271de91b2f6 100644 --- a/txs/tx_test.go +++ b/txs/tx_test.go @@ -6,9 +6,9 @@ import ( acm "github.com/eris-ltd/eris-db/account" ptypes "github.com/eris-ltd/eris-db/permission/types" + "github.com/stretchr/testify/assert" . "github.com/tendermint/go-common" "github.com/tendermint/go-crypto" - //"github.com/tendermint/tendermint/types" ) var chainID = "myChainID" @@ -177,6 +177,30 @@ func TestPermissionsTxSignable(t *testing.T) { } } +func TestEncodeTxDecodeTx(t *testing.T) { + inputAddress := []byte{1, 2, 3, 4, 5} + outputAddress := []byte{5, 4, 3, 2, 1} + amount := int64(2) + sequence := 1 + tx := &SendTx{ + Inputs: []*TxInput{{ + Address: inputAddress, + Amount: amount, + Sequence: sequence, + }}, + Outputs: []*TxOutput{{ + Address: outputAddress, + Amount: amount, + }}, + } + txBytes, err := EncodeTx(tx) + if err != nil { + t.Fatal(err) + } + txOut, err := DecodeTx(txBytes) + assert.Equal(t, tx, txOut) +} + /* func TestDupeoutTxSignable(t *testing.T) { privAcc := acm.GenPrivAccount()