diff --git a/event/filters_test.go b/event/filters_test.go index cf78c3c11489c5e4265517a2e4492bc53a13dafc..959f4c47f1df7627eb9dc0c4630d75d801ac9fc9 100644 --- a/event/filters_test.go +++ b/event/filters_test.go @@ -33,8 +33,8 @@ type FilterableObject struct { // Ops: All type IntegerFilter struct { op string - value int64 - match func(int64, int64) bool + value uint64 + match func(uint64, uint64) bool } func (this *IntegerFilter) Configure(fd *FilterData) error { @@ -57,7 +57,7 @@ func (this *IntegerFilter) Match(v interface{}) bool { if !ok { return false } - return this.match(int64(fo.Integer), this.value) + return this.match(uint64(fo.Integer), this.value) } // Filter for integer value. diff --git a/rpc/config.go b/rpc/config.go new file mode 100644 index 0000000000000000000000000000000000000000..230c2987fe0262b9fb8d7c82b1d0e1414cf1c7ea --- /dev/null +++ b/rpc/config.go @@ -0,0 +1,25 @@ +package rpc + +type RPCConfig struct { + V0 *TMConfig `json:",omitempty" toml:",omitempty"` + TM *TMConfig `json:",omitempty" toml:",omitempty"` +} + +type TMConfig struct { + ListenAddress string +} + +type V0Config struct { +} + +func DefaultRPCConfig() *RPCConfig { + return &RPCConfig{ + TM: DefaultTMConfig(), + } +} + +func DefaultTMConfig() *TMConfig { + return &TMConfig{ + ListenAddress: ":46657", + } +} diff --git a/rpc/result.go b/rpc/result.go new file mode 100644 index 0000000000000000000000000000000000000000..cee5d57e1508584143062b3975f87cef6d3a5404 --- /dev/null +++ b/rpc/result.go @@ -0,0 +1,229 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rpc + +import ( + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/execution" + "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/txs" + "github.com/tendermint/go-wire/data" + ctypes "github.com/tendermint/tendermint/consensus/types" + "github.com/tendermint/tendermint/p2p" + tm_types "github.com/tendermint/tendermint/types" +) + +type Result struct { + ResultInner `json:"unwrap"` +} + +type ResultInner interface { +} + +func (res Result) Unwrap() ResultInner { + return res.ResultInner +} + +func (br Result) MarshalJSON() ([]byte, error) { + return mapper.ToJSON(br.ResultInner) +} + +func (br *Result) UnmarshalJSON(data []byte) (err error) { + parsed, err := mapper.FromJSON(data) + if err == nil && parsed != nil { + br.ResultInner = parsed.(ResultInner) + } + return err +} + +var mapper = data.NewMapper(Result{}). + // Transact + RegisterImplementation(&ResultBroadcastTx{}, "result_broadcast_tx", biota()). + // Events + RegisterImplementation(&ResultSubscribe{}, "result_subscribe", biota()). + RegisterImplementation(&ResultUnsubscribe{}, "result_unsubscribe", biota()). + RegisterImplementation(&ResultEvent{}, "result_event", biota()). + // Status + RegisterImplementation(&ResultStatus{}, "result_status", biota()). + RegisterImplementation(&ResultNetInfo{}, "result_net_info", biota()). + // Accounts + RegisterImplementation(&ResultGetAccount{}, "result_get_account", biota()). + RegisterImplementation(&ResultListAccounts{}, "result_list_account", biota()). + RegisterImplementation(&ResultGetStorage{}, "result_get_storage", biota()). + RegisterImplementation(&ResultDumpStorage{}, "result_dump_storage", biota()). + // Simulated call + RegisterImplementation(&ResultCall{}, "result_call", biota()). + // Blockchain + RegisterImplementation(&ResultGenesis{}, "result_genesis", biota()). + RegisterImplementation(&ResultChainId{}, "result_chain_id", biota()). + RegisterImplementation(&ResultBlockchainInfo{}, "result_blockchain_info", biota()). + RegisterImplementation(&ResultGetBlock{}, "result_get_block", biota()). + // Consensus + RegisterImplementation(&ResultListUnconfirmedTxs{}, "result_list_unconfirmed_txs", biota()). + RegisterImplementation(&ResultListValidators{}, "result_list_validators", biota()). + RegisterImplementation(&ResultDumpConsensusState{}, "result_dump_consensus_state", biota()). + RegisterImplementation(&ResultPeers{}, "result_peers", biota()). + // Names + RegisterImplementation(&ResultGetName{}, "result_get_name", biota()). + RegisterImplementation(&ResultListNames{}, "result_list_names", biota()). + // Private keys and signing + RegisterImplementation(&ResultSignTx{}, "result_sign_tx", biota()). + RegisterImplementation(&ResultGeneratePrivateAccount{}, "result_generate_private_account", biota()) + +type ResultGetStorage struct { + Key []byte `json:"key"` + Value []byte `json:"value"` +} + +func (br Result) ResultGetStorage() *ResultGetStorage { + if res, ok := br.ResultInner.(*ResultGetStorage); ok { + return res + } + return nil +} + +type ResultCall struct { + *execution.Call `json:"unwrap"` +} + +type ResultListAccounts struct { + BlockHeight uint64 `json:"block_height"` + Accounts []*acm.ConcreteAccount `json:"accounts"` +} + +type ResultDumpStorage struct { + StorageRoot []byte `json:"storage_root"` + StorageItems []StorageItem `json:"storage_items"` +} + +type StorageItem struct { + Key []byte `json:"key"` + Value []byte `json:"value"` +} + +type ResultBlockchainInfo struct { + LastHeight uint64 `json:"last_height"` + BlockMetas []*tm_types.BlockMeta `json:"block_metas"` +} + +type ResultGetBlock struct { + BlockMeta *tm_types.BlockMeta `json:"block_meta"` + Block *tm_types.Block `json:"block"` +} + +type ResultStatus struct { + NodeInfo *p2p.NodeInfo `json:"node_info"` + GenesisHash []byte `json:"genesis_hash"` + PubKey acm.PublicKey `json:"pub_key"` + LatestBlockHash []byte `json:"latest_block_hash"` + LatestBlockHeight uint64 `json:"latest_block_height"` + LatestBlockTime int64 `json:"latest_block_time"` // nano +} + +type ResultChainId struct { + ChainName string `json:"chain_name"` + ChainId string `json:"chain_id"` + GenesisHash []byte `json:"genesis_hash"` +} + +type ResultSubscribe struct { + Event string `json:"event"` + SubscriptionId string `json:"subscription_id"` +} + +type ResultUnsubscribe struct { + SubscriptionId string `json:"subscription_id"` +} + +type Peer struct { + NodeInfo *p2p.NodeInfo `json:"node_info"` + IsOutbound bool `json:"is_outbound"` +} + +type ResultNetInfo struct { + Listening bool `json:"listening"` + Listeners []string `json:"listeners"` + Peers []*Peer `json:"peers"` +} + +type ResultListValidators struct { + BlockHeight uint64 `json:"block_height"` + BondedValidators []*acm.ConcreteValidator `json:"bonded_validators"` + UnbondingValidators []*acm.ConcreteValidator `json:"unbonding_validators"` +} + +type ResultDumpConsensusState struct { + RoundState *ctypes.RoundState `json:"consensus_state"` + PeerRoundStates []*ctypes.PeerRoundState `json:"peer_round_states"` +} + +type ResultPeers struct { + Peers []*Peer `json:"peers"` +} + +type ResultListNames struct { + BlockHeight uint64 `json:"block_height"` + Names []*execution.NameRegEntry `json:"names"` +} + +type ResultGeneratePrivateAccount struct { + PrivAccount *acm.ConcretePrivateAccount `json:"priv_account"` +} + +type ResultGetAccount struct { + Account *acm.ConcreteAccount `json:"account"` +} + +type ResultBroadcastTx struct { + *txs.Receipt `json:"unwrap"` +} + +type ResultListUnconfirmedTxs struct { + N int `json:"n_txs"` + Txs []txs.Wrapper `json:"txs"` +} + +type ResultGetName struct { + Entry *execution.NameRegEntry `json:"entry"` +} + +type ResultGenesis struct { + Genesis genesis.GenesisDoc `json:"genesis"` +} + +type ResultSignTx struct { + Tx txs.Wrapper `json:"tx"` +} + +type ResultEvent struct { + Event string `json:"event"` + event.AnyEventData `json:"data"` +} + +func (re ResultEvent) Wrap() Result { + return Result{ + ResultInner: &re, + } +} + +// Type byte helper +var nextByte byte = 1 + +func biota() (b byte) { + b = nextByte + nextByte++ + return +} diff --git a/rpc/result_test.go b/rpc/result_test.go new file mode 100644 index 0000000000000000000000000000000000000000..983f7cf7258167bb13672e910ea32d954b2ed4df --- /dev/null +++ b/rpc/result_test.go @@ -0,0 +1,84 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rpc + +import ( + "testing" + + "fmt" + + "encoding/json" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/txs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tendermint/go-wire" +) + +func TestResultBroadcastTx(t *testing.T) { + // Make sure these are unpacked as expected + res := ResultBroadcastTx{ + Receipt: &txs.Receipt{ + ContractAddr: acm.Address{0, 2, 3}, + CreatesContract: true, + TxHash: []byte("foo"), + }, + } + + assert.Equal(t, `{"tx_hash":"666F6F","creates_contract":true,"contract_addr":"0002030000000000000000000000000000000000"}`, + string(wire.JSONBytes(res))) + + res2 := new(ResultBroadcastTx) + wire.ReadBinaryBytes(wire.BinaryBytes(res), res2) + assert.Equal(t, res, *res2) +} + +func TestListUnconfirmedTxs(t *testing.T) { + res := &ResultListUnconfirmedTxs{ + N: 3, + Txs: []txs.Tx{ + &txs.CallTx{ + Address: &acm.Address{1}, + }, + }, + } + fmt.Println(string(wire.JSONBytes(res))) + +} + +func TestResultListAccounts(t *testing.T) { + concreteAcc := acm.AsConcreteAccount(acm.FromAddressable( + acm.GeneratePrivateAccountFromSecret("Super Semi Secret"))) + acc := concreteAcc + res := ResultListAccounts{ + Accounts: []*acm.ConcreteAccount{acc}, + BlockHeight: 2, + } + bs, err := json.Marshal(res) + require.NoError(t, err) + resOut := new(ResultListAccounts) + json.Unmarshal(bs, resOut) + bsOut, err := json.Marshal(resOut) + require.NoError(t, err) + assert.Equal(t, string(bs), string(bsOut)) +} + +func TestResultEvent(t *testing.T) { + res := ResultEvent{ + Event: "a", + } + fmt.Println(res) +} diff --git a/rpc/rpc_test.go b/rpc/rpc_test.go deleted file mode 100644 index fbdfca374c8cd4a764ea064d06e74ce5b5ca0e4b..0000000000000000000000000000000000000000 --- a/rpc/rpc_test.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package rpc - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -// ... -func TestNewJsonRpcResponse(t *testing.T) { - id := "testId" - data := "a string" - resp := RPCResponse(&RPCResultResponse{ - Result: data, - Id: id, - JSONRPC: "2.0", - }) - respGen := NewRPCResponse(id, data) - assert.Equal(t, respGen, resp) -} - -// ... -func TestNewJsonRpcErrorResponse(t *testing.T) { - id := "testId" - code := 100 - message := "the error" - resp := RPCResponse(&RPCErrorResponse{ - Error: &RPCError{code, message}, - Id: id, - JSONRPC: "2.0", - }) - respGen := NewRPCErrorResponse(id, code, message) - assert.Equal(t, respGen, resp) -} diff --git a/rpc/service.go b/rpc/service.go new file mode 100644 index 0000000000000000000000000000000000000000..b2fc8454be2d56643475d6cb46803ed6d87e3a98 --- /dev/null +++ b/rpc/service.go @@ -0,0 +1,401 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rpc + +import ( + "fmt" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/binary" + bcm "github.com/hyperledger/burrow/blockchain" + "github.com/hyperledger/burrow/consensus/tendermint/query" + "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/execution" + "github.com/hyperledger/burrow/logging" + "github.com/hyperledger/burrow/logging/structure" + logging_types "github.com/hyperledger/burrow/logging/types" + "github.com/hyperledger/burrow/txs" + tm_types "github.com/tendermint/tendermint/types" +) + +// Magic! Should probably be configurable, but not shouldn't be so huge we +// end up DoSing ourselves. +const MaxBlockLookback = 100 + +// Base service that provides implementation for all underlying RPC methods +type Service interface { + // Transact + BroadcastTx(tx txs.Tx) (*ResultBroadcastTx, error) + // Events + Subscribe(eventId string, callback func(eventData event.AnyEventData)) (*ResultSubscribe, error) + Unsubscribe(subscriptionId string) (*ResultUnsubscribe, error) + // Status + Status() (*ResultStatus, error) + NetInfo() (*ResultNetInfo, error) + // Accounts + GetAccount(address acm.Address) (*ResultGetAccount, error) + ListAccounts() (*ResultListAccounts, error) + GetStorage(address acm.Address, key []byte) (*ResultGetStorage, error) + DumpStorage(address acm.Address) (*ResultDumpStorage, error) + // Simulated call + Call(fromAddress, toAddress acm.Address, data []byte) (*ResultCall, error) + CallCode(fromAddress acm.Address, code, data []byte) (*ResultCall, error) + // Blockchain + Genesis() (*ResultGenesis, error) + ChainId() (*ResultChainId, error) + BlockchainInfo(minHeight, maxHeight uint64) (*ResultBlockchainInfo, error) + GetBlock(height uint64) (*ResultGetBlock, error) + // Consensus + ListUnconfirmedTxs(maxTxs int) (*ResultListUnconfirmedTxs, error) + ListValidators() (*ResultListValidators, error) + DumpConsensusState() (*ResultDumpConsensusState, error) + Peers() (*ResultPeers, error) + // Names + GetName(name string) (*ResultGetName, error) + ListNames() (*ResultListNames, error) + // Private keys and signing + SignTx(tx txs.Tx, concretePrivateAccounts []*acm.ConcretePrivateAccount) (*ResultSignTx, error) + GeneratePrivateAccount() (*ResultGeneratePrivateAccount, error) +} + +type service struct { + state acm.StateIterable + eventEmitter event.Emitter + nameReg execution.NameRegIterable + blockchain bcm.Blockchain + transactor execution.Transactor + nodeView query.NodeView + logger logging_types.InfoTraceLogger +} + +var _ Service = &service{} + +func NewService(state acm.StateIterable, eventEmitter event.Emitter, nameReg execution.NameRegIterable, + blockchain bcm.Blockchain, transactor execution.Transactor, nodeView query.NodeView, + logger logging_types.InfoTraceLogger) *service { + + return &service{ + state: state, + eventEmitter: eventEmitter, + nameReg: nameReg, + blockchain: blockchain, + transactor: transactor, + nodeView: nodeView, + logger: logger.With(structure.ComponentKey, "Service"), + } +} + +// All methods in this file return (Result*, error) which is the return +// signature assumed by go-rpc + +func (s *service) Subscribe(eventId string, callback func(event.AnyEventData)) (*ResultSubscribe, error) { + subscriptionId, err := event.GenerateSubId() + + logging.InfoMsg(s.logger, "Subscribing to event", + "eventId", eventId, "subscriptionId", subscriptionId) + if err != nil { + return nil, err + } + err = s.eventEmitter.Subscribe(subscriptionId, eventId, callback) + if err != nil { + return nil, err + } + return &ResultSubscribe{ + SubscriptionId: subscriptionId, + Event: eventId, + }, nil +} + +func (s *service) Unsubscribe(subscriptionId string) (*ResultUnsubscribe, error) { + err := s.eventEmitter.Unsubscribe(subscriptionId) + if err != nil { + return nil, err + } else { + return &ResultUnsubscribe{SubscriptionId: subscriptionId}, nil + } +} + +func (s *service) Status() (*ResultStatus, error) { + tip := s.blockchain.Tip() + latestHeight := tip.LastBlockHeight() + var ( + latestBlockMeta *tm_types.BlockMeta + latestBlockHash []byte + latestBlockTime int64 + ) + if latestHeight != 0 { + latestBlockMeta = s.nodeView.BlockStore().LoadBlockMeta(int(latestHeight)) + latestBlockHash = latestBlockMeta.Header.Hash() + latestBlockTime = latestBlockMeta.Header.Time.UnixNano() + } + return &ResultStatus{ + NodeInfo: s.nodeView.NodeInfo(), + GenesisHash: s.blockchain.GenesisHash(), + PubKey: s.nodeView.PrivValidatorPublicKey(), + LatestBlockHash: latestBlockHash, + LatestBlockHeight: latestHeight, + LatestBlockTime: latestBlockTime}, nil +} + +func (s *service) ChainId() (*ResultChainId, error) { + return &ResultChainId{ + ChainName: s.blockchain.GenesisDoc().ChainName, + ChainId: s.blockchain.ChainID(), + GenesisHash: s.blockchain.GenesisHash(), + }, nil +} + +func (s *service) Peers() (*ResultPeers, error) { + peers := make([]*Peer, s.nodeView.Peers().Size()) + for i, peer := range s.nodeView.Peers().List() { + peers[i] = &Peer{ + NodeInfo: peer.NodeInfo(), + IsOutbound: peer.IsOutbound(), + } + } + return &ResultPeers{ + Peers: peers, + }, nil +} + +func (s *service) NetInfo() (*ResultNetInfo, error) { + listening := s.nodeView.IsListening() + listeners := []string{} + for _, listener := range s.nodeView.Listeners() { + listeners = append(listeners, listener.String()) + } + peers, err := s.Peers() + if err != nil { + return nil, err + } + return &ResultNetInfo{ + Listening: listening, + Listeners: listeners, + Peers: peers.Peers, + }, nil +} + +func (s *service) Genesis() (*ResultGenesis, error) { + return &ResultGenesis{ + Genesis: s.blockchain.GenesisDoc(), + }, nil +} + +// Accounts +func (s *service) GetAccount(address acm.Address) (*ResultGetAccount, error) { + acc, err := s.state.GetAccount(address) + if err != nil { + return nil, err + } + return &ResultGetAccount{Account: acm.AsConcreteAccount(acc)}, nil +} + +func (s *service) ListAccounts() (*ResultListAccounts, error) { + accounts := make([]*acm.ConcreteAccount, 0) + s.state.IterateAccounts(func(account acm.Account) (stop bool) { + accounts = append(accounts, acm.AsConcreteAccount(account)) + return + }) + + return &ResultListAccounts{ + BlockHeight: s.blockchain.Tip().LastBlockHeight(), + Accounts: accounts, + }, nil +} + +func (s *service) GetStorage(address acm.Address, key []byte) (*ResultGetStorage, error) { + account, err := s.state.GetAccount(address) + if err != nil { + return nil, err + } + if account == nil { + return nil, fmt.Errorf("UnknownAddress: %s", address) + } + + value, err := s.state.GetStorage(address, binary.LeftPadWord256(key)) + if err != nil { + return nil, err + } + if value == binary.Zero256 { + return &ResultGetStorage{Key: key, Value: nil}, nil + } + return &ResultGetStorage{Key: key, Value: value.UnpadLeft()}, nil +} + +func (s *service) DumpStorage(address acm.Address) (*ResultDumpStorage, error) { + account, err := s.state.GetAccount(address) + if err != nil { + return nil, err + } + if account == nil { + return nil, fmt.Errorf("UnknownAddress: %X", address) + } + storageItems := []StorageItem{} + s.state.IterateStorage(address, func(key, value binary.Word256) (stop bool) { + storageItems = append(storageItems, StorageItem{Key: key.UnpadLeft(), Value: value.UnpadLeft()}) + return + }) + return &ResultDumpStorage{ + StorageRoot: account.StorageRoot(), + StorageItems: storageItems, + }, nil +} + +func (s *service) Call(fromAddress, toAddress acm.Address, data []byte) (*ResultCall, error) { + call, err := s.transactor.Call(fromAddress, toAddress, data) + if err != nil { + return nil, err + } + return &ResultCall{ + Call: call, + }, nil +} + +func (s *service) CallCode(fromAddress acm.Address, code, data []byte) (*ResultCall, error) { + call, err := s.transactor.CallCode(fromAddress, code, data) + if err != nil { + return nil, err + } + return &ResultCall{ + Call: call, + }, nil +} + +// Name registry +func (s *service) GetName(name string) (*ResultGetName, error) { + entry := s.nameReg.GetNameRegEntry(name) + if entry == nil { + return nil, fmt.Errorf("name %s not found", name) + } + return &ResultGetName{Entry: entry}, nil +} + +func (s *service) ListNames() (*ResultListNames, error) { + var names []*execution.NameRegEntry + s.nameReg.IterateNameRegEntries(func(entry *execution.NameRegEntry) (stop bool) { + names = append(names, entry) + return false + }) + return &ResultListNames{ + BlockHeight: s.blockchain.Tip().LastBlockHeight(), + Names: names, + }, nil +} + +func (s *service) BroadcastTx(tx txs.Tx) (*ResultBroadcastTx, error) { + receipt, err := s.transactor.BroadcastTx(tx) + if err != nil { + return nil, err + } + return &ResultBroadcastTx{ + Receipt: receipt, + }, nil +} + +func (s *service) ListUnconfirmedTxs(maxTxs int) (*ResultListUnconfirmedTxs, error) { + // Get all transactions for now + transactions, err := s.nodeView.MempoolTransactions(maxTxs) + if err != nil { + return nil, err + } + wrappedTxs := make([]txs.Wrapper, len(transactions)) + for i, tx := range transactions { + wrappedTxs[i] = txs.Wrap(tx) + } + return &ResultListUnconfirmedTxs{ + N: len(transactions), + Txs: wrappedTxs, + }, 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. +// Passing 0 for maxHeight sets the upper height of the range to the current +// blockchain height. +func (s *service) BlockchainInfo(minHeight, maxHeight uint64) (*ResultBlockchainInfo, error) { + latestHeight := s.blockchain.Tip().LastBlockHeight() + + if minHeight == 0 { + minHeight = 1 + } + if maxHeight == 0 || latestHeight < maxHeight { + maxHeight = latestHeight + } + if maxHeight > minHeight && maxHeight-minHeight > MaxBlockLookback { + minHeight = maxHeight - MaxBlockLookback + } + + blockMetas := []*tm_types.BlockMeta{} + for height := maxHeight; height >= minHeight; height-- { + blockMeta := s.nodeView.BlockStore().LoadBlockMeta(int(height)) + blockMetas = append(blockMetas, blockMeta) + } + + return &ResultBlockchainInfo{ + LastHeight: latestHeight, + BlockMetas: blockMetas, + }, nil +} + +func (s *service) GetBlock(height uint64) (*ResultGetBlock, error) { + return &ResultGetBlock{ + Block: s.nodeView.BlockStore().LoadBlock(int(height)), + BlockMeta: s.nodeView.BlockStore().LoadBlockMeta(int(height)), + }, nil +} + +func (s *service) ListValidators() (*ResultListValidators, error) { + // TODO: when we reintroduce support for bonding and unbonding update this + // to reflect the mutable bonding state + validators := s.blockchain.Validators() + concreteValidators := make([]*acm.ConcreteValidator, len(validators)) + for i, validator := range validators { + concreteValidators[i] = acm.AsConcreteValidator(validator) + } + return &ResultListValidators{ + BlockHeight: s.blockchain.Tip().LastBlockHeight(), + BondedValidators: concreteValidators, + UnbondingValidators: nil, + }, nil +} + +func (s *service) DumpConsensusState() (*ResultDumpConsensusState, error) { + peerRoundState, err := s.nodeView.PeerRoundStates() + if err != nil { + return nil, err + } + return &ResultDumpConsensusState{ + RoundState: s.nodeView.RoundState(), + PeerRoundStates: peerRoundState, + }, nil +} + +// TODO: Either deprecate this or ensure it can only happen over secure transport +func (s *service) SignTx(tx txs.Tx, concretePrivateAccounts []*acm.ConcretePrivateAccount) (*ResultSignTx, error) { + privateAccounts := make([]acm.PrivateAccount, len(concretePrivateAccounts)) + for i, cpa := range concretePrivateAccounts { + privateAccounts[i] = cpa.PrivateAccount() + } + + tx, err := s.transactor.SignTx(tx, privateAccounts) + return &ResultSignTx{Tx: txs.Wrap(tx)}, err +} + +func (s *service) GeneratePrivateAccount() (*ResultGeneratePrivateAccount, error) { + return &ResultGeneratePrivateAccount{ + PrivAccount: acm.GeneratePrivateAccount().ConcretePrivateAccount, + }, nil +} diff --git a/rpc/tendermint/client/client.go b/rpc/tendermint/client/client.go deleted file mode 100644 index fe9f8ca55e774e61098074c89a94b9f2e2b957e5..0000000000000000000000000000000000000000 --- a/rpc/tendermint/client/client.go +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package client - -import ( - "errors" - "fmt" - - acm "github.com/hyperledger/burrow/account" - core_types "github.com/hyperledger/burrow/core/types" - rpc_types "github.com/hyperledger/burrow/rpc/tendermint/core/types" - "github.com/hyperledger/burrow/txs" - "github.com/tendermint/go-wire" -) - -type RPCClient interface { - Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) -} - -func Status(client RPCClient) (*rpc_types.ResultStatus, error) { - res, err := call(client, "status") - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultStatus), nil -} - -func ChainId(client RPCClient) (*rpc_types.ResultChainId, error) { - res, err := call(client, "chain_id") - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultChainId), nil -} - -func GenPrivAccount(client RPCClient) (*acm.PrivAccount, error) { - res, err := call(client, "unsafe/gen_priv_account") - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultGenPrivAccount).PrivAccount, nil -} - -func GetAccount(client RPCClient, address []byte) (*acm.Account, error) { - res, err := call(client, "get_account", - "address", address) - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultGetAccount).Account, nil -} - -func SignTx(client RPCClient, tx txs.Tx, - privAccounts []*acm.PrivAccount) (txs.Tx, error) { - res, err := call(client, "unsafe/sign_tx", - "tx", wrappedTx{tx}, - "privAccounts", privAccounts) - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultSignTx).Tx, nil -} - -func BroadcastTx(client RPCClient, - tx txs.Tx) (txs.Receipt, error) { - res, err := call(client, "broadcast_tx", - "tx", wrappedTx{tx}) - if err != nil { - return txs.Receipt{}, err - } - receiptBytes := res.(*rpc_types.ResultBroadcastTx).Data - receipt := txs.Receipt{} - err = wire.ReadBinaryBytes(receiptBytes, &receipt) - return receipt, err - -} - -func DumpStorage(client RPCClient, - address []byte) (*rpc_types.ResultDumpStorage, error) { - res, err := call(client, "dump_storage", - "address", address) - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultDumpStorage), err -} - -func GetStorage(client RPCClient, address, key []byte) ([]byte, error) { - res, err := call(client, "get_storage", - "address", address, - "key", key) - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultGetStorage).Value, nil -} - -func CallCode(client RPCClient, fromAddress, code, - data []byte) (*rpc_types.ResultCall, error) { - res, err := call(client, "call_code", - "fromAddress", fromAddress, - "code", code, - "data", data) - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultCall), err -} - -func Call(client RPCClient, fromAddress, toAddress, - data []byte) (*rpc_types.ResultCall, error) { - res, err := call(client, "call", - "fromAddress", fromAddress, - "toAddress", toAddress, - "data", data) - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultCall), err -} - -func GetName(client RPCClient, name string) (*core_types.NameRegEntry, error) { - res, err := call(client, "get_name", - "name", name) - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultGetName).Entry, nil -} - -func BlockchainInfo(client RPCClient, minHeight, - maxHeight int) (*rpc_types.ResultBlockchainInfo, error) { - res, err := call(client, "blockchain", - "minHeight", minHeight, - "maxHeight", maxHeight) - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultBlockchainInfo), err -} - -func GetBlock(client RPCClient, height int) (*rpc_types.ResultGetBlock, error) { - res, err := call(client, "get_block", - "height", height) - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultGetBlock), err -} - -func ListUnconfirmedTxs(client RPCClient) (*rpc_types.ResultListUnconfirmedTxs, error) { - res, err := call(client, "list_unconfirmed_txs") - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultListUnconfirmedTxs), err -} - -func ListValidators(client RPCClient) (*rpc_types.ResultListValidators, error) { - res, err := call(client, "list_validators") - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultListValidators), err -} - -func DumpConsensusState(client RPCClient) (*rpc_types.ResultDumpConsensusState, error) { - res, err := call(client, "dump_consensus_state") - if err != nil { - return nil, err - } - return res.(*rpc_types.ResultDumpConsensusState), err -} - -func call(client RPCClient, method string, - paramKeyVals ...interface{}) (res rpc_types.BurrowResult, err error) { - pMap, err := paramsMap(paramKeyVals...) - if err != nil { - return - } - _, err = client.Call(method, pMap, &res) - return -} - -func paramsMap(orderedKeyVals ...interface{}) (map[string]interface{}, error) { - if len(orderedKeyVals)%2 != 0 { - return nil, fmt.Errorf("mapAndValues requires a even length list of"+ - " keys and values but got: %v (length %v)", - orderedKeyVals, len(orderedKeyVals)) - } - paramsMap := make(map[string]interface{}) - for i := 0; i < len(orderedKeyVals); i += 2 { - key, ok := orderedKeyVals[i].(string) - if !ok { - return nil, errors.New("mapAndValues requires every even element" + - " of orderedKeyVals to be a string key") - } - val := orderedKeyVals[i+1] - paramsMap[key] = val - } - return paramsMap, nil -} - -type wrappedTx struct { - txs.Tx `json:"unwrap"` -} diff --git a/rpc/tendermint/client/client_test.go b/rpc/tendermint/client/client_test.go deleted file mode 100644 index 13a5c98e48e668d53accfb113e91bf06c1ef72d1..0000000000000000000000000000000000000000 --- a/rpc/tendermint/client/client_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package client - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestParamsMap(t *testing.T) { - type aStruct struct { - Baz int - } - dict, err := paramsMap("Foo", aStruct{5}, - "Bar", "Nibbles") - assert.NoError(t, err, "Should not be a paramsMaperror") - assert.Equal(t, map[string]interface{}{ - "Foo": aStruct{5}, - "Bar": "Nibbles", - }, dict) - - // Empty map - dict, err = paramsMap() - assert.Equal(t, map[string]interface{}{}, dict) - assert.NoError(t, err, "Empty mapsAndValues call should be fine") - - // Invalid maps - assert.NoError(t, err, "Empty mapsAndValues call should be fine") - _, err = paramsMap("Foo", 4, "Bar") - assert.Error(t, err, "Should be an error to get an odd number of arguments") - - _, err = paramsMap("Foo", 4, 4, "Bar") - assert.Error(t, err, "Should be an error to provide non-string keys") -} diff --git a/rpc/tendermint/client/websocket_client.go b/rpc/tendermint/client/websocket_client.go deleted file mode 100644 index 4749517b72febf7b62929fe4fa7b18c986567bdb..0000000000000000000000000000000000000000 --- a/rpc/tendermint/client/websocket_client.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package client - -import "github.com/tendermint/go-rpc/types" - -type WebsocketClient interface { - WriteJSON(v interface{}) error -} - -func Subscribe(websocketClient WebsocketClient, eventId string) error { - return websocketClient.WriteJSON(rpctypes.RPCRequest{ - JSONRPC: "2.0", - ID: "", - Method: "subscribe", - Params: map[string]interface{}{"eventId": eventId}, - }) -} - -func Unsubscribe(websocketClient WebsocketClient, subscriptionId string) error { - return websocketClient.WriteJSON(rpctypes.RPCRequest{ - JSONRPC: "2.0", - ID: "", - Method: "unsubscribe", - Params: map[string]interface{}{"subscriptionId": subscriptionId}, - }) -} diff --git a/rpc/tendermint/core/routes.go b/rpc/tendermint/core/routes.go deleted file mode 100644 index b295b3a6de5d12b5b71dccb8b274f35aad7143c3..0000000000000000000000000000000000000000 --- a/rpc/tendermint/core/routes.go +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package core - -import ( - "fmt" - - acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/definitions" - ctypes "github.com/hyperledger/burrow/rpc/tendermint/core/types" - "github.com/hyperledger/burrow/txs" - rpc "github.com/tendermint/go-rpc/server" - rpctypes "github.com/tendermint/go-rpc/types" -) - -// TODO: [ben] encapsulate Routes into a struct for a given TendermintPipe - -// Magic! Should probably be configurable, but not shouldn't be so huge we -// end up DoSing ourselves. -const maxBlockLookback = 20 - -// TODO: eliminate redundancy between here and reading code from core/ -type TendermintRoutes struct { - tendermintPipe definitions.TendermintPipe -} - -func (tmRoutes *TendermintRoutes) GetRoutes() map[string]*rpc.RPCFunc { - var routes = map[string]*rpc.RPCFunc{ - "subscribe": rpc.NewWSRPCFunc(tmRoutes.Subscribe, "eventId"), - "unsubscribe": rpc.NewWSRPCFunc(tmRoutes.Unsubscribe, "subscriptionId"), - "status": rpc.NewRPCFunc(tmRoutes.StatusResult, ""), - "net_info": rpc.NewRPCFunc(tmRoutes.NetInfoResult, ""), - "genesis": rpc.NewRPCFunc(tmRoutes.GenesisResult, ""), - "chain_id": rpc.NewRPCFunc(tmRoutes.ChainIdResult, ""), - "get_account": rpc.NewRPCFunc(tmRoutes.GetAccountResult, "address"), - "get_storage": rpc.NewRPCFunc(tmRoutes.GetStorageResult, "address,key"), - "call": rpc.NewRPCFunc(tmRoutes.CallResult, "fromAddress,toAddress,data"), - "call_code": rpc.NewRPCFunc(tmRoutes.CallCodeResult, "fromAddress,code,data"), - "dump_storage": rpc.NewRPCFunc(tmRoutes.DumpStorageResult, "address"), - "list_accounts": rpc.NewRPCFunc(tmRoutes.ListAccountsResult, ""), - "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_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"), - // TODO: [Silas] do we also carry forward "consensus_state" as in v0? - } - return routes -} - -func (tmRoutes *TendermintRoutes) Subscribe(wsCtx rpctypes.WSRPCContext, - eventId string) (ctypes.BurrowResult, error) { - // NOTE: RPCResponses of subscribed events have id suffix "#event" - // TODO: we really ought to allow multiple subscriptions from the same client address - // to the same event. The code as it stands reflects the somewhat broken tendermint - // implementation. We can use GenerateSubId to randomize the subscriptions id - // and return it in the result. This would require clients to hang on to a - // subscription id if they wish to unsubscribe, but then again they can just - // drop their connection - result, err := tmRoutes.tendermintPipe.Subscribe(eventId, - func(result ctypes.BurrowResult) { - wsCtx.GetRemoteAddr() - // NOTE: EventSwitch callbacks must be nonblocking - wsCtx.TryWriteRPCResponse( - rpctypes.NewRPCResponse(wsCtx.Request.ID+"#event", &result, "")) - }) - if err != nil { - return nil, err - } else { - return result, nil - } -} - -func (tmRoutes *TendermintRoutes) Unsubscribe(wsCtx rpctypes.WSRPCContext, - subscriptionId string) (ctypes.BurrowResult, error) { - result, err := tmRoutes.tendermintPipe.Unsubscribe(subscriptionId) - if err != nil { - return nil, err - } else { - return result, nil - } -} - -func (tmRoutes *TendermintRoutes) StatusResult() (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.Status(); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) NetInfoResult() (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.NetInfo(); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) GenesisResult() (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.Genesis(); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) ChainIdResult() (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.ChainId(); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) GetAccountResult(address []byte) (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.GetAccount(address); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) GetStorageResult(address, key []byte) (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.GetStorage(address, key); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) CallResult(fromAddress, toAddress, - data []byte) (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.Call(fromAddress, toAddress, data); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) CallCodeResult(fromAddress, code, - data []byte) (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.CallCode(fromAddress, code, data); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) DumpStorageResult(address []byte) (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.DumpStorage(address); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) ListAccountsResult() (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.ListAccounts(); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) GetNameResult(name string) (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.GetName(name); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) ListNamesResult() (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.ListNames(); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) GenPrivAccountResult() (ctypes.BurrowResult, error) { - //if r, err := tmRoutes.tendermintPipe.GenPrivAccount(); err != nil { - // return nil, err - //} else { - // return r, nil - //} - return nil, fmt.Errorf("Unimplemented as poor practice to generate private account over unencrypted RPC") -} - -func (tmRoutes *TendermintRoutes) SignTxResult(tx txs.Tx, - privAccounts []*acm.PrivAccount) (ctypes.BurrowResult, error) { - // if r, err := tmRoutes.tendermintPipe.SignTx(tx, privAccounts); err != nil { - // return nil, err - // } else { - // return r, nil - // } - return nil, fmt.Errorf("Unimplemented as poor practice to pass private account over unencrypted RPC") -} - -func (tmRoutes *TendermintRoutes) BroadcastTxResult(tx txs.Tx) (ctypes.BurrowResult, error) { - if r, err := tmRoutes.tendermintPipe.BroadcastTxSync(tx); err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) BlockchainInfo(minHeight, - maxHeight int) (ctypes.BurrowResult, error) { - r, err := tmRoutes.tendermintPipe.BlockchainInfo(minHeight, maxHeight, - maxBlockLookback) - if err != nil { - return nil, err - } else { - return r, nil - } -} - -func (tmRoutes *TendermintRoutes) ListUnconfirmedTxs() (ctypes.BurrowResult, error) { - // Get all Txs for now - r, err := tmRoutes.tendermintPipe.ListUnconfirmedTxs(-1) - if err != nil { - return nil, err - } else { - return r, nil - } -} -func (tmRoutes *TendermintRoutes) GetBlock(height int) (ctypes.BurrowResult, error) { - r, err := tmRoutes.tendermintPipe.GetBlock(height) - if err != nil { - return nil, err - } else { - return r, nil - } -} -func (tmRoutes *TendermintRoutes) ListValidators() (ctypes.BurrowResult, error) { - r, err := tmRoutes.tendermintPipe.ListValidators() - if err != nil { - return nil, err - } else { - return r, nil - } -} -func (tmRoutes *TendermintRoutes) DumpConsensusState() (ctypes.BurrowResult, error) { - return tmRoutes.tendermintPipe.DumpConsensusState() -} diff --git a/rpc/tendermint/core/types/responses.go b/rpc/tendermint/core/types/responses.go deleted file mode 100644 index 33b46d4265e23f7f7831fc2ca74effe45b0a2c15..0000000000000000000000000000000000000000 --- a/rpc/tendermint/core/types/responses.go +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -import ( - acm "github.com/hyperledger/burrow/account" - core_types "github.com/hyperledger/burrow/core/types" - genesis "github.com/hyperledger/burrow/genesis" - "github.com/hyperledger/burrow/txs" - tendermint_types "github.com/tendermint/tendermint/types" - - consensus_types "github.com/hyperledger/burrow/consensus/types" - abcitypes "github.com/tendermint/abci/types" - "github.com/tendermint/go-crypto" - "github.com/tendermint/go-p2p" - "github.com/tendermint/go-rpc/types" - "github.com/tendermint/go-wire" -) - -type ResultGetStorage struct { - Key []byte `json:"key"` - Value []byte `json:"value"` -} - -type ResultCall struct { - Return []byte `json:"return"` - GasUsed int64 `json:"gas_used"` - // TODO ... -} - -type ResultListAccounts struct { - BlockHeight int `json:"block_height"` - Accounts []*acm.Account `json:"accounts"` -} - -type ResultDumpStorage struct { - StorageRoot []byte `json:"storage_root"` - StorageItems []StorageItem `json:"storage_items"` -} - -type StorageItem struct { - Key []byte `json:"key"` - Value []byte `json:"value"` -} - -type ResultBlockchainInfo struct { - LastHeight int `json:"last_height"` - BlockMetas []*tendermint_types.BlockMeta `json:"block_metas"` -} - -type ResultGetBlock struct { - BlockMeta *tendermint_types.BlockMeta `json:"block_meta"` - Block *tendermint_types.Block `json:"block"` -} - -type ResultStatus struct { - NodeInfo *p2p.NodeInfo `json:"node_info"` - GenesisHash []byte `json:"genesis_hash"` - PubKey crypto.PubKey `json:"pub_key"` - LatestBlockHash []byte `json:"latest_block_hash"` - LatestBlockHeight int `json:"latest_block_height"` - LatestBlockTime int64 `json:"latest_block_time"` // nano -} - -type ResultChainId struct { - ChainName string `json:"chain_name"` - ChainId string `json:"chain_id"` - GenesisHash []byte `json:"genesis_hash"` -} - -type ResultSubscribe struct { - Event string `json:"event"` - SubscriptionId string `json:"subscription_id"` -} - -type ResultUnsubscribe struct { - SubscriptionId string `json:"subscription_id"` -} - -type ResultNetInfo struct { - Listening bool `json:"listening"` - Listeners []string `json:"listeners"` - Peers []*consensus_types.Peer `json:"peers"` -} - -type ResultListValidators struct { - BlockHeight int `json:"block_height"` - BondedValidators []consensus_types.Validator `json:"bonded_validators"` - UnbondingValidators []consensus_types.Validator `json:"unbonding_validators"` -} - -type ResultDumpConsensusState struct { - ConsensusState *consensus_types.ConsensusState `json:"consensus_state"` - PeerConsensusStates []*ResultPeerConsensusState `json:"peer_consensus_states"` -} - -type ResultPeerConsensusState struct { - PeerKey string `json:"peer_key"` - PeerConsensusState string `json:"peer_consensus_state"` -} - -type ResultListNames struct { - BlockHeight int `json:"block_height"` - Names []*core_types.NameRegEntry `json:"names"` -} - -type ResultGenPrivAccount struct { - PrivAccount *acm.PrivAccount `json:"priv_account"` -} - -type ResultGetAccount struct { - Account *acm.Account `json:"account"` -} - -type ResultBroadcastTx struct { - Code abcitypes.CodeType `json:"code"` - Data []byte `json:"data"` - Log string `json:"log"` -} - -type ResultListUnconfirmedTxs struct { - N int `json:"n_txs"` - Txs []txs.Tx `json:"txs"` -} - -type ResultGetName struct { - Entry *core_types.NameRegEntry `json:"entry"` -} - -type ResultGenesis struct { - Genesis *genesis.GenesisDoc `json:"genesis"` -} - -type ResultSignTx struct { - Tx txs.Tx `json:"tx"` -} - -type ResultEvent struct { - Event string `json:"event"` - Data txs.EventData `json:"data"` -} - -//---------------------------------------- -// result types - -const ( - ResultTypeGetStorage = byte(0x01) - ResultTypeCall = byte(0x02) - ResultTypeListAccounts = byte(0x03) - ResultTypeDumpStorage = byte(0x04) - ResultTypeBlockchainInfo = byte(0x05) - ResultTypeGetBlock = byte(0x06) - ResultTypeStatus = byte(0x07) - ResultTypeNetInfo = byte(0x08) - ResultTypeListValidators = byte(0x09) - ResultTypeDumpConsensusState = byte(0x0A) - ResultTypeListNames = byte(0x0B) - ResultTypeGenPrivAccount = byte(0x0C) - ResultTypeGetAccount = byte(0x0D) - ResultTypeBroadcastTx = byte(0x0E) - ResultTypeListUnconfirmedTxs = byte(0x0F) - ResultTypeGetName = byte(0x10) - ResultTypeGenesis = byte(0x11) - ResultTypeSignTx = byte(0x12) - ResultTypeEvent = byte(0x13) // so websockets can respond to rpc functions - ResultTypeSubscribe = byte(0x14) - ResultTypeUnsubscribe = byte(0x15) - ResultTypePeerConsensusState = byte(0x16) - ResultTypeChainId = byte(0x17) -) - -type BurrowResult interface { - rpctypes.Result -} - -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}, - {&ResultDumpConsensusState{}, ResultTypePeerConsensusState}, - {&ResultListNames{}, ResultTypeListNames}, - {&ResultGenPrivAccount{}, ResultTypeGenPrivAccount}, - {&ResultGetAccount{}, ResultTypeGetAccount}, - {&ResultBroadcastTx{}, ResultTypeBroadcastTx}, - {&ResultListUnconfirmedTxs{}, ResultTypeListUnconfirmedTxs}, - {&ResultGetName{}, ResultTypeGetName}, - {&ResultGenesis{}, ResultTypeGenesis}, - {&ResultSignTx{}, ResultTypeSignTx}, - {&ResultEvent{}, ResultTypeEvent}, - {&ResultSubscribe{}, ResultTypeSubscribe}, - {&ResultUnsubscribe{}, ResultTypeUnsubscribe}, - {&ResultChainId{}, ResultTypeChainId}, - } -} - -var _ = wire.RegisterInterface(struct{ BurrowResult }{}, ConcreteTypes()...) diff --git a/rpc/tendermint/core/types/responses_test.go b/rpc/tendermint/core/types/responses_test.go deleted file mode 100644 index c745017c8d74a8bc9d695667c5dec01a1abbb983..0000000000000000000000000000000000000000 --- a/rpc/tendermint/core/types/responses_test.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -import ( - "testing" - - "time" - - consensus_types "github.com/hyperledger/burrow/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/core/types/responses_util.go b/rpc/tendermint/core/types/responses_util.go deleted file mode 100644 index cd3e15e8c6675dd92aaf19610bc6f52608c87e8c..0000000000000000000000000000000000000000 --- a/rpc/tendermint/core/types/responses_util.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -import ( - "github.com/hyperledger/burrow/core/types" -) - -// UnwrapResultDumpStorage does a deep copy to remove /rpc/tendermint/core/types -// and expose /core/types instead. This is largely an artefact to be removed once -// go-wire and go-rpc are deprecated. -// This is not an efficient code, especially given Storage can be big. -func UnwrapResultDumpStorage(result *ResultDumpStorage) *types.Storage { - storageRoot := make([]byte, len(result.StorageRoot)) - copy(storageRoot, result.StorageRoot) - storageItems := make([]types.StorageItem, len(result.StorageItems)) - for i, item := range result.StorageItems { - key := make([]byte, len(item.Key)) - value := make([]byte, len(item.Value)) - copy(key, item.Key) - copy(value, item.Value) - storageItems[i] = types.StorageItem{ - Key: key, - Value: value, - } - } - return &types.Storage{ - StorageRoot: storageRoot, - StorageItems: storageItems, - } -} diff --git a/rpc/tendermint/core/websocket.go b/rpc/tendermint/core/websocket.go deleted file mode 100644 index 2a4feffe1ff25787d298a4e51c2cd6232782e7ad..0000000000000000000000000000000000000000 --- a/rpc/tendermint/core/websocket.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package core - -import ( - "fmt" - "net" - "net/http" - "strings" - - events "github.com/tendermint/go-events" - rpcserver "github.com/tendermint/go-rpc/server" - - definitions "github.com/hyperledger/burrow/definitions" - server "github.com/hyperledger/burrow/server" -) - -type TendermintWebsocketServer struct { - routes TendermintRoutes - listeners []net.Listener -} - -func NewTendermintWebsocketServer(config *server.ServerConfig, - tendermintPipe definitions.TendermintPipe, evsw events.EventSwitch) ( - *TendermintWebsocketServer, error) { - - if tendermintPipe == nil { - return nil, fmt.Errorf("No Tendermint pipe provided.") - } - tendermintRoutes := TendermintRoutes{ - tendermintPipe: tendermintPipe, - } - routes := tendermintRoutes.GetRoutes() - listenerAddresses := strings.Split(config.Tendermint.RpcLocalAddress, ",") - if len(listenerAddresses) == 0 { - return nil, fmt.Errorf("No RPC listening addresses provided in [servers.tendermint.rpc_local_address] in configuration file: %s", - listenerAddresses) - } - listeners := make([]net.Listener, len(listenerAddresses)) - for i, listenerAddress := range listenerAddresses { - mux := http.NewServeMux() - wm := rpcserver.NewWebsocketManager(routes, evsw) - mux.HandleFunc(config.Tendermint.Endpoint, wm.WebsocketHandler) - rpcserver.RegisterRPCFuncs(mux, routes) - listener, err := rpcserver.StartHTTPServer(listenerAddress, mux) - if err != nil { - return nil, err - } - listeners[i] = listener - } - return &TendermintWebsocketServer{ - routes: tendermintRoutes, - listeners: listeners, - }, nil -} - -func (tmServer *TendermintWebsocketServer) Shutdown() { - for _, listener := range tmServer.listeners { - listener.Close() - } -} diff --git a/rpc/tendermint/test/runner/main.go b/rpc/tendermint/test/runner/main.go deleted file mode 100644 index e17ee5b8184791e65df67ec8a0ccceac216a0a29..0000000000000000000000000000000000000000 --- a/rpc/tendermint/test/runner/main.go +++ /dev/null @@ -1,31 +0,0 @@ -// +build integration - -// Space above here matters -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "fmt" - - rpctest "github.com/hyperledger/burrow/rpc/tendermint/test" - "github.com/hyperledger/burrow/util" -) - -func main() { - //fmt.Printf("%s", util.IsAddress("hello")) - fmt.Printf("%s", util.IsAddress("hello"), rpctest.Successor(2)) - //defer os.Exit(0) -} diff --git a/rpc/tendermint/test/shared.go b/rpc/tendermint/test/shared.go deleted file mode 100644 index 4ca904ddea2817bcffe61c0e16f5e11966c85692..0000000000000000000000000000000000000000 --- a/rpc/tendermint/test/shared.go +++ /dev/null @@ -1,432 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package test - -import ( - "bytes" - "errors" - "fmt" - "hash/fnv" - "path" - "strconv" - "testing" - - "time" - - acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/config" - "github.com/hyperledger/burrow/core" - core_types "github.com/hyperledger/burrow/core/types" - genesis "github.com/hyperledger/burrow/genesis" - "github.com/hyperledger/burrow/logging/lifecycle" - "github.com/hyperledger/burrow/manager/burrow-mint/evm" - ptypes "github.com/hyperledger/burrow/permission/types" - "github.com/hyperledger/burrow/rpc/tendermint/client" - edbcli "github.com/hyperledger/burrow/rpc/tendermint/client" - rpc_types "github.com/hyperledger/burrow/rpc/tendermint/core/types" - "github.com/hyperledger/burrow/server" - "github.com/hyperledger/burrow/test/fixtures" - "github.com/hyperledger/burrow/txs" - "github.com/hyperledger/burrow/word256" - "github.com/spf13/viper" - "github.com/tendermint/go-crypto" - rpcclient "github.com/tendermint/go-rpc/client" - "github.com/tendermint/tendermint/types" -) - -const chainID = "RPC_Test_Chain" - -// global variables for use across all tests -var ( - serverConfig *server.ServerConfig - rootWorkDir string - mempoolCount = 0 - websocketAddr string - genesisDoc *genesis.GenesisDoc - websocketEndpoint string - users = makeUsers(5) // make keys - jsonRpcClient client.RPCClient - httpClient client.RPCClient - clients map[string]client.RPCClient - testCore *core.Core -) - -// We use this to wrap tests -func TestWrapper(runner func() int) int { - fmt.Println("Running with integration TestWrapper (rpc/tendermint/test/shared_test.go)...") - ffs := fixtures.NewFileFixtures("burrow") - - defer func() { - // Tendermint likes to try and save to priv_validator.json after its been - // asked to shutdown so we pause to try and avoid collision - time.Sleep(time.Second) - ffs.RemoveAll() - }() - - vm.SetDebug(true) - err := initGlobalVariables(ffs) - - if err != nil { - panic(err) - } - - tmServer, err := testCore.NewGatewayTendermint(serverConfig) - defer func() { - // Shutdown -- make sure we don't hit a race on ffs.RemoveAll - tmServer.Shutdown() - testCore.Stop() - }() - - if err != nil { - panic(err) - } - - return runner() -} - -// initialize config and create new node -func initGlobalVariables(ffs *fixtures.FileFixtures) error { - configBytes, err := config.GetConfigurationFileBytes(chainID, - "test_single_node", - "", - "burrow", - true, - "46657", - "burrow serve") - if err != nil { - return err - } - - genesisBytes, err := genesisFileBytesFromUsers(chainID, users) - if err != nil { - return err - } - - testConfigFile := ffs.AddFile("config.toml", string(configBytes)) - rootWorkDir = ffs.AddDir("rootWorkDir") - rootDataDir := ffs.AddDir("rootDataDir") - genesisFile := ffs.AddFile("rootWorkDir/genesis.json", string(genesisBytes)) - genesisDoc = genesis.GenesisDocFromJSON(genesisBytes) - - if ffs.Error != nil { - return ffs.Error - } - - testConfig := viper.New() - testConfig.SetConfigFile(testConfigFile) - err = testConfig.ReadInConfig() - - if err != nil { - return err - } - - sconf, err := core.LoadServerConfig(chainID, testConfig) - if err != nil { - return err - } - serverConfig = sconf - - rpcAddr := serverConfig.Tendermint.RpcLocalAddress - websocketAddr = rpcAddr - websocketEndpoint = "/websocket" - - consensusConfig, err := core.LoadModuleConfig(testConfig, rootWorkDir, - rootDataDir, genesisFile, chainID, "consensus") - if err != nil { - return err - } - - managerConfig, err := core.LoadModuleConfig(testConfig, rootWorkDir, - rootDataDir, genesisFile, chainID, "manager") - if err != nil { - return err - } - - // Set up priv_validator.json before we start tendermint (otherwise it will - // create its own one. - saveNewPriv() - logger, _ := lifecycle.NewStdErrLogger() - // To spill tendermint logs on the floor: - // lifecycle.CaptureTendermintLog15Output(loggers.NewNoopInfoTraceLogger()) - lifecycle.CaptureTendermintLog15Output(logger) - lifecycle.CaptureStdlibLogOutput(logger) - - testCore, err = core.NewCore("testCore", consensusConfig, managerConfig, - logger) - if err != nil { - return err - } - - jsonRpcClient = rpcclient.NewJSONRPCClient(rpcAddr) - httpClient = rpcclient.NewURIClient(rpcAddr) - - clients = map[string]client.RPCClient{ - "JSONRPC": jsonRpcClient, - "HTTP": httpClient, - } - return nil -} - -// Deterministic account generation helper. Pass number of accounts to make -func makeUsers(n int) []*acm.PrivAccount { - accounts := []*acm.PrivAccount{} - for i := 0; i < n; i++ { - secret := "mysecret" + strconv.Itoa(i) - user := acm.GenPrivAccountFromSecret(secret) - accounts = append(accounts, user) - } - return accounts -} - -func genesisFileBytesFromUsers(chainName string, accounts []*acm.PrivAccount) ([]byte, error) { - if len(accounts) < 1 { - return nil, errors.New("Please pass in at least 1 account to be the validator") - } - genesisValidators := make([]*genesis.GenesisValidator, 1) - genesisAccounts := make([]*genesis.GenesisAccount, len(accounts)) - genesisValidators[0] = genesisValidatorFromPrivAccount(accounts[0]) - - for i, acc := range accounts { - genesisAccounts[i] = genesisAccountFromPrivAccount(acc) - } - - return genesis.GenerateGenesisFileBytes(chainName, genesisAccounts, genesisValidators) -} - -func genesisValidatorFromPrivAccount(account *acm.PrivAccount) *genesis.GenesisValidator { - return &genesis.GenesisValidator{ - Amount: 1000000, - Name: fmt.Sprintf("full-account_%X", account.Address), - PubKey: account.PubKey, - UnbondTo: []genesis.BasicAccount{ - { - Address: account.Address, - Amount: 100, - }, - }, - } -} - -func genesisAccountFromPrivAccount(account *acm.PrivAccount) *genesis.GenesisAccount { - return genesis.NewGenesisAccount(account.Address, 100000, - fmt.Sprintf("account_%X", account.Address), &ptypes.DefaultAccountPermissions) -} - -func saveNewPriv() { - // Save new priv_validator file. - priv := &types.PrivValidator{ - Address: users[0].Address, - PubKey: crypto.PubKeyEd25519(users[0].PubKey.(crypto.PubKeyEd25519)), - PrivKey: crypto.PrivKeyEd25519(users[0].PrivKey.(crypto.PrivKeyEd25519)), - } - priv.SetFile(path.Join(rootWorkDir, "priv_validator.json")) - priv.Save() -} - -//------------------------------------------------------------------------------- -// some default transaction functions - -func makeDefaultSendTx(t *testing.T, client client.RPCClient, addr []byte, - amt int64) *txs.SendTx { - nonce := getNonce(t, client, users[0].Address) - tx := txs.NewSendTx() - tx.AddInputWithNonce(users[0].PubKey, amt, nonce+1) - tx.AddOutput(addr, amt) - return tx -} - -func makeDefaultSendTxSigned(t *testing.T, client client.RPCClient, addr []byte, - amt int64) *txs.SendTx { - tx := makeDefaultSendTx(t, client, addr, amt) - tx.SignInput(chainID, 0, users[0]) - return tx -} - -func makeDefaultCallTx(t *testing.T, client client.RPCClient, addr, code []byte, amt, gasLim, - fee int64) *txs.CallTx { - nonce := getNonce(t, client, users[0].Address) - tx := txs.NewCallTxWithNonce(users[0].PubKey, addr, code, amt, gasLim, fee, - nonce+1) - tx.Sign(chainID, users[0]) - return tx -} - -func makeDefaultNameTx(t *testing.T, client client.RPCClient, name, value string, amt, - fee int64) *txs.NameTx { - nonce := getNonce(t, client, users[0].Address) - tx := txs.NewNameTxWithNonce(users[0].PubKey, name, value, amt, fee, nonce+1) - tx.Sign(chainID, users[0]) - return tx -} - -//------------------------------------------------------------------------------- -// rpc call wrappers (fail on err) - -// get an account's nonce -func getNonce(t *testing.T, client client.RPCClient, addr []byte) int { - ac, err := edbcli.GetAccount(client, addr) - if err != nil { - t.Fatal(err) - } - if ac == nil { - return 0 - } - return ac.Sequence -} - -// get the account -func getAccount(t *testing.T, client client.RPCClient, addr []byte) *acm.Account { - ac, err := edbcli.GetAccount(client, addr) - if err != nil { - t.Fatal(err) - } - return ac -} - -// sign transaction -func signTx(t *testing.T, client client.RPCClient, tx txs.Tx, - privAcc *acm.PrivAccount) txs.Tx { - signedTx, err := edbcli.SignTx(client, tx, []*acm.PrivAccount{privAcc}) - if err != nil { - t.Fatal(err) - } - return signedTx -} - -// broadcast transaction -func broadcastTx(t *testing.T, client client.RPCClient, tx txs.Tx) txs.Receipt { - rec, err := edbcli.BroadcastTx(client, tx) - if err != nil { - t.Fatal(err) - } - mempoolCount += 1 - return rec -} - -// dump all storage for an account. currently unused -func dumpStorage(t *testing.T, addr []byte) *rpc_types.ResultDumpStorage { - client := clients["HTTP"] - resp, err := edbcli.DumpStorage(client, addr) - if err != nil { - t.Fatal(err) - } - return resp -} - -func getStorage(t *testing.T, client client.RPCClient, addr, key []byte) []byte { - resp, err := edbcli.GetStorage(client, addr, key) - if err != nil { - t.Fatal(err) - } - return resp -} - -func callCode(t *testing.T, client client.RPCClient, fromAddress, code, data, - expected []byte) { - resp, err := edbcli.CallCode(client, fromAddress, code, data) - if err != nil { - t.Fatal(err) - } - ret := resp.Return - // NOTE: we don't flip memory when it comes out of RETURN (?!) - if bytes.Compare(ret, word256.LeftPadWord256(expected).Bytes()) != 0 { - t.Fatalf("Conflicting return value. Got %x, expected %x", ret, expected) - } -} - -func callContract(t *testing.T, client client.RPCClient, fromAddress, toAddress, - data, expected []byte) { - resp, err := edbcli.Call(client, fromAddress, toAddress, data) - if err != nil { - t.Fatal(err) - } - ret := resp.Return - // NOTE: we don't flip memory when it comes out of RETURN (?!) - if bytes.Compare(ret, word256.LeftPadWord256(expected).Bytes()) != 0 { - t.Fatalf("Conflicting return value. Got %x, expected %x", ret, expected) - } -} - -// get the namereg entry -func getNameRegEntry(t *testing.T, client client.RPCClient, name string) *core_types.NameRegEntry { - entry, err := edbcli.GetName(client, name) - if err != nil { - t.Fatal(err) - } - return entry -} - -// 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 - contractCode := []byte{0x60, 0x5, 0x60, 0x6, 0x1, 0x60, 0x0, 0x52, 0x60, 0x20, - 0x60, 0x0, 0xf3} - // the is the code we need to return the contractCode when the contract is initialized - lenCode := len(contractCode) - // push code to the stack - //code := append([]byte{byte(0x60 + lenCode - 1)}, RightPadWord256(contractCode).Bytes()...) - code := append([]byte{0x7f}, - word256.RightPadWord256(contractCode).Bytes()...) - // store it in memory - code = append(code, []byte{0x60, 0x0, 0x52}...) - // return whats in memory - //code = append(code, []byte{0x60, byte(32 - lenCode), 0x60, byte(lenCode), 0xf3}...) - code = append(code, []byte{0x60, byte(lenCode), 0x60, 0x0, 0xf3}...) - // return init code, contract code, expected return - return code, contractCode, word256.LeftPadBytes([]byte{0xb}, 32) -} - -// simple call contract calls another contract -func simpleCallContract(addr []byte) ([]byte, []byte, []byte) { - gas1, gas2 := byte(0x1), byte(0x1) - value := byte(0x1) - inOff, inSize := byte(0x0), byte(0x0) // no call data - retOff, retSize := byte(0x0), byte(0x20) - // this is the code we want to run (call a contract and return) - contractCode := []byte{0x60, retSize, 0x60, retOff, 0x60, inSize, 0x60, inOff, - 0x60, value, 0x73} - contractCode = append(contractCode, addr...) - contractCode = append(contractCode, []byte{0x61, gas1, gas2, 0xf1, 0x60, 0x20, - 0x60, 0x0, 0xf3}...) - - // the is the code we need to return; the contractCode when the contract is initialized - // it should copy the code from the input into memory - lenCode := len(contractCode) - memOff := byte(0x0) - inOff = byte(0xc) // length of code before codeContract - length := byte(lenCode) - - code := []byte{0x60, length, 0x60, inOff, 0x60, memOff, 0x37} - // return whats in memory - code = append(code, []byte{0x60, byte(lenCode), 0x60, 0x0, 0xf3}...) - code = append(code, contractCode...) - // return init code, contract code, expected return - return code, contractCode, word256.LeftPadBytes([]byte{0xb}, 32) -} diff --git a/rpc/tm/client/client.go b/rpc/tm/client/client.go new file mode 100644 index 0000000000000000000000000000000000000000..f1654899e0baf27e0189fb644d0e33991370d4bb --- /dev/null +++ b/rpc/tm/client/client.go @@ -0,0 +1,207 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "errors" + "fmt" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/execution" + "github.com/hyperledger/burrow/rpc" + "github.com/hyperledger/burrow/rpc/tm/method" + "github.com/hyperledger/burrow/txs" +) + +type RPCClient interface { + Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) +} + +func Status(client RPCClient) (*rpc.ResultStatus, error) { + res := new(rpc.Result) + _, err := client.Call(method.Status, pmap(), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultStatus), nil +} + +func ChainId(client RPCClient) (*rpc.ResultChainId, error) { + res := new(rpc.Result) + _, err := client.Call(method.ChainID, pmap(), &res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultChainId), nil +} + +func GenPrivAccount(client RPCClient) (*rpc.ResultGeneratePrivateAccount, error) { + res := new(rpc.Result) + _, err := client.Call(method.GeneratePrivateAccount, pmap(), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultGeneratePrivateAccount), nil +} + +func GetAccount(client RPCClient, address acm.Address) (acm.Account, error) { + res := new(rpc.Result) + _, err := client.Call(method.GetAccount, pmap("address", address), res) + if err != nil { + return nil, err + } + concreteAccount := res.Unwrap().(*rpc.ResultGetAccount).Account + if concreteAccount == nil { + return nil, nil + } + return concreteAccount.Account(), nil +} + +func SignTx(client RPCClient, tx txs.Tx, privAccounts []*acm.ConcretePrivateAccount) (txs.Tx, error) { + res := new(rpc.Result) + _, err := client.Call(method.SignTx, pmap("tx", tx, "privAccounts", privAccounts), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultSignTx).Tx, nil +} + +func BroadcastTx(client RPCClient, tx txs.Tx) (*txs.Receipt, error) { + res := new(rpc.Result) + _, err := client.Call(method.BroadcastTx, pmap("tx", txs.Wrap(tx)), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultBroadcastTx).Receipt, nil +} + +func DumpStorage(client RPCClient, address acm.Address) (*rpc.ResultDumpStorage, error) { + res := new(rpc.Result) + _, err := client.Call(method.DumpStorage, pmap("address", address), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultDumpStorage), nil +} + +func GetStorage(client RPCClient, address acm.Address, key []byte) ([]byte, error) { + res := new(rpc.Result) + _, err := client.Call(method.GetStorage, pmap("address", address, "key", key), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultGetStorage).Value, nil +} + +func CallCode(client RPCClient, fromAddress acm.Address, code, data []byte) (*rpc.ResultCall, error) { + res := new(rpc.Result) + _, err := client.Call(method.CallCode, pmap("fromAddress", fromAddress, "code", code, "data", data), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultCall), nil +} + +func Call(client RPCClient, fromAddress, toAddress acm.Address, data []byte) (*rpc.ResultCall, error) { + res := new(rpc.Result) + _, err := client.Call(method.Call, pmap("fromAddress", fromAddress, "toAddress", toAddress, + "data", data), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultCall), nil +} + +func GetName(client RPCClient, name string) (*execution.NameRegEntry, error) { + res := new(rpc.Result) + _, err := client.Call(method.GetName, pmap("name", name), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultGetName).Entry, nil +} + +func BlockchainInfo(client RPCClient, minHeight, maxHeight int) (*rpc.ResultBlockchainInfo, error) { + res := new(rpc.Result) + _, err := client.Call(method.Blockchain, pmap("minHeight", minHeight, "maxHeight", maxHeight), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultBlockchainInfo), nil +} + +func GetBlock(client RPCClient, height int) (*rpc.ResultGetBlock, error) { + res := new(rpc.Result) + _, err := client.Call(method.GetBlock, pmap("height", height), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultGetBlock), nil +} + +func ListUnconfirmedTxs(client RPCClient, maxTxs int) (*rpc.ResultListUnconfirmedTxs, error) { + res := new(rpc.Result) + _, err := client.Call(method.ListUnconfirmedTxs, pmap("maxTxs", maxTxs), res) + if err != nil { + return nil, err + } + resCon := res.Unwrap().(*rpc.ResultListUnconfirmedTxs) + return resCon, nil +} + +func ListValidators(client RPCClient) (*rpc.ResultListValidators, error) { + res := new(rpc.Result) + _, err := client.Call(method.ListValidators, pmap(), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultListValidators), nil +} + +func DumpConsensusState(client RPCClient) (*rpc.ResultDumpConsensusState, error) { + res := new(rpc.Result) + _, err := client.Call(method.DumpConsensusState, pmap(), res) + if err != nil { + return nil, err + } + return res.Unwrap().(*rpc.ResultDumpConsensusState), nil +} + +func pmap(keyvals ...interface{}) map[string]interface{} { + pm, err := paramsMap(keyvals...) + if err != nil { + panic(err) + } + return pm +} + +func paramsMap(orderedKeyVals ...interface{}) (map[string]interface{}, error) { + if len(orderedKeyVals)%2 != 0 { + return nil, fmt.Errorf("mapAndValues requires a even length list of"+ + " keys and values but got: %v (length %v)", + orderedKeyVals, len(orderedKeyVals)) + } + paramsMap := make(map[string]interface{}) + for i := 0; i < len(orderedKeyVals); i += 2 { + key, ok := orderedKeyVals[i].(string) + if !ok { + return nil, errors.New("mapAndValues requires every even element" + + " of orderedKeyVals to be a string key") + } + val := orderedKeyVals[i+1] + paramsMap[key] = val + } + return paramsMap, nil +} diff --git a/rpc/tendermint/test/rpc_client_test.go b/rpc/tm/client/client_test.go similarity index 66% rename from rpc/tendermint/test/rpc_client_test.go rename to rpc/tm/client/client_test.go index 3f7da59afb790e358e2d0dda423ae7e2291067cc..4140a21531bce1e39af38662784a4e5c44150e8a 100644 --- a/rpc/tendermint/test/rpc_client_test.go +++ b/rpc/tm/client/client_test.go @@ -1,6 +1,3 @@ -// +build integration - -// Space above here matters // Copyright 2017 Monax Industries Limited // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,23 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -package test +package client import ( "bytes" - "fmt" "testing" "time" - "golang.org/x/crypto/ripemd160" - - consensus_types "github.com/hyperledger/burrow/consensus/types" - burrow_client "github.com/hyperledger/burrow/rpc/tendermint/client" + "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/event" + exe_events "github.com/hyperledger/burrow/execution/events" "github.com/hyperledger/burrow/txs" - "github.com/hyperledger/burrow/word256" - "github.com/stretchr/testify/assert" - _ "github.com/tendermint/tendermint/config/tendermint_test" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/consensus/types" + tm_types "github.com/tendermint/tendermint/types" + "golang.org/x/crypto/ripemd160" ) // When run with `-test.short` we only run: @@ -40,8 +36,7 @@ import ( // 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, burrow_client.RPCClient)) { +func testWithAllClients(t *testing.T, testFunction func(*testing.T, string, RPCClient)) { for clientName, client := range clients { testFunction(t, clientName, client) } @@ -49,28 +44,26 @@ func testWithAllClients(t *testing.T, //-------------------------------------------------------------------------------- func TestStatus(t *testing.T) { - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { - resp, err := burrow_client.Status(client) + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { + resp, err := Status(client) assert.NoError(t, err) - fmt.Println(resp) - if resp.NodeInfo.Network != chainID { - t.Fatal(fmt.Errorf("ChainID mismatch: got %s expected %s", - resp.NodeInfo.Network, chainID)) - } + assert.Equal(t, genesisDoc.ChainID(), resp.NodeInfo.Network, "ChainID should match NodeInfo.Network") }) } func TestBroadcastTx(t *testing.T) { wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { // Avoid duplicate Tx in mempool amt := hashString(clientName) % 1000 - toAddr := users[1].Address + toAddr := privateAccounts[1].Address() tx := makeDefaultSendTxSigned(t, client, toAddr, amt) receipt, err := broadcastTxAndWaitForBlock(t, client, wsc, tx) - assert.NoError(t, err) - if receipt.CreatesContract > 0 { - t.Fatal("This tx does not create a contract") + if err != nil { + t.Fatal(err) + } + if receipt.CreatesContract { + t.Fatal("This tx should not create a contract") } if len(receipt.TxHash) == 0 { t.Fatal("Failed to compute tx hash") @@ -78,7 +71,7 @@ func TestBroadcastTx(t *testing.T) { n, errp := new(int), new(error) buf := new(bytes.Buffer) hasher := ripemd160.New() - tx.WriteSignBytes(chainID, buf, n, errp) + tx.WriteSignBytes(genesisDoc.ChainID(), buf, n, errp) assert.NoError(t, *errp) txSignBytes := buf.Bytes() hasher.Write(txSignBytes) @@ -95,14 +88,14 @@ func TestGetAccount(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { - acc := getAccount(t, client, users[0].Address) + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { + acc := getAccount(t, client, privateAccounts[0].Address()) if acc == nil { t.Fatal("Account was nil") } - if bytes.Compare(acc.Address, users[0].Address) != 0 { - t.Fatalf("Failed to get correct account. Got %x, expected %x", acc.Address, - users[0].Address) + if acc.Address() != privateAccounts[0].Address() { + t.Fatalf("Failed to get correct account. Got %s, expected %s", acc.Address(), + privateAccounts[0].Address()) } }) } @@ -112,23 +105,21 @@ func TestGetStorage(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - defer func() { - wsc.Stop() - }() - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { - eid := txs.EventStringNewBlock() + defer stopWSClient(wsc) + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { + eid := tm_types.EventStringNewBlock() subscribe(t, wsc, eid) defer func() { unsubscribe(t, wsc, eid) }() - amt, gasLim, fee := int64(1100), int64(1000), int64(1000) + amt, gasLim, fee := uint64(1100), uint64(1000), uint64(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) assert.NoError(t, err) - assert.Equal(t, uint8(1), receipt.CreatesContract, "This transaction should"+ + assert.Equal(t, true, receipt.CreatesContract, "This transaction should"+ " create a contract") assert.NotEqual(t, 0, len(receipt.TxHash), "Receipt should contain a"+ " transaction hash") @@ -137,8 +128,8 @@ func TestGetStorage(t *testing.T) { " created a contract but the contract address is empty") v := getStorage(t, client, contractAddr, []byte{0x1}) - got := word256.LeftPadWord256(v) - expected := word256.LeftPadWord256([]byte{0x5}) + got := binary.LeftPadWord256(v) + expected := binary.LeftPadWord256([]byte{0x5}) if got.Compare(expected) != 0 { t.Fatalf("Wrong storage value. Got %x, expected %x", got.Bytes(), expected.Bytes()) @@ -151,21 +142,21 @@ func TestCallCode(t *testing.T) { t.Skip("skipping test in short mode.") } - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { // 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, users[0].PubKey.Address(), code, data, expected) + callCode(t, client, privateAccounts[0].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(word256.LeftPadWord256([]byte{0x5}).Bytes(), - word256.LeftPadWord256([]byte{0x6}).Bytes()...) + data = append(binary.LeftPadWord256([]byte{0x5}).Bytes(), + binary.LeftPadWord256([]byte{0x6}).Bytes()...) expected = []byte{0xb} - callCode(t, client, users[0].PubKey.Address(), code, data, expected) + callCode(t, client, privateAccounts[0].Address(), code, data, expected) }) } @@ -174,18 +165,16 @@ func TestCallContract(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - defer func() { - wsc.Stop() - }() - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { - eid := txs.EventStringNewBlock() + defer stopWSClient(wsc) + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { + eid := tm_types.EventStringNewBlock() subscribe(t, wsc, eid) defer func() { unsubscribe(t, wsc, eid) }() // create the contract - amt, gasLim, fee := int64(6969), int64(1000), int64(1000) + amt, gasLim, fee := uint64(6969), uint64(1000), uint64(1000) code, _, _ := simpleContract() tx := makeDefaultCallTx(t, client, nil, code, amt, gasLim, fee) receipt, err := broadcastTxAndWaitForBlock(t, client, wsc, tx) @@ -193,7 +182,7 @@ func TestCallContract(t *testing.T) { if err != nil { t.Fatalf("Problem broadcasting transaction: %v", err) } - assert.Equal(t, uint8(1), receipt.CreatesContract, "This transaction should"+ + assert.Equal(t, true, receipt.CreatesContract, "This transaction should"+ " create a contract") assert.NotEqual(t, 0, len(receipt.TxHash), "Receipt should contain a"+ " transaction hash") @@ -204,7 +193,7 @@ func TestCallContract(t *testing.T) { // run a call through the contract data := []byte{} expected := []byte{0xb} - callContract(t, client, users[0].PubKey.Address(), contractAddr, data, expected) + callContract(t, client, privateAccounts[0].Address(), contractAddr, data, expected) }) } @@ -213,26 +202,27 @@ func TestNameReg(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { - + defer stopWSClient(wsc) + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { 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) + fee := uint64(1000) + numDesiredBlocks := uint64(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), + subscribeAndWaitForNext(t, wsc, exe_events.EventStringNameReg(name), func() { broadcastTxAndWaitForBlock(t, client, wsc, tx) }, - func(eid string, eventData txs.EventData) (bool, error) { - eventDataTx := asEventDataTx(t, eventData) + func(eid string, eventData event.AnyEventData) (bool, error) { + eventDataTx := eventData.EventDataTx() + assert.NotNil(t, eventDataTx, "could not convert %s to EventDataTx", eventData) tx, ok := eventDataTx.Tx.(*txs.NameTx) if !ok { t.Fatalf("Could not convert %v to *NameTx", eventDataTx) @@ -241,14 +231,13 @@ func TestNameReg(t *testing.T) { 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, users[0].Address, entry.Owner) + assert.Equal(t, privateAccounts[0].Address(), entry.Owner) // update the data as the owner, make sure still there - numDesiredBlocks = int64(5) + numDesiredBlocks = uint64(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" @@ -256,15 +245,14 @@ func TestNameReg(t *testing.T) { 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(users[1].PubKey, name, "never mind", amt, fee, - getNonce(t, client, users[1].Address)+1) - tx.Sign(chainID, users[1]) + tx = txs.NewNameTxWithNonce(privateAccounts[1].PublicKey(), name, "never mind", amt, fee, + getNonce(t, client, privateAccounts[1].Address())+1) + tx.Sign(genesisDoc.ChainID(), privateAccounts[1]) _, err := broadcastTxAndWaitForBlock(t, client, wsc, tx) assert.Error(t, err, "Expected error when updating someone else's unexpired"+ @@ -279,37 +267,44 @@ func TestNameReg(t *testing.T) { //now the entry should be expired, so we can update as non owner const data2 = "this is not my beautiful house" - tx = txs.NewNameTxWithNonce(users[1].PubKey, name, data2, amt, fee, - getNonce(t, client, users[1].Address)+1) - tx.Sign(chainID, users[1]) + tx = txs.NewNameTxWithNonce(privateAccounts[1].PublicKey(), name, data2, amt, fee, + getNonce(t, client, privateAccounts[1].Address())+1) + tx.Sign(genesisDoc.ChainID(), privateAccounts[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, users[1].Address, entry.Owner) + assert.Equal(t, privateAccounts[1].Address(), entry.Owner) }) } +func TestWaitBlocks(t *testing.T) { + wsc := newWSClient() + defer stopWSClient(wsc) + waitNBlocks(t, wsc, 5) +} + func TestBlockchainInfo(t *testing.T) { wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { + defer stopWSClient(wsc) + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { // wait a mimimal number of blocks to ensure that the later query for block // headers has a non-trivial length nBlocks := 4 waitNBlocks(t, wsc, nBlocks) - resp, err := burrow_client.BlockchainInfo(client, 0, 0) + resp, err := BlockchainInfo(client, 0, 0) if err != nil { t.Fatalf("Failed to get blockchain info: %v", err) } lastBlockHeight := resp.LastHeight nMetaBlocks := len(resp.BlockMetas) - assert.True(t, nMetaBlocks <= lastBlockHeight, + assert.True(t, uint64(nMetaBlocks) <= lastBlockHeight, "Logically number of block metas should be equal or less than block height.") assert.True(t, nBlocks <= len(resp.BlockMetas), - "Should see at least 4 BlockMetas after waiting for 4 blocks") + "Should see at least %v BlockMetas after waiting for %v blocks but saw %v", + nBlocks, nBlocks, len(resp.BlockMetas)) // For the maximum number (default to 20) of retrieved block headers, // check that they correctly chain to each other. lastBlockHash := resp.BlockMetas[nMetaBlocks-1].Header.Hash() @@ -323,7 +318,7 @@ func TestBlockchainInfo(t *testing.T) { // Now retrieve only two blockheaders (h=1, and h=2) and check that we got // two results. - resp, err = burrow_client.BlockchainInfo(client, 1, 2) + resp, err = BlockchainInfo(client, 1, 2) assert.NoError(t, err) assert.Equal(t, 2, len(resp.BlockMetas), "Should see 2 BlockMetas after extracting 2 blocks") @@ -335,12 +330,13 @@ func TestListUnconfirmedTxs(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { - amt, gasLim, fee := int64(1100), int64(1000), int64(1000) + defer stopWSClient(wsc) + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { + amt, gasLim, fee := uint64(1100), uint64(1000), uint64(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) + tx := txs.Wrap(makeDefaultCallTx(t, client, nil, code, amt, gasLim, fee)) + txChan := make(chan []txs.Wrapper) // 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 @@ -351,8 +347,11 @@ func TestListUnconfirmedTxs(t *testing.T) { go func() { for { - resp, err := burrow_client.ListUnconfirmedTxs(client) - assert.NoError(t, err) + resp, err := ListUnconfirmedTxs(client, -1) + if resp != nil { + + } + require.NoError(t, err) if resp.N > 0 { txChan <- resp.Txs } @@ -362,14 +361,12 @@ func TestListUnconfirmedTxs(t *testing.T) { runThenWaitForBlock(t, wsc, nextBlockPredicateFn(), func() { broadcastTx(t, client, tx) select { - case <-time.After(time.Second * timeoutSeconds): + case <-time.After(time.Second * timeoutSeconds * 10): t.Fatal("Timeout out waiting for unconfirmed transactions to appear") case transactions := <-txChan: - assert.Len(t, transactions, 1, - "There should only be a single transaction in the mempool during "+ - "this test (previous txs should have made it into a block)") - assert.Contains(t, transactions, tx, - "Transaction should be returned by ListUnconfirmedTxs") + assert.Len(t, transactions, 1, "There should only be a single transaction in the "+ + "mempool during this test (previous txs should have made it into a block)") + assert.Contains(t, transactions, tx, "Transaction should be returned by ListUnconfirmedTxs") } }) }) @@ -377,9 +374,9 @@ func TestListUnconfirmedTxs(t *testing.T) { func TestGetBlock(t *testing.T) { wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { waitNBlocks(t, wsc, 3) - resp, err := burrow_client.GetBlock(client, 2) + resp, err := GetBlock(client, 2) assert.NoError(t, err) assert.Equal(t, 2, resp.Block.Height) assert.Equal(t, 2, resp.BlockMeta.Header.Height) @@ -388,38 +385,54 @@ func TestGetBlock(t *testing.T) { func TestListValidators(t *testing.T) { wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { waitNBlocks(t, wsc, 3) - resp, err := burrow_client.ListValidators(client) + resp, err := 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) + validator := resp.BondedValidators[0] + assert.Equal(t, genesisDoc.Validators[0].PublicKey, validator.PublicKey) }) } func TestDumpConsensusState(t *testing.T) { wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { + startTime := time.Now() + testWithAllClients(t, func(t *testing.T, clientName string, client RPCClient) { waitNBlocks(t, wsc, 3) - resp, err := burrow_client.DumpConsensusState(client) + resp, err := 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 + commitTime := resp.RoundState.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) + 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, types.RoundStepNewHeight, resp.RoundState.Step) }) } -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) +func TestParamsMap(t *testing.T) { + type aStruct struct { + Baz int } - return eventDataTx + dict, err := paramsMap("Foo", aStruct{5}, + "Bar", "Nibbles") + assert.NoError(t, err, "Should not be a paramsMaperror") + assert.Equal(t, map[string]interface{}{ + "Foo": aStruct{5}, + "Bar": "Nibbles", + }, dict) + + // Empty map + dict, err = paramsMap() + assert.Equal(t, map[string]interface{}{}, dict) + assert.NoError(t, err, "Empty mapsAndValues call should be fine") + + // Invalid maps + assert.NoError(t, err, "Empty mapsAndValues call should be fine") + _, err = paramsMap("Foo", 4, "Bar") + assert.Error(t, err, "Should be an error to get an odd number of arguments") + + _, err = paramsMap("Foo", 4, 4, "Bar") + assert.Error(t, err, "Should be an error to provide non-string keys") } diff --git a/rpc/tm/client/shared.go b/rpc/tm/client/shared.go new file mode 100644 index 0000000000000000000000000000000000000000..0aaf157c5433901056eccaa4aadf933e3fa342bf --- /dev/null +++ b/rpc/tm/client/shared.go @@ -0,0 +1,316 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "bytes" + "fmt" + "hash/fnv" + "strconv" + "testing" + + "os" + + "time" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/consensus/tendermint/validator" + "github.com/hyperledger/burrow/core" + "github.com/hyperledger/burrow/execution" + "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/logging/loggers" + "github.com/hyperledger/burrow/permission" + "github.com/hyperledger/burrow/rpc" + "github.com/hyperledger/burrow/txs" + "github.com/stretchr/testify/require" + tm_config "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/rpc/lib/client" +) + +const ( + chainName = "RPC_Test_Chain" + rpcAddr = "0.0.0.0:46657" + websocketAddr = rpcAddr + websocketEndpoint = "/websocket" + testDir = "./test_scratch/tm_test" +) + +// global variables for use across all tests +var ( + privateAccounts = makePrivateAccounts(5) // make keys + jsonRpcClient = rpcclient.NewJSONRPCClient(rpcAddr) + httpClient = rpcclient.NewURIClient(rpcAddr) + clients = map[string]RPCClient{ + "JSONRPC": jsonRpcClient, + "HTTP": httpClient, + } + // Initialised in initGlobalVariables + genesisDoc = new(genesis.GenesisDoc) + kernel = new(core.Kernel) +) + +// We use this to wrap tests +func TestWrapper(runner func() int) int { + fmt.Println("Running with integration TestWrapper (rpc/tendermint/test/shared_test.go)...") + + err := initGlobalVariables() + if err != nil { + panic(err) + } + + err = kernel.Boot() + if err != nil { + panic(err) + } + + defer kernel.Shutdown() + + return runner() +} + +func initGlobalVariables() error { + var err error + os.RemoveAll(testDir) + os.MkdirAll(testDir, 0777) + os.Chdir(testDir) + tmConf := tm_config.DefaultConfig() + //logger, _ := lifecycle.NewStdErrLogger() + logger := loggers.NewNoopInfoTraceLogger() + privValidator := validator.NewPrivValidatorMemory(privateAccounts[0], privateAccounts[0]) + genesisDoc = testGenesisDoc() + kernel, err = core.NewKernel(privValidator, genesisDoc, tmConf, rpc.DefaultRPCConfig(), logger) + return err +} + +func testGenesisDoc() *genesis.GenesisDoc { + accounts := make(map[string]acm.Account, len(privateAccounts)) + for i, pa := range privateAccounts { + account := acm.FromAddressable(pa) + account.AddToBalance(1 << 32) + account.SetPermissions(permission.AllAccountPermissions.Clone()) + accounts[fmt.Sprintf("user_%v", i)] = account + } + genesisTime, err := time.Parse("02-01-2006", "27-10-2017") + if err != nil { + panic("could not parse test genesis time") + } + return genesis.MakeGenesisDocFromAccounts(chainName, nil, genesisTime, accounts, + map[string]acm.Validator{ + "genesis_validator": acm.AsValidator(accounts["user_0"]), + }) +} + +// Deterministic account generation helper. Pass number of accounts to make +func makePrivateAccounts(n int) []acm.PrivateAccount { + accounts := make([]acm.PrivateAccount, n) + for i := 0; i < n; i++ { + accounts[i] = acm.GeneratePrivateAccountFromSecret("mysecret" + strconv.Itoa(i)) + } + return accounts +} + +//------------------------------------------------------------------------------- +// some default transaction functions + +func makeDefaultSendTx(t *testing.T, client RPCClient, addr acm.Address, amt uint64) *txs.SendTx { + nonce := getNonce(t, client, privateAccounts[0].Address()) + tx := txs.NewSendTx() + tx.AddInputWithNonce(privateAccounts[0].PublicKey(), amt, nonce+1) + tx.AddOutput(addr, amt) + return tx +} + +func makeDefaultSendTxSigned(t *testing.T, client RPCClient, addr acm.Address, amt uint64) *txs.SendTx { + tx := makeDefaultSendTx(t, client, addr, amt) + tx.SignInput(genesisDoc.ChainID(), 0, privateAccounts[0]) + return tx +} + +func makeDefaultCallTx(t *testing.T, client RPCClient, addr *acm.Address, code []byte, amt, gasLim, + fee uint64) *txs.CallTx { + nonce := getNonce(t, client, privateAccounts[0].Address()) + tx := txs.NewCallTxWithNonce(privateAccounts[0].PublicKey(), addr, code, amt, gasLim, fee, + nonce+1) + tx.Sign(genesisDoc.ChainID(), privateAccounts[0]) + return tx +} + +func makeDefaultCallTxWithNonce(t *testing.T, addr *acm.Address, sequence uint64, code []byte, + amt, gasLim, fee uint64) *txs.CallTx { + + tx := txs.NewCallTxWithNonce(privateAccounts[0].PublicKey(), addr, code, amt, gasLim, fee, sequence) + tx.Sign(genesisDoc.ChainID(), privateAccounts[0]) + return tx +} + +func makeDefaultNameTx(t *testing.T, client RPCClient, name, value string, amt, fee uint64) *txs.NameTx { + nonce := getNonce(t, client, privateAccounts[0].Address()) + tx := txs.NewNameTxWithNonce(privateAccounts[0].PublicKey(), name, value, amt, fee, nonce+1) + tx.Sign(genesisDoc.ChainID(), privateAccounts[0]) + return tx +} + +//------------------------------------------------------------------------------- +// rpc call wrappers (fail on err) + +// get an account's nonce +func getNonce(t *testing.T, client RPCClient, addr acm.Address) uint64 { + acc, err := GetAccount(client, addr) + if err != nil { + t.Fatal(err) + } + if acc == nil { + return 0 + } + return acc.Sequence() +} + +// get the account +func getAccount(t *testing.T, client RPCClient, addr acm.Address) acm.Account { + ac, err := GetAccount(client, addr) + if err != nil { + t.Fatal(err) + } + return ac +} + +// sign transaction +func signTx(t *testing.T, client RPCClient, tx txs.Tx, + privAcc *acm.ConcretePrivateAccount) txs.Tx { + signedTx, err := SignTx(client, tx, []*acm.ConcretePrivateAccount{privAcc}) + if err != nil { + t.Fatal(err) + } + return signedTx +} + +// broadcast transaction +func broadcastTx(t *testing.T, client RPCClient, tx txs.Tx) *txs.Receipt { + rec, err := BroadcastTx(client, tx) + require.NoError(t, err) + return rec +} + +// dump all storage for an account. currently unused +func dumpStorage(t *testing.T, addr acm.Address) *rpc.ResultDumpStorage { + client := clients["HTTP"] + resp, err := DumpStorage(client, addr) + if err != nil { + t.Fatal(err) + } + return resp +} + +func getStorage(t *testing.T, client RPCClient, addr acm.Address, key []byte) []byte { + resp, err := GetStorage(client, addr, key) + if err != nil { + t.Fatal(err) + } + return resp +} + +func callCode(t *testing.T, client RPCClient, fromAddress acm.Address, code, data, + expected []byte) { + resp, err := CallCode(client, fromAddress, code, data) + if err != nil { + t.Fatal(err) + } + ret := resp.Return + // NOTE: we don't flip memory when it comes out of RETURN (?!) + if bytes.Compare(ret, binary.LeftPadWord256(expected).Bytes()) != 0 { + t.Fatalf("Conflicting return value. Got %x, expected %x", ret, expected) + } +} + +func callContract(t *testing.T, client RPCClient, fromAddress, toAddress acm.Address, + data, expected []byte) { + resp, err := Call(client, fromAddress, toAddress, data) + if err != nil { + t.Fatal(err) + } + ret := resp.Return + // NOTE: we don't flip memory when it comes out of RETURN (?!) + if bytes.Compare(ret, binary.LeftPadWord256(expected).Bytes()) != 0 { + t.Fatalf("Conflicting return value. Got %x, expected %x", ret, expected) + } +} + +// get the namereg entry +func getNameRegEntry(t *testing.T, client RPCClient, name string) *execution.NameRegEntry { + entry, err := GetName(client, name) + if err != nil { + t.Fatal(err) + } + return entry +} + +// Returns a positive int64 hash of text (consumers want int64 instead of uint64) +func hashString(text string) uint64 { + hasher := fnv.New64() + hasher.Write([]byte(text)) + return uint64(hasher.Sum64()) +} + +//-------------------------------------------------------------------------------- +// 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 + contractCode := []byte{0x60, 0x5, 0x60, 0x6, 0x1, 0x60, 0x0, 0x52, 0x60, 0x20, + 0x60, 0x0, 0xf3} + // the is the code we need to return the contractCode when the contract is initialized + lenCode := len(contractCode) + // push code to the stack + //code := append([]byte{byte(0x60 + lenCode - 1)}, RightPadWord256(contractCode).Bytes()...) + code := append([]byte{0x7f}, + binary.RightPadWord256(contractCode).Bytes()...) + // store it in memory + code = append(code, []byte{0x60, 0x0, 0x52}...) + // return whats in memory + //code = append(code, []byte{0x60, byte(32 - lenCode), 0x60, byte(lenCode), 0xf3}...) + code = append(code, []byte{0x60, byte(lenCode), 0x60, 0x0, 0xf3}...) + // return init code, contract code, expected return + return code, contractCode, binary.LeftPadBytes([]byte{0xb}, 32) +} + +// simple call contract calls another contract +func simpleCallContract(addr acm.Address) ([]byte, []byte, []byte) { + gas1, gas2 := byte(0x1), byte(0x1) + value := byte(0x1) + inOff, inSize := byte(0x0), byte(0x0) // no call data + retOff, retSize := byte(0x0), byte(0x20) + // this is the code we want to run (call a contract and return) + contractCode := []byte{0x60, retSize, 0x60, retOff, 0x60, inSize, 0x60, inOff, + 0x60, value, 0x73} + contractCode = append(contractCode, addr.Bytes()...) + contractCode = append(contractCode, []byte{0x61, gas1, gas2, 0xf1, 0x60, 0x20, + 0x60, 0x0, 0xf3}...) + + // the is the code we need to return; the contractCode when the contract is initialized + // it should copy the code from the input into memory + lenCode := len(contractCode) + memOff := byte(0x0) + inOff = byte(0xc) // length of code before codeContract + length := byte(lenCode) + + code := []byte{0x60, length, 0x60, inOff, 0x60, memOff, 0x37} + // return whats in memory + code = append(code, []byte{0x60, byte(lenCode), 0x60, 0x0, 0xf3}...) + code = append(code, contractCode...) + // return init code, contract code, expected return + return code, contractCode, binary.LeftPadBytes([]byte{0xb}, 32) +} diff --git a/rpc/tendermint/test/shared_test.go b/rpc/tm/client/shared_test.go similarity index 92% rename from rpc/tendermint/test/shared_test.go rename to rpc/tm/client/shared_test.go index 5b2a1455c161aa75320800e5c89180554b91e1f5..46c939cf89dd282fa278cbec3ed83512ee3a3a39 100644 --- a/rpc/tendermint/test/shared_test.go +++ b/rpc/tm/client/shared_test.go @@ -1,6 +1,3 @@ -// +build integration - -// Space above here matters // Copyright 2017 Monax Industries Limited // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package test +package client import ( "os" diff --git a/rpc/tm/client/websocket_client.go b/rpc/tm/client/websocket_client.go new file mode 100644 index 0000000000000000000000000000000000000000..d20eeb737395b377b40eb63adb391e9872c71fe2 --- /dev/null +++ b/rpc/tm/client/websocket_client.go @@ -0,0 +1,47 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "context" + "fmt" + + "github.com/tendermint/tendermint/rpc/lib/types" +) + +type WebsocketClient interface { + Send(ctx context.Context, request rpctypes.RPCRequest) error +} + +func Subscribe(wsc WebsocketClient, eventId string) error { + req, err := rpctypes.MapToRequest(fmt.Sprintf("wsclient_subscribe?eventId=%s", eventId), + "subscribe", map[string]interface{}{"eventId": eventId}) + if err != nil { + return err + } + return wsc.Send(context.Background(), req) + + //return wsc.Call(context.Background(), "subscribe", + // map[string]interface{}{"eventId": eventId}) +} + +func Unsubscribe(websocketClient WebsocketClient, subscriptionId string) error { + req, err := rpctypes.MapToRequest(fmt.Sprintf("wsclient_unsubscribe?subId=%s", subscriptionId), + "unsubscribe", map[string]interface{}{"subscriptionId": subscriptionId}) + if err != nil { + return err + } + return websocketClient.Send(context.Background(), req) +} diff --git a/rpc/tendermint/test/websocket_client_test.go b/rpc/tm/client/websocket_client_test.go similarity index 66% rename from rpc/tendermint/test/websocket_client_test.go rename to rpc/tm/client/websocket_client_test.go index 4bb2609269bfdbb85978cc15f23fbea6d2292f3b..d5ce845a72e60a17b1987b2755137e9aaea9a5dc 100644 --- a/rpc/tendermint/test/websocket_client_test.go +++ b/rpc/tm/client/websocket_client_test.go @@ -1,6 +1,3 @@ -// +build integration - -// Space above here matters // Copyright 2017 Monax Industries Limited // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,18 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -package test +package client import ( "fmt" "testing" - "time" - core_types "github.com/hyperledger/burrow/rpc/tendermint/core/types" + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/event" + exe_events "github.com/hyperledger/burrow/execution/events" + evm_events "github.com/hyperledger/burrow/execution/evm/events" + "github.com/hyperledger/burrow/rpc" "github.com/hyperledger/burrow/txs" "github.com/stretchr/testify/assert" - _ "github.com/tendermint/tendermint/config/tendermint_test" + "github.com/stretchr/testify/require" + tm_types "github.com/tendermint/tendermint/types" ) //-------------------------------------------------------------------------------- @@ -35,21 +36,21 @@ import ( // make a simple connection to the server func TestWSConnect(t *testing.T) { wsc := newWSClient() - wsc.Stop() + stopWSClient(wsc) } // receive a new block message func TestWSNewBlock(t *testing.T) { wsc := newWSClient() - eid := txs.EventStringNewBlock() + eid := tm_types.EventStringNewBlock() subId := subscribeAndGetSubscriptionId(t, wsc, eid) defer func() { unsubscribe(t, wsc, subId) - wsc.Stop() + stopWSClient(wsc) }() waitForEvent(t, wsc, eid, func() {}, - func(eid string, eventData txs.EventData) (bool, error) { - fmt.Println("Check: ", eventData.(txs.EventDataNewBlock).Block) + func(eid string, eventData event.AnyEventData) (bool, error) { + fmt.Println("Check: ", eventData.EventDataNewBlock().Block) return true, nil }) } @@ -60,19 +61,19 @@ func TestWSBlockchainGrowth(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - eid := txs.EventStringNewBlock() + eid := tm_types.EventStringNewBlock() subId := subscribeAndGetSubscriptionId(t, wsc, eid) defer func() { unsubscribe(t, wsc, subId) - wsc.Stop() + stopWSClient(wsc) }() // listen for NewBlock, ensure height increases by 1 var initBlockN int for i := 0; i < 2; i++ { waitForEvent(t, wsc, eid, func() {}, - func(eid string, eventData txs.EventData) (bool, error) { - eventDataNewBlock, ok := eventData.(txs.EventDataNewBlock) - if !ok { + func(eid string, eventData event.AnyEventData) (bool, error) { + eventDataNewBlock := eventData.EventDataNewBlock() + if eventDataNewBlock == nil { t.Fatalf("Was expecting EventDataNewBlock but got %v", eventData) } block := eventDataNewBlock.Block @@ -93,16 +94,16 @@ 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 := users[1].Address - amt := int64(100) - eidInput := txs.EventStringAccInput(users[0].Address) - eidOutput := txs.EventStringAccOutput(toAddr) + toAddr := privateAccounts[1].Address() + amt := uint64(100) + eidInput := exe_events.EventStringAccInput(privateAccounts[0].Address()) + eidOutput := exe_events.EventStringAccOutput(toAddr) subIdInput := subscribeAndGetSubscriptionId(t, wsc, eidInput) subIdOutput := subscribeAndGetSubscriptionId(t, wsc, eidOutput) defer func() { unsubscribe(t, wsc, subIdInput) unsubscribe(t, wsc, subIdOutput) - wsc.Stop() + stopWSClient(wsc) }() waitForEvent(t, wsc, eidInput, func() { tx := makeDefaultSendTxSigned(t, jsonRpcClient, toAddr, amt) @@ -119,25 +120,25 @@ func TestWSDoubleFire(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - eid := txs.EventStringAccInput(users[0].Address) + eid := exe_events.EventStringAccInput(privateAccounts[0].Address()) subId := subscribeAndGetSubscriptionId(t, wsc, eid) defer func() { unsubscribe(t, wsc, subId) - wsc.Stop() + stopWSClient(wsc) }() - amt := int64(100) - toAddr := users[1].Address + amt := uint64(100) + toAddr := privateAccounts[1].Address() // broadcast the transaction, wait to hear about it waitForEvent(t, wsc, eid, func() { tx := makeDefaultSendTxSigned(t, jsonRpcClient, toAddr, amt) broadcastTx(t, jsonRpcClient, tx) - }, func(eid string, b txs.EventData) (bool, error) { + }, func(eid string, b event.AnyEventData) (bool, error) { return true, nil }) // but make sure we don't hear about it twice err := waitForEvent(t, wsc, eid, func() {}, - func(eid string, b txs.EventData) (bool, error) { + func(eid string, b event.AnyEventData) (bool, error) { return false, nil }) assert.True(t, err.Timeout(), "We should have timed out waiting for second"+ @@ -150,15 +151,15 @@ func TestWSCallWait(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - eid1 := txs.EventStringAccInput(users[0].Address) + eid1 := exe_events.EventStringAccInput(privateAccounts[0].Address()) subId1 := subscribeAndGetSubscriptionId(t, wsc, eid1) defer func() { unsubscribe(t, wsc, subId1) - wsc.Stop() + stopWSClient(wsc) }() - amt, gasLim, fee := int64(10000), int64(1000), int64(1000) + amt, gasLim, fee := uint64(10000), uint64(1000), uint64(1000) code, returnCode, returnVal := simpleContract() - var contractAddr []byte + var contractAddr acm.Address // wait for the contract to be created waitForEvent(t, wsc, eid1, func() { tx := makeDefaultCallTx(t, jsonRpcClient, nil, code, amt, gasLim, fee) @@ -167,8 +168,8 @@ func TestWSCallWait(t *testing.T) { }, unmarshalValidateTx(amt, returnCode)) // susbscribe to the new contract - amt = int64(10001) - eid2 := txs.EventStringAccOutput(contractAddr) + amt = uint64(10001) + eid2 := exe_events.EventStringAccOutput(contractAddr) subId2 := subscribeAndGetSubscriptionId(t, wsc, eid2) defer func() { unsubscribe(t, wsc, subId2) @@ -176,7 +177,7 @@ func TestWSCallWait(t *testing.T) { // get the return value from a call data := []byte{0x1} waitForEvent(t, wsc, eid2, func() { - tx := makeDefaultCallTx(t, jsonRpcClient, contractAddr, data, amt, gasLim, fee) + tx := makeDefaultCallTx(t, jsonRpcClient, &contractAddr, data, amt, gasLim, fee) receipt := broadcastTx(t, jsonRpcClient, tx) contractAddr = receipt.ContractAddr }, unmarshalValidateTx(amt, returnVal)) @@ -189,25 +190,24 @@ func TestWSCallNoWait(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - amt, gasLim, fee := int64(10000), int64(1000), int64(1000) + defer stopWSClient(wsc) + amt, gasLim, fee := uint64(10000), uint64(1000), uint64(1000) code, _, returnVal := simpleContract() tx := makeDefaultCallTx(t, jsonRpcClient, nil, code, amt, gasLim, fee) - receipt := broadcastTx(t, jsonRpcClient, tx) + receipt, err := broadcastTxAndWaitForBlock(t, jsonRpcClient, wsc, tx) + require.NoError(t, err) contractAddr := receipt.ContractAddr // susbscribe to the new contract - amt = int64(10001) - eid := txs.EventStringAccOutput(contractAddr) + amt = uint64(10001) + eid := exe_events.EventStringAccOutput(contractAddr) subId := subscribeAndGetSubscriptionId(t, wsc, eid) - defer func() { - unsubscribe(t, wsc, subId) - wsc.Stop() - }() + defer unsubscribe(t, wsc, subId) // get the return value from a call data := []byte{0x1} waitForEvent(t, wsc, eid, func() { - tx := makeDefaultCallTx(t, jsonRpcClient, contractAddr, data, amt, gasLim, fee) + tx := makeDefaultCallTx(t, jsonRpcClient, &contractAddr, data, amt, gasLim, fee) broadcastTx(t, jsonRpcClient, tx) }, unmarshalValidateTx(amt, returnVal)) } @@ -218,47 +218,52 @@ func TestWSCallCall(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - amt, gasLim, fee := int64(10000), int64(1000), int64(1000) + defer stopWSClient(wsc) + amt, gasLim, fee := uint64(10000), uint64(1000), uint64(1000) code, _, returnVal := simpleContract() txid := new([]byte) // deploy the two contracts tx := makeDefaultCallTx(t, jsonRpcClient, nil, code, amt, gasLim, fee) - receipt := broadcastTx(t, jsonRpcClient, tx) + receipt, err := broadcastTxAndWaitForBlock(t, jsonRpcClient, wsc, tx) + require.NoError(t, err) contractAddr1 := receipt.ContractAddr + // subscribe to the new contracts + eid := evm_events.EventStringAccCall(contractAddr1) + subId := subscribeAndGetSubscriptionId(t, wsc, eid) + defer unsubscribe(t, wsc, subId) + // call contract2, which should call contract1, and wait for ev1 code, _, _ = simpleCallContract(contractAddr1) tx = makeDefaultCallTx(t, jsonRpcClient, nil, code, amt, gasLim, fee) receipt = broadcastTx(t, jsonRpcClient, tx) contractAddr2 := receipt.ContractAddr - // subscribe to the new contracts - amt = int64(10001) - eid := txs.EventStringAccCall(contractAddr1) - subId := subscribeAndGetSubscriptionId(t, wsc, eid) - defer func() { - unsubscribe(t, wsc, subId) - wsc.Stop() - }() - // call contract2, which should call contract1, and wait for ev1 - // let the contract get created first - waitForEvent(t, wsc, eid, func() { - }, func(eid string, b txs.EventData) (bool, error) { - return true, nil - }) + waitForEvent(t, wsc, eid, + // Runner + func() { + }, + // Event Checker + func(eid string, b event.AnyEventData) (bool, error) { + return true, nil + }) // call it - waitForEvent(t, wsc, eid, func() { - tx := makeDefaultCallTx(t, jsonRpcClient, contractAddr2, nil, amt, gasLim, fee) - broadcastTx(t, jsonRpcClient, tx) - *txid = txs.TxHash(chainID, tx) - }, unmarshalValidateCall(users[0].Address, returnVal, txid)) + waitForEvent(t, wsc, eid, + // Runner + func() { + tx := makeDefaultCallTx(t, jsonRpcClient, &contractAddr2, nil, amt, gasLim, fee) + broadcastTx(t, jsonRpcClient, tx) + *txid = txs.TxHash(genesisDoc.ChainID(), tx) + }, + // Event checker + unmarshalValidateCall(privateAccounts[0].Address(), returnVal, txid)) } func TestSubscribe(t *testing.T) { wsc := newWSClient() var subId string - subscribe(t, wsc, txs.EventStringNewBlock()) + subscribe(t, wsc, tm_types.EventStringNewBlock()) // timeout to check subscription process is live timeout := time.After(timeoutSeconds * time.Second) @@ -268,13 +273,12 @@ Subscribe: case <-timeout: 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 - } + case response := <-wsc.ResponsesCh: + require.Nil(t, response.Error) + res := readResult(t, *response.Result).(*rpc.ResultSubscribe) + assert.Equal(t, tm_types.EventStringNewBlock(), res.Event) + subId = res.SubscriptionId + break Subscribe } } @@ -289,11 +293,12 @@ Subscribe: } return - case bs := <-wsc.ResultsCh: - resultEvent, ok := readResult(t, bs).(*core_types.ResultEvent) - if ok { - _, ok := resultEvent.Data.(txs.EventDataNewBlock) - if ok { + case response := <-wsc.ResponsesCh: + require.Nil(t, response.Error) + if res, ok := readResult(t, *response.Result).(*rpc.ResultEvent); ok { + enb := res.EventDataNewBlock() + if enb != nil { + assert.Equal(t, genesisDoc.ChainID(), enb.Block.ChainID) if blocksSeen > 1 { t.Fatal("Continued to see NewBlock event after unsubscribing") } else { diff --git a/rpc/tendermint/test/websocket_helpers.go b/rpc/tm/client/websocket_helpers.go similarity index 56% rename from rpc/tendermint/test/websocket_helpers.go rename to rpc/tm/client/websocket_helpers.go index 1e013b5203dade9e0b76eac80e5f1361b88bac5b..05c0a8831d9a2dbcbdd7eaca9879808f439174a0 100644 --- a/rpc/tendermint/test/websocket_helpers.go +++ b/rpc/tm/client/websocket_helpers.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package test +package client import ( "bytes" @@ -20,18 +20,20 @@ import ( "testing" "time" - burrow_client "github.com/hyperledger/burrow/rpc/tendermint/client" - ctypes "github.com/hyperledger/burrow/rpc/tendermint/core/types" - "github.com/hyperledger/burrow/txs" + "encoding/json" - rpcclient "github.com/tendermint/go-rpc/client" - "github.com/tendermint/go-wire" + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/rpc" + "github.com/hyperledger/burrow/txs" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/rpc/lib/client" tm_types "github.com/tendermint/tendermint/types" ) const ( timeoutSeconds = 2 - expectBlockInSeconds = timeoutSeconds * 2 + expectBlockInSeconds = 2 ) //-------------------------------------------------------------------------------- @@ -47,16 +49,20 @@ func newWSClient() *rpcclient.WSClient { return wsc } +func stopWSClient(wsc *rpcclient.WSClient) { + wsc.Stop() +} + // subscribe to an event func subscribe(t *testing.T, wsc *rpcclient.WSClient, eventId string) { - if err := burrow_client.Subscribe(wsc, eventId); err != nil { + if err := Subscribe(wsc, eventId); err != nil { t.Fatal(err) } } func subscribeAndGetSubscriptionId(t *testing.T, wsc *rpcclient.WSClient, eventId string) string { - if err := burrow_client.Subscribe(wsc, eventId); err != nil { + if err := Subscribe(wsc, eventId); err != nil { t.Fatal(err) } @@ -65,10 +71,11 @@ func subscribeAndGetSubscriptionId(t *testing.T, wsc *rpcclient.WSClient, select { case <-timeout.C: t.Fatal("Timeout waiting for subscription result") - case bs := <-wsc.ResultsCh: - resultSubscribe, ok := readResult(t, bs).(*ctypes.ResultSubscribe) + case response := <-wsc.ResponsesCh: + require.Nil(t, response.Error, "got error response from websocket channel") + res, ok := readResult(t, *response.Result).(*rpc.ResultSubscribe) if ok { - return resultSubscribe.SubscriptionId + return res.SubscriptionId } } } @@ -76,20 +83,20 @@ func subscribeAndGetSubscriptionId(t *testing.T, wsc *rpcclient.WSClient, // unsubscribe from an event func unsubscribe(t *testing.T, wsc *rpcclient.WSClient, subscriptionId string) { - if err := burrow_client.Unsubscribe(wsc, subscriptionId); err != nil { + if err := Unsubscribe(wsc, subscriptionId); err != nil { t.Fatal(err) } } // broadcast transaction and wait for new block -func broadcastTxAndWaitForBlock(t *testing.T, client burrow_client.RPCClient, - wsc *rpcclient.WSClient, tx txs.Tx) (txs.Receipt, error) { - var rec txs.Receipt +func broadcastTxAndWaitForBlock(t *testing.T, client RPCClient, wsc *rpcclient.WSClient, + tx txs.Tx) (*txs.Receipt, error) { + + var rec *txs.Receipt var err error runThenWaitForBlock(t, wsc, nextBlockPredicateFn(), func() { - rec, err = burrow_client.BroadcastTx(client, tx) - mempoolCount += 1 + rec, err = BroadcastTx(client, tx) }) return rec, err } @@ -121,40 +128,37 @@ func waitNBlocks(t *testing.T, wsc *rpcclient.WSClient, n int) { func() {}) } -func runThenWaitForBlock(t *testing.T, wsc *rpcclient.WSClient, - predicate blockPredicate, runner func()) { - subscribeAndWaitForNext(t, wsc, txs.EventStringNewBlock(), - runner, - func(event string, eventData txs.EventData) (bool, error) { - return predicate(eventData.(txs.EventDataNewBlock).Block), nil - }) +func runThenWaitForBlock(t *testing.T, wsc *rpcclient.WSClient, predicate blockPredicate, runner func()) { + eventDataChecker := func(event string, eventData event.AnyEventData) (bool, error) { + eventDataNewBlock := eventData.EventDataNewBlock() + if eventDataNewBlock == nil { + return false, fmt.Errorf("could not convert %#v to EventDataNewBlock", eventData) + } + return predicate(eventDataNewBlock.Block), nil + } + subscribeAndWaitForNext(t, wsc, tm_types.EventStringNewBlock(), runner, eventDataChecker) } -func subscribeAndWaitForNext(t *testing.T, wsc *rpcclient.WSClient, event string, - runner func(), - eventDataChecker func(string, txs.EventData) (bool, error)) { +func subscribeAndWaitForNext(t *testing.T, wsc *rpcclient.WSClient, event string, runner func(), + eventDataChecker func(string, event.AnyEventData) (bool, error)) { + subId := subscribeAndGetSubscriptionId(t, wsc, event) defer unsubscribe(t, wsc, subId) - waitForEvent(t, - wsc, - event, - runner, - eventDataChecker) + waitForEvent(t, wsc, event, runner, eventDataChecker) } // waitForEvent executes runner that is expected to trigger events. It then // waits for any events on the supplies WSClient and checks the eventData with // the eventDataChecker which is a function that is passed the event name -// and the EventData and returns the pair of stopWaiting, err. Where if +// and the Data and returns the pair of stopWaiting, err. Where if // stopWaiting is true waitForEvent will return or if stopWaiting is false // waitForEvent will keep listening for new events. If an error is returned // waitForEvent will fail the test. -func waitForEvent(t *testing.T, wsc *rpcclient.WSClient, eventid string, - runner func(), - eventDataChecker func(string, txs.EventData) (bool, error)) waitForEventResult { +func waitForEvent(t *testing.T, wsc *rpcclient.WSClient, eventID string, runner func(), + eventDataChecker func(string, event.AnyEventData) (bool, error)) waitForEventResult { // go routine to wait for websocket msg - eventsCh := make(chan txs.EventData) + eventsCh := make(chan event.AnyEventData) shutdownEventsCh := make(chan bool, 1) errCh := make(chan error) @@ -163,27 +167,21 @@ func waitForEvent(t *testing.T, wsc *rpcclient.WSClient, eventid string, // Read message go func() { - var err error LOOP: for { select { case <-shutdownEventsCh: break LOOP - case r := <-wsc.ResultsCh: - result := new(ctypes.BurrowResult) - wire.ReadJSONPtr(result, r, &err) - if err != nil { - errCh <- err + case r := <-wsc.ResponsesCh: + if r.Error != nil { + errCh <- r.Error break LOOP } - event, ok := (*result).(*ctypes.ResultEvent) - if ok && event.Event == eventid { + resultEvent, _ := readResult(t, *r.Result).(*rpc.ResultEvent) + if resultEvent != nil && resultEvent.Event == eventID { // Keep feeding events - eventsCh <- event.Data + eventsCh <- resultEvent.AnyEventData } - case err := <-wsc.ErrorsCh: - errCh <- err - break LOOP case <-wsc.Quit: break LOOP } @@ -200,10 +198,8 @@ func waitForEvent(t *testing.T, wsc *rpcclient.WSClient, eventid string, return waitForEventResult{timeout: true} case eventData := <-eventsCh: // run the check - stopWaiting, err := eventDataChecker(eventid, eventData) - if err != nil { - t.Fatal(err) // Show the stack trace. - } + stopWaiting, err := eventDataChecker(eventID, eventData) + require.NoError(t, err) if stopWaiting { return waitForEventResult{} } @@ -224,38 +220,44 @@ func (err waitForEventResult) Timeout() bool { //-------------------------------------------------------------------------------- -func unmarshalValidateSend(amt int64, - toAddr []byte) func(string, txs.EventData) (bool, error) { - return func(eid string, eventData txs.EventData) (bool, error) { - var data = eventData.(txs.EventDataTx) +func unmarshalValidateSend(amt uint64, toAddr acm.Address) func(string, event.AnyEventData) (bool, error) { + return func(eid string, eventData event.AnyEventData) (bool, error) { + data := eventData.EventDataTx() + if data == nil { + return true, fmt.Errorf("event data %s is not EventDataTx", eventData) + } if data.Exception != "" { return true, fmt.Errorf(data.Exception) } tx := data.Tx.(*txs.SendTx) - if !bytes.Equal(tx.Inputs[0].Address, users[0].Address) { - return true, fmt.Errorf("Senders do not match up! Got %x, expected %x", tx.Inputs[0].Address, users[0].Address) + if tx.Inputs[0].Address != privateAccounts[0].Address() { + return true, fmt.Errorf("senders do not match up! Got %s, expected %s", tx.Inputs[0].Address, + privateAccounts[0].Address) } if tx.Inputs[0].Amount != amt { - return true, fmt.Errorf("Amt does not match up! Got %d, expected %d", tx.Inputs[0].Amount, amt) + return true, fmt.Errorf("amt does not match up! Got %d, expected %d", tx.Inputs[0].Amount, amt) } - if !bytes.Equal(tx.Outputs[0].Address, toAddr) { - return true, fmt.Errorf("Receivers do not match up! Got %x, expected %x", tx.Outputs[0].Address, users[0].Address) + if tx.Outputs[0].Address != toAddr { + return true, fmt.Errorf("receivers do not match up! Got %s, expected %s", tx.Outputs[0].Address, + privateAccounts[0].Address) } return true, nil } } -func unmarshalValidateTx(amt int64, - returnCode []byte) func(string, txs.EventData) (bool, error) { - return func(eid string, eventData txs.EventData) (bool, error) { - var data = eventData.(txs.EventDataTx) +func unmarshalValidateTx(amt uint64, returnCode []byte) func(string, event.AnyEventData) (bool, error) { + return func(eid string, eventData event.AnyEventData) (bool, error) { + data := eventData.EventDataTx() + if data == nil { + return true, fmt.Errorf("event data %s is not EventDataTx", eventData) + } if data.Exception != "" { return true, fmt.Errorf(data.Exception) } tx := data.Tx.(*txs.CallTx) - if !bytes.Equal(tx.Input.Address, users[0].Address) { + if tx.Input.Address != privateAccounts[0].Address() { return true, fmt.Errorf("Senders do not match up! Got %x, expected %x", - tx.Input.Address, users[0].Address) + tx.Input.Address, privateAccounts[0].Address) } if tx.Input.Amount != amt { return true, fmt.Errorf("Amt does not match up! Got %d, expected %d", @@ -269,20 +271,21 @@ func unmarshalValidateTx(amt int64, } } -func unmarshalValidateCall(origin, - returnCode []byte, txid *[]byte) func(string, txs.EventData) (bool, error) { - return func(eid string, eventData txs.EventData) (bool, error) { - var data = eventData.(txs.EventDataCall) +func unmarshalValidateCall(origin acm.Address, returnCode []byte, txid *[]byte) func(string, event.AnyEventData) (bool, error) { + return func(eid string, eventData event.AnyEventData) (bool, error) { + data := eventData.EventDataCall() + if data == nil { + return true, fmt.Errorf("event data %s is not EventDataTx", eventData) + } if data.Exception != "" { return true, fmt.Errorf(data.Exception) } - if !bytes.Equal(data.Origin, origin) { - return true, fmt.Errorf("Origin does not match up! Got %x, expected %x", - data.Origin, origin) + if data.Origin != origin { + return true, fmt.Errorf("origin does not match up! Got %s, expected %s", data.Origin, origin) } ret := data.Return if !bytes.Equal(ret, returnCode) { - return true, fmt.Errorf("Call did not return correctly. Got %x, expected %x", ret, returnCode) + return true, fmt.Errorf("call did not return correctly. Got %x, expected %x", ret, returnCode) } if !bytes.Equal(data.TxID, *txid) { return true, fmt.Errorf("TxIDs do not match up! Got %x, expected %x", @@ -292,12 +295,11 @@ func unmarshalValidateCall(origin, } } -func readResult(t *testing.T, bs []byte) ctypes.BurrowResult { - var err error - result := new(ctypes.BurrowResult) - wire.ReadJSONPtr(result, bs, &err) +func readResult(t *testing.T, bs []byte) rpc.ResultInner { + result := new(rpc.Result) + err := json.Unmarshal(bs, result) if err != nil { - t.Fatal(err) + require.NoError(t, err) } - return *result + return result.ResultInner } diff --git a/rpc/tm/method/method.go b/rpc/tm/method/method.go new file mode 100644 index 0000000000000000000000000000000000000000..927c2b4fd3529df075aa449a97b18892c2f4b8e1 --- /dev/null +++ b/rpc/tm/method/method.go @@ -0,0 +1,40 @@ +package method + +const ( + Subscribe = "subscribe" + Unsubscribe = "unsubscribe" + + // Status + Status = "status" + NetInfo = "net_info" + + // Accounts + ListAccounts = "list_accounts" + GetAccount = "get_account" + GetStorage = "get_storage" + DumpStorage = "dump_storage" + + // Simulated call + Call = "call" + CallCode = "call_code" + + // Names + GetName = "get_name" + ListNames = "list_names" + BroadcastTx = "broadcast_tx" + + // Blockchain + Genesis = "genesis" + ChainID = "chain_id" + Blockchain = "blockchain" + GetBlock = "get_block" + + // Consensus + ListUnconfirmedTxs = "list_unconfirmed_txs" + ListValidators = "list_validators" + DumpConsensusState = "dump_consensus_state" + + // Private keys and signing + GeneratePrivateAccount = "unsafe/gen_priv_account" + SignTx = "unsafe/sign_tx" +) diff --git a/rpc/tm/routes.go b/rpc/tm/routes.go new file mode 100644 index 0000000000000000000000000000000000000000..8223795f7b6efb2c4da03b4c55a82b1ae9439209 --- /dev/null +++ b/rpc/tm/routes.go @@ -0,0 +1,141 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tm + +import ( + "fmt" + "reflect" + + "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/rpc" + "github.com/hyperledger/burrow/rpc/tm/method" + "github.com/hyperledger/burrow/txs" + gorpc "github.com/tendermint/tendermint/rpc/lib/server" + "github.com/tendermint/tendermint/rpc/lib/types" +) + +func GetRoutes(service rpc.Service) map[string]*gorpc.RPCFunc { + return map[string]*gorpc.RPCFunc{ + // Transact + method.BroadcastTx: gorpc.NewRPCFunc(func(tx txs.Wrapper) (rpc.Result, error) { + return wrapReturnBurrowResult(service.BroadcastTx(tx.Unwrap())) + }, "tx"), + // Events + method.Subscribe: gorpc.NewWSRPCFunc(func(wsCtx rpctypes.WSRPCContext, + eventId string) (rpc.Result, error) { + return wrapReturnBurrowResult(service.Subscribe(eventId, + func(eventData event.AnyEventData) { + // NOTE: EventSwitch callbacks must be nonblocking + wsCtx.TryWriteRPCResponse(rpctypes.NewRPCSuccessResponse(wsCtx.Request.ID+"#event", + rpc.ResultEvent{Event: eventId, AnyEventData: eventData}.Wrap())) + })) + }, "eventId"), + method.Unsubscribe: gorpc.NewWSRPCFunc(func(wsCtx rpctypes.WSRPCContext, + subscriptionId string) (rpc.Result, error) { + return wrapReturnBurrowResult(service.Unsubscribe(subscriptionId)) + }, "subscriptionId"), + // Status + method.Status: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.Status), ""), + method.NetInfo: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.NetInfo), ""), + // Accounts + method.ListAccounts: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.ListAccounts), ""), + method.GetAccount: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.GetAccount), "address"), + method.GetStorage: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.GetStorage), "address,key"), + method.DumpStorage: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.DumpStorage), "address"), + // Simulated call + method.Call: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.Call), "fromAddress,toAddress,data"), + method.CallCode: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.CallCode), "fromAddress,code,data"), + // Blockchain + method.Genesis: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.Genesis), ""), + method.ChainID: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.ChainId), ""), + method.Blockchain: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.BlockchainInfo), "minHeight,maxHeight"), + method.GetBlock: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.GetBlock), "height"), + // Consensus + method.ListUnconfirmedTxs: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.ListUnconfirmedTxs), "maxTxs"), + method.ListValidators: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.ListValidators), ""), + method.DumpConsensusState: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.DumpConsensusState), ""), + // Names + method.GetName: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.GetName), "name"), + method.ListNames: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.ListNames), ""), + // Private keys and signing + method.GeneratePrivateAccount: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.GeneratePrivateAccount), ""), + method.SignTx: gorpc.NewRPCFunc(mustWrapFuncBurrowResult(service.SignTx), "tx,privAccounts"), + } +} + +func mustWrapFuncBurrowResult(f interface{}) interface{} { + wrapped, err := wrapFuncBurrowResult(f) + if err != nil { + panic(fmt.Errorf("must be able to wrap RPC function: %v", err)) + } + return wrapped +} + +// Takes a function with a covariant return type in func(args...) (ResultInner, error) +// and returns it as func(args...) (Result, error) so it can be serialised with the mapper +func wrapFuncBurrowResult(f interface{}) (interface{}, error) { + rv := reflect.ValueOf(f) + rt := rv.Type() + if rt.Kind() != reflect.Func { + return nil, fmt.Errorf("must be passed a func f, but got: %#v", f) + } + + in := make([]reflect.Type, rt.NumIn()) + for i := 0; i < rt.NumIn(); i++ { + in[i] = rt.In(i) + } + + if rt.NumOut() != 2 { + return nil, fmt.Errorf("expects f to return the pair of ResultInner, error but got %v return types", + rt.NumOut()) + } + + out := make([]reflect.Type, 2) + err := checkTypeImplements(rt.Out(0), (*rpc.ResultInner)(nil)) + if err != nil { + return nil, fmt.Errorf("wrong first return type: %v", err) + } + err = checkTypeImplements(rt.Out(1), (*error)(nil)) + if err != nil { + return nil, fmt.Errorf("wrong second return type: %v", err) + } + + out[0] = reflect.TypeOf(rpc.Result{}) + out[1] = rt.Out(1) + + return reflect.MakeFunc(reflect.FuncOf(in, out, false), + func(args []reflect.Value) []reflect.Value { + ret := rv.Call(args) + burrowResult := reflect.New(out[0]) + burrowResult.Elem().Field(0).Set(ret[0]) + ret[0] = burrowResult.Elem() + return ret + }).Interface(), nil +} + +func wrapReturnBurrowResult(result rpc.ResultInner, err error) (rpc.Result, error) { + return rpc.Result{ResultInner: result}, err +} + +// Passed a type and a pointer to an interface value checks that typ implements that interface +// returning a human-readable error if it does not +// (e.g. ifacePtr := (*MyInterface)(nil) can be a reasonably convenient stand-in for an actual type literal), +func checkTypeImplements(typ reflect.Type, ifacePtr interface{}) error { + ifaceType := reflect.TypeOf(ifacePtr).Elem() + if !typ.Implements(ifaceType) { + return fmt.Errorf("%s does not implement interface %s", typ, ifaceType) + } + return nil +} diff --git a/rpc/tm/routes_test.go b/rpc/tm/routes_test.go new file mode 100644 index 0000000000000000000000000000000000000000..2136c389cdac0d1217971f8b38c308ba8a39873f --- /dev/null +++ b/rpc/tm/routes_test.go @@ -0,0 +1,29 @@ +package tm + +import ( + "testing" + + "github.com/hyperledger/burrow/rpc" + "github.com/stretchr/testify/require" +) + +func testChainId(chainName string) (*rpc.ResultChainId, error) { + return &rpc.ResultChainId{ + ChainName: chainName, + ChainId: "Foos", + GenesisHash: []byte{}, + }, nil +} + +func TestWrapFuncBurrowResult(t *testing.T) { + f, err := wrapFuncBurrowResult(testChainId) + require.NoError(t, err) + fOut, ok := f.(func(string) (rpc.Result, error)) + require.True(t, ok, "must be able to cast to function type") + br, err := fOut("Blum") + require.NoError(t, err) + bs, err := br.MarshalJSON() + require.NoError(t, err) + require.Equal(t, `{"type":"result_chain_id","data":{"chain_name":"Blum","chain_id":"Foos","genesis_hash":""}}`, + string(bs)) +} diff --git a/rpc/tm/server.go b/rpc/tm/server.go new file mode 100644 index 0000000000000000000000000000000000000000..13726d58c0493bfc676f1815deb68c794acf64e8 --- /dev/null +++ b/rpc/tm/server.go @@ -0,0 +1,44 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tm + +import ( + "net" + "net/http" + + "github.com/hyperledger/burrow/consensus/tendermint" + "github.com/hyperledger/burrow/logging/structure" + logging_types "github.com/hyperledger/burrow/logging/types" + "github.com/hyperledger/burrow/rpc" + "github.com/tendermint/tendermint/rpc/lib/server" + "github.com/tendermint/tmlibs/events" +) + +func StartServer(service rpc.Service, pattern, listenAddress string, evsw events.EventSwitch, + logger logging_types.InfoTraceLogger) (net.Listener, error) { + + logger = logger.With(structure.ComponentKey, "RPC_TM") + routes := GetRoutes(service) + mux := http.NewServeMux() + wm := rpcserver.NewWebsocketManager(routes, evsw) + mux.HandleFunc(pattern, wm.WebsocketHandler) + tmLogger := tendermint.NewLogger(logger) + rpcserver.RegisterRPCFuncs(mux, routes, tmLogger) + listener, err := rpcserver.StartHTTPServer(listenAddress, mux, tmLogger) + if err != nil { + return nil, err + } + return listener, nil +}