diff options
Diffstat (limited to 'eth/api.go')
-rw-r--r-- | eth/api.go | 492 |
1 files changed, 372 insertions, 120 deletions
diff --git a/eth/api.go b/eth/api.go index a1630e2d1..0fa855f15 100644 --- a/eth/api.go +++ b/eth/api.go @@ -42,8 +42,10 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/miner" + "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rlp" - rpc "github.com/ethereum/go-ethereum/rpc/v2" + "github.com/ethereum/go-ethereum/rpc" ) const ( @@ -51,6 +53,40 @@ const ( defaultGas = uint64(90000) ) +// blockByNumber is a commonly used helper function which retrieves and returns +// the block for the given block number, capable of handling two special blocks: +// rpc.LatestBlockNumber adn rpc.PendingBlockNumber. It returns nil when no block +// could be found. +func blockByNumber(m *miner.Miner, bc *core.BlockChain, blockNr rpc.BlockNumber) *types.Block { + // Pending block is only known by the miner + if blockNr == rpc.PendingBlockNumber { + return m.PendingBlock() + } + // Otherwise resolve and return the block + if blockNr == rpc.LatestBlockNumber { + return bc.CurrentBlock() + } + return bc.GetBlockByNumber(uint64(blockNr)) +} + +// stateAndBlockByNumber is a commonly used helper function which retrieves and +// returns the state and containing block for the given block number, capable of +// handling two special states: rpc.LatestBlockNumber adn rpc.PendingBlockNumber. +// It returns nil when no block or state could be found. +func stateAndBlockByNumber(m *miner.Miner, bc *core.BlockChain, blockNr rpc.BlockNumber, chainDb ethdb.Database) (*state.StateDB, *types.Block, error) { + // Pending state is only known by the miner + if blockNr == rpc.PendingBlockNumber { + return m.PendingState(), m.PendingBlock(), nil + } + // Otherwise resolve the block number and return its state + block := blockByNumber(m, bc, blockNr) + if block == nil { + return nil, nil, nil + } + stateDb, err := state.New(block.Root(), chainDb) + return stateDb, block, err +} + // PublicEthereumAPI provides an API to access Ethereum related information. // It offers only methods that operate on public data that is freely available to anyone. type PublicEthereumAPI struct { @@ -212,6 +248,39 @@ func NewPublicTxPoolAPI(e *Ethereum) *PublicTxPoolAPI { return &PublicTxPoolAPI{e} } +// Content returns the transactions contained within the transaction pool. +func (s *PublicTxPoolAPI) Content() map[string]map[string]map[string][]*RPCTransaction { + content := map[string]map[string]map[string][]*RPCTransaction{ + "pending": make(map[string]map[string][]*RPCTransaction), + "queued": make(map[string]map[string][]*RPCTransaction), + } + pending, queue := s.e.TxPool().Content() + + // Flatten the pending transactions + for account, batches := range pending { + dump := make(map[string][]*RPCTransaction) + for nonce, txs := range batches { + nonce := fmt.Sprintf("%d", nonce) + for _, tx := range txs { + dump[nonce] = append(dump[nonce], newRPCPendingTransaction(tx)) + } + } + content["pending"][account.Hex()] = dump + } + // Flatten the queued transactions + for account, batches := range queue { + dump := make(map[string][]*RPCTransaction) + for nonce, txs := range batches { + nonce := fmt.Sprintf("%d", nonce) + for _, tx := range txs { + dump[nonce] = append(dump[nonce], newRPCPendingTransaction(tx)) + } + } + content["queued"][account.Hex()] = dump + } + return content +} + // Status returns the number of pending and queued transaction in the pool. func (s *PublicTxPoolAPI) Status() map[string]*rpc.HexNumber { pending, queue := s.e.TxPool().Stats() @@ -221,6 +290,47 @@ func (s *PublicTxPoolAPI) Status() map[string]*rpc.HexNumber { } } +// Inspect retrieves the content of the transaction pool and flattens it into an +// easily inspectable list. +func (s *PublicTxPoolAPI) Inspect() map[string]map[string]map[string][]string { + content := map[string]map[string]map[string][]string{ + "pending": make(map[string]map[string][]string), + "queued": make(map[string]map[string][]string), + } + pending, queue := s.e.TxPool().Content() + + // Define a formatter to flatten a transaction into a string + var format = func(tx *types.Transaction) string { + if to := tx.To(); to != nil { + return fmt.Sprintf("%s: %v wei + %v × %v gas", tx.To().Hex(), tx.Value(), tx.Gas(), tx.GasPrice()) + } + return fmt.Sprintf("contract creation: %v wei + %v × %v gas", tx.Value(), tx.Gas(), tx.GasPrice()) + } + // Flatten the pending transactions + for account, batches := range pending { + dump := make(map[string][]string) + for nonce, txs := range batches { + nonce := fmt.Sprintf("%d", nonce) + for _, tx := range txs { + dump[nonce] = append(dump[nonce], format(tx)) + } + } + content["pending"][account.Hex()] = dump + } + // Flatten the queued transactions + for account, batches := range queue { + dump := make(map[string][]string) + for nonce, txs := range batches { + nonce := fmt.Sprintf("%d", nonce) + for _, tx := range txs { + dump[nonce] = append(dump[nonce], format(tx)) + } + } + content["queued"][account.Hex()] = dump + } + return content +} + // PublicAccountAPI provides an API to access accounts managed by this node. // It offers only methods that can retrieve accounts. type PublicAccountAPI struct { @@ -293,11 +403,12 @@ type PublicBlockChainAPI struct { chainDb ethdb.Database eventMux *event.TypeMux am *accounts.Manager + miner *miner.Miner } // NewPublicBlockChainAPI creates a new Etheruem blockchain API. -func NewPublicBlockChainAPI(bc *core.BlockChain, chainDb ethdb.Database, eventMux *event.TypeMux, am *accounts.Manager) *PublicBlockChainAPI { - return &PublicBlockChainAPI{bc: bc, chainDb: chainDb, eventMux: eventMux, am: am} +func NewPublicBlockChainAPI(bc *core.BlockChain, m *miner.Miner, chainDb ethdb.Database, eventMux *event.TypeMux, am *accounts.Manager) *PublicBlockChainAPI { + return &PublicBlockChainAPI{bc: bc, miner: m, chainDb: chainDb, eventMux: eventMux, am: am} } // BlockNumber returns the block number of the chain head. @@ -305,36 +416,29 @@ func (s *PublicBlockChainAPI) BlockNumber() *big.Int { return s.bc.CurrentHeader().Number } -// GetBalance returns the amount of wei for the given address in the state of the given block number. -// When block number equals rpc.LatestBlockNumber the current block is used. +// GetBalance returns the amount of wei for the given address in the state of the +// given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta +// block numbers are also allowed. func (s *PublicBlockChainAPI) GetBalance(address common.Address, blockNr rpc.BlockNumber) (*big.Int, error) { - block := blockByNumber(s.bc, blockNr) - if block == nil { - return nil, nil - } - - state, err := state.New(block.Root(), s.chainDb) - if err != nil { + state, _, err := stateAndBlockByNumber(s.miner, s.bc, blockNr, s.chainDb) + if state == nil || err != nil { return nil, err } return state.GetBalance(address), nil } -// blockByNumber is a commonly used helper function which retrieves and returns the block for the given block number. It -// returns nil when no block could be found. -func blockByNumber(bc *core.BlockChain, blockNr rpc.BlockNumber) *types.Block { - if blockNr == rpc.LatestBlockNumber { - return bc.CurrentBlock() - } - - return bc.GetBlockByNumber(uint64(blockNr)) -} - // GetBlockByNumber returns the requested block. When blockNr is -1 the chain head is returned. When fullTx is true all // transactions in the block are returned in full detail, otherwise only the transaction hash is returned. func (s *PublicBlockChainAPI) GetBlockByNumber(blockNr rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { - if block := blockByNumber(s.bc, blockNr); block != nil { - return s.rpcOutputBlock(block, true, fullTx) + if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { + response, err := s.rpcOutputBlock(block, true, fullTx) + if err == nil && blockNr == rpc.PendingBlockNumber { + // Pending blocks need to nil out a few fields + for _, field := range []string{"hash", "nonce", "logsBloom", "miner"} { + response[field] = nil + } + } + return response, err } return nil, nil } @@ -351,11 +455,7 @@ func (s *PublicBlockChainAPI) GetBlockByHash(blockHash common.Hash, fullTx bool) // GetUncleByBlockNumberAndIndex returns the uncle block for the given block hash and index. When fullTx is true // all transactions in the block are returned in full detail, otherwise only the transaction hash is returned. func (s *PublicBlockChainAPI) GetUncleByBlockNumberAndIndex(blockNr rpc.BlockNumber, index rpc.HexNumber) (map[string]interface{}, error) { - if blockNr == rpc.PendingBlockNumber { - return nil, nil - } - - if block := blockByNumber(s.bc, blockNr); block != nil { + if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { uncles := block.Uncles() if index.Int() < 0 || index.Int() >= len(uncles) { glog.V(logger.Debug).Infof("uncle block on index %d not found for block #%d", index.Int(), blockNr) @@ -384,11 +484,7 @@ func (s *PublicBlockChainAPI) GetUncleByBlockHashAndIndex(blockHash common.Hash, // GetUncleCountByBlockNumber returns number of uncles in the block for the given block number func (s *PublicBlockChainAPI) GetUncleCountByBlockNumber(blockNr rpc.BlockNumber) *rpc.HexNumber { - if blockNr == rpc.PendingBlockNumber { - return rpc.NewHexNumber(0) - } - - if block := blockByNumber(s.bc, blockNr); block != nil { + if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { return rpc.NewHexNumber(len(block.Uncles())) } return nil @@ -428,38 +524,26 @@ func (s *PublicBlockChainAPI) NewBlocks(args NewBlocksArgs) (rpc.Subscription, e // GetCode returns the code stored at the given address in the state for the given block number. func (s *PublicBlockChainAPI) GetCode(address common.Address, blockNr rpc.BlockNumber) (string, error) { - return s.GetData(address, blockNr) -} - -// GetData returns the data stored at the given address in the state for the given block number. -func (s *PublicBlockChainAPI) GetData(address common.Address, blockNr rpc.BlockNumber) (string, error) { - if block := blockByNumber(s.bc, blockNr); block != nil { - state, err := state.New(block.Root(), s.chainDb) - if err != nil { - return "", err - } - res := state.GetCode(address) - if len(res) == 0 { // backwards compatibility - return "0x", nil - } - return common.ToHex(res), nil + state, _, err := stateAndBlockByNumber(s.miner, s.bc, blockNr, s.chainDb) + if state == nil || err != nil { + return "", err } - - return "0x", nil + res := state.GetCode(address) + if len(res) == 0 { // backwards compatibility + return "0x", nil + } + return common.ToHex(res), nil } -// GetStorageAt returns the storage from the state at the given address, key and block number. +// GetStorageAt returns the storage from the state at the given address, key and +// block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta block +// numbers are also allowed. func (s *PublicBlockChainAPI) GetStorageAt(address common.Address, key string, blockNr rpc.BlockNumber) (string, error) { - if block := blockByNumber(s.bc, blockNr); block != nil { - state, err := state.New(block.Root(), s.chainDb) - if err != nil { - return "", err - } - - return state.GetState(address, common.HexToHash(key)).Hex(), nil + state, _, err := stateAndBlockByNumber(s.miner, s.bc, blockNr, s.chainDb) + if state == nil || err != nil { + return "0x", err } - - return "0x", nil + return state.GetState(address, common.HexToHash(key)).Hex(), nil } // callmsg is the message type used for call transations. @@ -490,55 +574,51 @@ type CallArgs struct { } func (s *PublicBlockChainAPI) doCall(args CallArgs, blockNr rpc.BlockNumber) (string, *big.Int, error) { - if block := blockByNumber(s.bc, blockNr); block != nil { - stateDb, err := state.New(block.Root(), s.chainDb) - if err != nil { - return "0x", nil, err - } - - stateDb = stateDb.Copy() - var from *state.StateObject - if args.From == (common.Address{}) { - accounts, err := s.am.Accounts() - if err != nil || len(accounts) == 0 { - from = stateDb.GetOrNewStateObject(common.Address{}) - } else { - from = stateDb.GetOrNewStateObject(accounts[0].Address) - } + // Fetch the state associated with the block number + stateDb, block, err := stateAndBlockByNumber(s.miner, s.bc, blockNr, s.chainDb) + if stateDb == nil || err != nil { + return "0x", nil, err + } + stateDb = stateDb.Copy() + + // Retrieve the account state object to interact with + var from *state.StateObject + if args.From == (common.Address{}) { + accounts, err := s.am.Accounts() + if err != nil || len(accounts) == 0 { + from = stateDb.GetOrNewStateObject(common.Address{}) } else { - from = stateDb.GetOrNewStateObject(args.From) - } - - from.SetBalance(common.MaxBig) - - msg := callmsg{ - from: from, - to: &args.To, - gas: args.Gas.BigInt(), - gasPrice: args.GasPrice.BigInt(), - value: args.Value.BigInt(), - data: common.FromHex(args.Data), - } - - if msg.gas.Cmp(common.Big0) == 0 { - msg.gas = big.NewInt(50000000) - } - - if msg.gasPrice.Cmp(common.Big0) == 0 { - msg.gasPrice = new(big.Int).Mul(big.NewInt(50), common.Shannon) + from = stateDb.GetOrNewStateObject(accounts[0].Address) } + } else { + from = stateDb.GetOrNewStateObject(args.From) + } + from.SetBalance(common.MaxBig) - header := s.bc.CurrentBlock().Header() - vmenv := core.NewEnv(stateDb, s.bc, msg, header) - gp := new(core.GasPool).AddGas(common.MaxBig) - res, gas, err := core.ApplyMessage(vmenv, msg, gp) - if len(res) == 0 { // backwards compatability - return "0x", gas, err - } - return common.ToHex(res), gas, err + // Assemble the CALL invocation + msg := callmsg{ + from: from, + to: &args.To, + gas: args.Gas.BigInt(), + gasPrice: args.GasPrice.BigInt(), + value: args.Value.BigInt(), + data: common.FromHex(args.Data), } + if msg.gas.Cmp(common.Big0) == 0 { + msg.gas = big.NewInt(50000000) + } + if msg.gasPrice.Cmp(common.Big0) == 0 { + msg.gasPrice = new(big.Int).Mul(big.NewInt(50), common.Shannon) + } + // Execute the call and return + vmenv := core.NewEnv(stateDb, s.bc, msg, block.Header()) + gp := new(core.GasPool).AddGas(common.MaxBig) - return "0x", common.Big0, nil + res, gas, err := core.ApplyMessage(vmenv, msg, gp) + if len(res) == 0 { // backwards compatibility + return "0x", gas, err + } + return common.ToHex(res), gas, err } // Call executes the given transaction on the state for the given block number. @@ -684,19 +764,21 @@ type PublicTransactionPoolAPI struct { eventMux *event.TypeMux chainDb ethdb.Database bc *core.BlockChain + miner *miner.Miner am *accounts.Manager txPool *core.TxPool txMu sync.Mutex } // NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool. -func NewPublicTransactionPoolAPI(txPool *core.TxPool, chainDb ethdb.Database, eventMux *event.TypeMux, bc *core.BlockChain, am *accounts.Manager) *PublicTransactionPoolAPI { +func NewPublicTransactionPoolAPI(txPool *core.TxPool, m *miner.Miner, chainDb ethdb.Database, eventMux *event.TypeMux, bc *core.BlockChain, am *accounts.Manager) *PublicTransactionPoolAPI { return &PublicTransactionPoolAPI{ eventMux: eventMux, chainDb: chainDb, bc: bc, am: am, txPool: txPool, + miner: m, } } @@ -720,14 +802,9 @@ func getTransaction(chainDb ethdb.Database, txPool *core.TxPool, txHash common.H // GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number. func (s *PublicTransactionPoolAPI) GetBlockTransactionCountByNumber(blockNr rpc.BlockNumber) *rpc.HexNumber { - if blockNr == rpc.PendingBlockNumber { - return rpc.NewHexNumber(0) - } - - if block := blockByNumber(s.bc, blockNr); block != nil { + if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { return rpc.NewHexNumber(len(block.Transactions())) } - return nil } @@ -741,7 +818,7 @@ func (s *PublicTransactionPoolAPI) GetBlockTransactionCountByHash(blockHash comm // GetTransactionByBlockNumberAndIndex returns the transaction for the given block number and index. func (s *PublicTransactionPoolAPI) GetTransactionByBlockNumberAndIndex(blockNr rpc.BlockNumber, index rpc.HexNumber) (*RPCTransaction, error) { - if block := blockByNumber(s.bc, blockNr); block != nil { + if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { return newRPCTransactionFromBlockIndex(block, index.Int()) } return nil, nil @@ -757,13 +834,8 @@ func (s *PublicTransactionPoolAPI) GetTransactionByBlockHashAndIndex(blockHash c // GetTransactionCount returns the number of transactions the given address has sent for the given block number func (s *PublicTransactionPoolAPI) GetTransactionCount(address common.Address, blockNr rpc.BlockNumber) (*rpc.HexNumber, error) { - block := blockByNumber(s.bc, blockNr) - if block == nil { - return nil, nil - } - - state, err := state.New(block.Root(), s.chainDb) - if err != nil { + state, _, err := stateAndBlockByNumber(s.miner, s.bc, blockNr, s.chainDb) + if state == nil || err != nil { return nil, err } return rpc.NewHexNumber(state.GetNonce(address)), nil @@ -1256,6 +1328,16 @@ func (api *PrivateAdminAPI) ExportChain(file string) (bool, error) { return true, nil } +func hasAllBlocks(chain *core.BlockChain, bs []*types.Block) bool { + for _, b := range bs { + if !chain.HasBlock(b.Hash()) { + return false + } + } + + return true +} + // ImportChain imports a blockchain from a local file. func (api *PrivateAdminAPI) ImportChain(file string) (bool, error) { // Make sure the can access the file to import @@ -1284,6 +1366,11 @@ func (api *PrivateAdminAPI) ImportChain(file string) (bool, error) { if len(blocks) == 0 { break } + + if hasAllBlocks(api.eth.BlockChain(), blocks) { + blocks = blocks[:0] + continue + } // Import the batch and reset the buffer if _, err := api.eth.BlockChain().InsertChain(blocks); err != nil { return false, fmt.Errorf("batch %d: failed to insert: %v", batch, err) @@ -1403,3 +1490,168 @@ func (api *PrivateDebugAPI) ProcessBlock(number uint64) (bool, error) { func (api *PrivateDebugAPI) SetHead(number uint64) { api.eth.BlockChain().SetHead(number) } + +// StructLogRes stores a structured log emitted by the evm while replaying a +// transaction in debug mode +type structLogRes struct { + Pc uint64 `json:"pc"` + Op string `json:"op"` + Gas *big.Int `json:"gas"` + GasCost *big.Int `json:"gasCost"` + Error error `json:"error"` + Stack []string `json:"stack"` + Memory map[string]string `json:"memory"` + Storage map[string]string `json:"storage"` +} + +// TransactionExecutionRes groups all structured logs emitted by the evm +// while replaying a transaction in debug mode as well as the amount of +// gas used and the return value +type TransactionExecutionResult struct { + Gas *big.Int `json:"gas"` + ReturnValue string `json:"returnValue"` + StructLogs []structLogRes `json:"structLogs"` +} + +func (s *PrivateDebugAPI) doReplayTransaction(txHash common.Hash) ([]vm.StructLog, []byte, *big.Int, error) { + // Retrieve the tx from the chain + tx, _, blockIndex, _ := core.GetTransaction(s.eth.ChainDb(), txHash) + + if tx == nil { + return nil, nil, nil, fmt.Errorf("Transaction not found") + } + + block := s.eth.BlockChain().GetBlockByNumber(blockIndex - 1) + if block == nil { + return nil, nil, nil, fmt.Errorf("Unable to retrieve prior block") + } + + // Create the state database + stateDb, err := state.New(block.Root(), s.eth.ChainDb()) + if err != nil { + return nil, nil, nil, err + } + + txFrom, err := tx.From() + + if err != nil { + return nil, nil, nil, fmt.Errorf("Unable to create transaction sender") + } + from := stateDb.GetOrNewStateObject(txFrom) + msg := callmsg{ + from: from, + to: tx.To(), + gas: tx.Gas(), + gasPrice: tx.GasPrice(), + value: tx.Value(), + data: tx.Data(), + } + + vmenv := core.NewEnv(stateDb, s.eth.BlockChain(), msg, block.Header()) + gp := new(core.GasPool).AddGas(block.GasLimit()) + vm.GenerateStructLogs = true + defer func() { vm.GenerateStructLogs = false }() + + ret, gas, err := core.ApplyMessage(vmenv, msg, gp) + if err != nil { + return nil, nil, nil, fmt.Errorf("Error executing transaction %v", err) + } + + return vmenv.StructLogs(), ret, gas, nil +} + +// Executes a transaction and returns the structured logs of the evm +// gathered during the execution +func (s *PrivateDebugAPI) ReplayTransaction(txHash common.Hash, stackDepth int, memorySize int, storageSize int) (*TransactionExecutionResult, error) { + + structLogs, ret, gas, err := s.doReplayTransaction(txHash) + + if err != nil { + return nil, err + } + + res := TransactionExecutionResult{ + Gas: gas, + ReturnValue: fmt.Sprintf("%x", ret), + StructLogs: make([]structLogRes, len(structLogs)), + } + + for index, trace := range structLogs { + + stackLength := len(trace.Stack) + + // Return full stack by default + if stackDepth != -1 && stackDepth < stackLength { + stackLength = stackDepth + } + + res.StructLogs[index] = structLogRes{ + Pc: trace.Pc, + Op: trace.Op.String(), + Gas: trace.Gas, + GasCost: trace.GasCost, + Error: trace.Err, + Stack: make([]string, stackLength), + Memory: make(map[string]string), + Storage: make(map[string]string), + } + + for i := 0; i < stackLength; i++ { + res.StructLogs[index].Stack[i] = fmt.Sprintf("%x", common.LeftPadBytes(trace.Stack[i].Bytes(), 32)) + } + + addr := 0 + memorySizeLocal := memorySize + + // Return full memory by default + if memorySize == -1 { + memorySizeLocal = len(trace.Memory) + } + + for i := 0; i+16 <= len(trace.Memory) && addr < memorySizeLocal; i += 16 { + res.StructLogs[index].Memory[fmt.Sprintf("%04d", addr*16)] = fmt.Sprintf("%x", trace.Memory[i:i+16]) + addr++ + } + + storageLength := len(trace.Stack) + if storageSize != -1 && storageSize < storageLength { + storageLength = storageSize + } + + i := 0 + for storageIndex, storageValue := range trace.Storage { + if i >= storageLength { + break + } + res.StructLogs[index].Storage[fmt.Sprintf("%x", storageIndex)] = fmt.Sprintf("%x", storageValue) + i++ + } + } + return &res, nil +} + +// PublicNetAPI offers network related RPC methods +type PublicNetAPI struct { + net *p2p.Server + networkVersion int +} + +// NewPublicNetAPI creates a new net api instance. +func NewPublicNetAPI(net *p2p.Server, networkVersion int) *PublicNetAPI { + return &PublicNetAPI{net, networkVersion} +} + +// Listening returns an indication if the node is listening for network connections. +func (s *PublicNetAPI) Listening() bool { + return true // always listening +} + +// Peercount returns the number of connected peers +func (s *PublicNetAPI) PeerCount() *rpc.HexNumber { + return rpc.NewHexNumber(s.net.PeerCount()) +} + +// ProtocolVersion returns the current ethereum protocol version. +func (s *PublicNetAPI) Version() string { + return fmt.Sprintf("%d", s.networkVersion) +} |