From b950a2977c6d74c9150fd37b7cf7fb20c762baf7 Mon Sep 17 00:00:00 2001 From: Bas van Kervel Date: Thu, 12 May 2016 19:32:04 +0200 Subject: [PATCH 1/8] [release/1.4.5] eth: add new RPC method (personal.) SignAndSendTransaction (cherry picked from commit 64a6c2c1b6c81fddccc7d3d728b7a05c5814124b) Conflicts: cmd/geth/js.go internal/web3ext/web3ext.go --- accounts/account_manager.go | 16 +++- accounts/accounts_test.go | 28 ++++++ cmd/geth/js.go | 2 +- eth/api.go | 180 ++++++++++++++++++++++-------------- eth/backend.go | 4 +- internal/web3ext/web3ext.go | 76 +++++++++------ 6 files changed, 207 insertions(+), 99 deletions(-) diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 3afadf6b22a3..bfb7556d6a31 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -147,9 +147,21 @@ func (am *Manager) Sign(addr common.Address, hash []byte) (signature []byte, err return crypto.Sign(hash, unlockedKey.PrivateKey) } +// SignWithPassphrase signs hash if the private key matching the given address can be +// decrypted with the given passphrase. +func (am *Manager) SignWithPassphrase(addr common.Address, passphrase string, hash []byte) (signature []byte, err error) { + _, key, err := am.getDecryptedKey(Account{Address: addr}, passphrase) + if err != nil { + return nil, err + } + + defer zeroKey(key.PrivateKey) + return crypto.Sign(hash, key.PrivateKey) +} + // Unlock unlocks the given account indefinitely. -func (am *Manager) Unlock(a Account, keyAuth string) error { - return am.TimedUnlock(a, keyAuth, 0) +func (am *Manager) Unlock(a Account, passphrase string) error { + return am.TimedUnlock(a, passphrase, 0) } // Lock removes the private key with the given address from memory. diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go index 829cf39683c6..2e5f2b44a473 100644 --- a/accounts/accounts_test.go +++ b/accounts/accounts_test.go @@ -81,6 +81,34 @@ func TestSign(t *testing.T) { } } +func TestSignWithPassphrase(t *testing.T) { + dir, am := tmpManager(t, true) + defer os.RemoveAll(dir) + + pass := "passwd" + acc, err := am.NewAccount(pass) + if err != nil { + t.Fatal(err) + } + + if _, unlocked := am.unlocked[acc.Address]; unlocked { + t.Fatal("expected account to be locked") + } + + _, err = am.SignWithPassphrase(acc.Address, pass, testSigData) + if err != nil { + t.Fatal(err) + } + + if _, unlocked := am.unlocked[acc.Address]; unlocked { + t.Fatal("expected account to be locked") + } + + if _, err = am.SignWithPassphrase(acc.Address, "invalid passwd", testSigData); err == nil { + t.Fatal("expected SignHash to fail with invalid password") + } +} + func TestTimedUnlock(t *testing.T) { dir, am := tmpManager(t, true) defer os.RemoveAll(dir) diff --git a/cmd/geth/js.go b/cmd/geth/js.go index 2b64303b2bbc..69c03f4c4011 100644 --- a/cmd/geth/js.go +++ b/cmd/geth/js.go @@ -41,8 +41,8 @@ import ( ) var ( - passwordRegexp = regexp.MustCompile("personal.[nu]") leadingSpace = regexp.MustCompile("^ ") + passwordRegexp = regexp.MustCompile("personal.[nus]") onlyws = regexp.MustCompile("^\\s*$") exit = regexp.MustCompile("^\\s*exit\\s*;*\\s*$") ) diff --git a/eth/api.go b/eth/api.go index 1d66f53fe53b..1f9fe5d558cb 100644 --- a/eth/api.go +++ b/eth/api.go @@ -148,7 +148,7 @@ func (s *PublicEthereumAPI) Etherbase() (common.Address, error) { return s.e.Etherbase() } -// see Etherbase +// Coinbase is the address that mining rewards will be send to (alias for Etherbase) func (s *PublicEthereumAPI) Coinbase() (common.Address, error) { return s.Etherbase() } @@ -217,18 +217,17 @@ func (s *PublicMinerAPI) SubmitWork(nonce rpc.HexNumber, solution, digest common // result[0], 32 bytes hex encoded current block header pow-hash // result[1], 32 bytes hex encoded seed hash used for DAG // result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty -func (s *PublicMinerAPI) GetWork() ([]string, error) { +func (s *PublicMinerAPI) GetWork() (work [3]string, err error) { if !s.e.IsMining() { if err := s.e.StartMining(0, ""); err != nil { - return nil, err + return work, err } } - if work, err := s.agent.GetWork(); err == nil { - return work[:], nil - } else { - glog.Infof("%v\n", err) + if work, err = s.agent.GetWork(); err == nil { + return } - return nil, fmt.Errorf("mining not ready") + glog.V(logger.Debug).Infof("%v", err) + return work, fmt.Errorf("mining not ready") } // SubmitHashrate can be used for remote miners to submit their hash rate. This enables the node to report the combined @@ -423,14 +422,23 @@ func (s *PublicAccountAPI) Accounts() []accounts.Account { } // PrivateAccountAPI provides an API to access accounts managed by this node. -// It offers methods to create, (un)lock en list accounts. +// It offers methods to create, (un)lock en list accounts. Some methods accept +// passwords and are therefore considered private by default. type PrivateAccountAPI struct { - am *accounts.Manager + am *accounts.Manager + txPool *core.TxPool + txMu *sync.Mutex + gpo *GasPriceOracle } // NewPrivateAccountAPI create a new PrivateAccountAPI. -func NewPrivateAccountAPI(am *accounts.Manager) *PrivateAccountAPI { - return &PrivateAccountAPI{am} +func NewPrivateAccountAPI(e *Ethereum) *PrivateAccountAPI { + return &PrivateAccountAPI{ + am: e.accountManager, + txPool: e.txPool, + txMu: &e.txMu, + gpo: e.gpo, + } } // ListAccounts will return a list of addresses for accounts this node manages. @@ -452,6 +460,8 @@ func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error) return common.Address{}, err } +// ImportRawKey stores the given hex encoded ECDSA key into the key directory, +// encrypting it with the passphrase. func (s *PrivateAccountAPI) ImportRawKey(privkey string, password string) (common.Address, error) { hexkey, err := hex.DecodeString(privkey) if err != nil { @@ -482,6 +492,34 @@ func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool { return s.am.Lock(addr) == nil } +// SignAndSendTransaction will create a transaction from the given arguments and +// tries to sign it with the key associated with args.To. If the given passwd isn't +// able to decrypt the key it fails. +func (s *PrivateAccountAPI) SignAndSendTransaction(args SendTxArgs, passwd string) (common.Hash, error) { + args = prepareSendTxArgs(args, s.gpo) + + s.txMu.Lock() + defer s.txMu.Unlock() + + if args.Nonce == nil { + args.Nonce = rpc.NewHexNumber(s.txPool.State().GetNonce(args.From)) + } + + var tx *types.Transaction + if args.To == nil { + tx = types.NewContractCreation(args.Nonce.Uint64(), args.Value.BigInt(), args.Gas.BigInt(), args.GasPrice.BigInt(), common.FromHex(args.Data)) + } else { + tx = types.NewTransaction(args.Nonce.Uint64(), *args.To, args.Value.BigInt(), args.Gas.BigInt(), args.GasPrice.BigInt(), common.FromHex(args.Data)) + } + + signature, err := s.am.SignWithPassphrase(args.From, passwd, tx.SigHash().Bytes()) + if err != nil { + return common.Hash{}, err + } + + return submitTransaction(s.txPool, tx, signature) +} + // PublicBlockChainAPI provides an API to access the Ethereum blockchain. // It offers only methods that operate on public data that is freely available to anyone. type PublicBlockChainAPI struct { @@ -645,15 +683,14 @@ func (s *PublicBlockChainAPI) NewBlocks(ctx context.Context, args NewBlocksArgs) // add a callback that is called on chain events which will format the block and notify the client s.muNewBlockSubscriptions.Lock() s.newBlockSubscriptions[subscription.ID()] = func(e core.ChainEvent) error { - if notification, err := s.rpcOutputBlock(e.Block, args.IncludeTransactions, args.TransactionDetails); err == nil { + notification, err := s.rpcOutputBlock(e.Block, args.IncludeTransactions, args.TransactionDetails) + if err == nil { return subscription.Notify(notification) - } else { - glog.V(logger.Warn).Info("unable to format block %v\n", err) } + glog.V(logger.Warn).Info("unable to format block %v\n", err) return nil } s.muNewBlockSubscriptions.Unlock() - return subscription, nil } @@ -700,6 +737,7 @@ func (m callmsg) Gas() *big.Int { return m.gas } func (m callmsg) Value() *big.Int { return m.value } func (m callmsg) Data() []byte { return m.data } +// CallArgs represents the arguments for a call. type CallArgs struct { From common.Address `json:"from"` To *common.Address `json:"to"` @@ -911,7 +949,7 @@ type PublicTransactionPoolAPI struct { miner *miner.Miner am *accounts.Manager txPool *core.TxPool - txMu sync.Mutex + txMu *sync.Mutex muPendingTxSubs sync.Mutex pendingTxSubs map[string]rpc.Subscription } @@ -925,6 +963,7 @@ func NewPublicTransactionPoolAPI(e *Ethereum) *PublicTransactionPoolAPI { bc: e.blockchain, am: e.accountManager, txPool: e.txPool, + txMu: &e.txMu, miner: e.miner, pendingTxSubs: make(map[string]rpc.Subscription), } @@ -1124,6 +1163,7 @@ func (s *PublicTransactionPoolAPI) sign(addr common.Address, tx *types.Transacti return tx.WithSignature(signature) } +// SendTxArgs represents the arguments to sumbit a new transaction into the transaction pool. type SendTxArgs struct { From common.Address `json:"from"` To *common.Address `json:"to"` @@ -1134,18 +1174,47 @@ type SendTxArgs struct { Nonce *rpc.HexNumber `json:"nonce"` } -// SendTransaction will create a transaction for the given transaction argument, sign it and submit it to the -// transaction pool. -func (s *PublicTransactionPoolAPI) SendTransaction(args SendTxArgs) (common.Hash, error) { +// prepareSendTxArgs is a helper function that fills in default values for unspecified tx fields. +func prepareSendTxArgs(args SendTxArgs, gpo *GasPriceOracle) SendTxArgs { if args.Gas == nil { args.Gas = rpc.NewHexNumber(defaultGas) } if args.GasPrice == nil { - args.GasPrice = rpc.NewHexNumber(s.gpo.SuggestPrice()) + args.GasPrice = rpc.NewHexNumber(gpo.SuggestPrice()) } if args.Value == nil { args.Value = rpc.NewHexNumber(0) } + return args +} + +// submitTransaction is a helper function that submits tx to txPool and creates a log entry. +func submitTransaction(txPool *core.TxPool, tx *types.Transaction, signature []byte) (common.Hash, error) { + signedTx, err := tx.WithSignature(signature) + if err != nil { + return common.Hash{}, err + } + + txPool.SetLocal(signedTx) + if err := txPool.Add(signedTx); err != nil { + return common.Hash{}, err + } + + if signedTx.To() == nil { + from, _ := signedTx.From() + addr := crypto.CreateAddress(from, signedTx.Nonce()) + glog.V(logger.Info).Infof("Tx(%s) created: %s\n", signedTx.Hash().Hex(), addr.Hex()) + } else { + glog.V(logger.Info).Infof("Tx(%s) to: %s\n", signedTx.Hash().Hex(), tx.To().Hex()) + } + + return signedTx.Hash(), nil +} + +// SendTransaction creates a transaction for the given argument, sign it and submit it to the +// transaction pool. +func (s *PublicTransactionPoolAPI) SendTransaction(args SendTxArgs) (common.Hash, error) { + args = prepareSendTxArgs(args, s.gpo) s.txMu.Lock() defer s.txMu.Unlock() @@ -1155,32 +1224,18 @@ func (s *PublicTransactionPoolAPI) SendTransaction(args SendTxArgs) (common.Hash } var tx *types.Transaction - contractCreation := (args.To == nil) - - if contractCreation { + if args.To == nil { tx = types.NewContractCreation(args.Nonce.Uint64(), args.Value.BigInt(), args.Gas.BigInt(), args.GasPrice.BigInt(), common.FromHex(args.Data)) } else { tx = types.NewTransaction(args.Nonce.Uint64(), *args.To, args.Value.BigInt(), args.Gas.BigInt(), args.GasPrice.BigInt(), common.FromHex(args.Data)) } - signedTx, err := s.sign(args.From, tx) + signature, err := s.am.Sign(args.From, tx.SigHash().Bytes()) if err != nil { return common.Hash{}, err } - s.txPool.SetLocal(signedTx) - if err := s.txPool.Add(signedTx); err != nil { - return common.Hash{}, err - } - - if contractCreation { - addr := crypto.CreateAddress(args.From, args.Nonce.Uint64()) - glog.V(logger.Info).Infof("Tx(%s) created: %s\n", signedTx.Hash().Hex(), addr.Hex()) - } else { - glog.V(logger.Info).Infof("Tx(%s) to: %s\n", signedTx.Hash().Hex(), tx.To().Hex()) - } - - return signedTx.Hash(), nil + return submitTransaction(s.txPool, tx, signature) } // SendRawTransaction will add the signed transaction to the transaction pool. @@ -1217,6 +1272,7 @@ func (s *PublicTransactionPoolAPI) Sign(addr common.Address, hash common.Hash) ( return common.ToHex(signature), error } +// SignTransactionArgs represents the arguments to sign a transaction. type SignTransactionArgs struct { From common.Address To *common.Address @@ -1243,6 +1299,7 @@ type Tx struct { Hash common.Hash `json:"hash"` } +// UnmarshalJSON parses JSON data into tx. func (tx *Tx) UnmarshalJSON(b []byte) (err error) { req := struct { To *common.Address `json:"to"` @@ -1283,8 +1340,7 @@ func (tx *Tx) UnmarshalJSON(b []byte) (err error) { tx.GasPrice = rpc.NewHexNumber(int64(50000000000)) } - contractCreation := (req.To == nil) - if contractCreation { + if req.To == nil { tx.tx = types.NewContractCreation(tx.Nonce.Uint64(), tx.Value.BigInt(), tx.GasLimit.BigInt(), tx.GasPrice.BigInt(), data) } else { tx.tx = types.NewTransaction(tx.Nonce.Uint64(), *tx.To, tx.Value.BigInt(), tx.GasLimit.BigInt(), tx.GasPrice.BigInt(), data) @@ -1293,6 +1349,7 @@ func (tx *Tx) UnmarshalJSON(b []byte) (err error) { return nil } +// SignTransactionResult represents a RLP encoded signed transaction. type SignTransactionResult struct { Raw string `json:"raw"` Tx *Tx `json:"tx"` @@ -1335,9 +1392,7 @@ func (s *PublicTransactionPoolAPI) SignTransaction(args SignTransactionArgs) (*S } var tx *types.Transaction - contractCreation := (args.To == nil) - - if contractCreation { + if args.To == nil { tx = types.NewContractCreation(args.Nonce.Uint64(), args.Value.BigInt(), args.Gas.BigInt(), args.GasPrice.BigInt(), common.FromHex(args.Data)) } else { tx = types.NewTransaction(args.Nonce.Uint64(), *args.To, args.Value.BigInt(), args.Gas.BigInt(), args.GasPrice.BigInt(), common.FromHex(args.Data)) @@ -1360,7 +1415,7 @@ func (s *PublicTransactionPoolAPI) SignTransaction(args SignTransactionArgs) (*S // the accounts this node manages. func (s *PublicTransactionPoolAPI) PendingTransactions() []*RPCTransaction { pending := s.txPool.GetTransactions() - transactions := make([]*RPCTransaction, 0) + transactions := make([]*RPCTransaction, 0, len(pending)) for _, tx := range pending { from, _ := tx.FromFrontier() if s.am.HasAddress(from) { @@ -1370,7 +1425,7 @@ func (s *PublicTransactionPoolAPI) PendingTransactions() []*RPCTransaction { return transactions } -// NewPendingTransaction creates a subscription that is triggered each time a transaction enters the transaction pool +// NewPendingTransactions creates a subscription that is triggered each time a transaction enters the transaction pool // and is send from one of the transactions this nodes manages. func (s *PublicTransactionPoolAPI) NewPendingTransactions(ctx context.Context) (rpc.Subscription, error) { notifier, supported := rpc.NotifierFromContext(ctx) @@ -1410,8 +1465,7 @@ func (s *PublicTransactionPoolAPI) Resend(tx Tx, gasPrice, gasLimit *rpc.HexNumb } var newTx *types.Transaction - contractCreation := (tx.tx.To() == nil) - if contractCreation { + if tx.tx.To() == nil { newTx = types.NewContractCreation(tx.tx.Nonce(), tx.tx.Value(), gasPrice.BigInt(), gasLimit.BigInt(), tx.tx.Data()) } else { newTx = types.NewTransaction(tx.tx.Nonce(), *tx.tx.To(), tx.tx.Value(), gasPrice.BigInt(), gasLimit.BigInt(), tx.tx.Data()) @@ -1612,7 +1666,7 @@ func (api *PrivateDebugAPI) ChaindbProperty(property string) (string, error) { return ldb.LDB().GetProperty(property) } -// BlockTraceResults is the returned value when replaying a block to check for +// BlockTraceResult is the returned value when replaying a block to check for // consensus results and full VM trace logs for all included transactions. type BlockTraceResult struct { Validated bool `json:"validated"` @@ -1647,7 +1701,7 @@ func (api *PrivateDebugAPI) TraceBlockFromFile(file string, config *vm.Config) B return api.TraceBlock(blockRlp, config) } -// TraceProcessBlock processes the block by canonical block number. +// TraceBlockByNumber processes the block by canonical block number. func (api *PrivateDebugAPI) TraceBlockByNumber(number uint64, config *vm.Config) BlockTraceResult { // Fetch the block that we aim to reprocess block := api.eth.BlockChain().GetBlockByNumber(number) @@ -1752,15 +1806,6 @@ type structLogRes struct { Storage map[string]string `json:"storage"` } -// VmLoggerOptions are the options used for debugging transactions and capturing -// specific data. -type VmLoggerOptions struct { - DisableMemory bool // disable memory capture - DisableStack bool // disable stack capture - DisableStorage bool // disable storage capture - FullStorage bool // show full storage (slow) -} - // formatLogs formats EVM returned structured logs for json output func formatLogs(structLogs []vm.StructLog) []structLogRes { formattedStructLogs := make([]structLogRes, len(structLogs)) @@ -1802,25 +1847,25 @@ func formatError(err error) string { // TraceTransaction returns the structured logs created during the execution of EVM // and returns them as a JSON object. -func (s *PrivateDebugAPI) TraceTransaction(txHash common.Hash, logger *vm.LogConfig) (*ExecutionResult, error) { +func (api *PrivateDebugAPI) TraceTransaction(txHash common.Hash, logger *vm.LogConfig) (*ExecutionResult, error) { if logger == nil { logger = new(vm.LogConfig) } // Retrieve the tx from the chain and the containing block - tx, blockHash, _, txIndex := core.GetTransaction(s.eth.ChainDb(), txHash) + tx, blockHash, _, txIndex := core.GetTransaction(api.eth.ChainDb(), txHash) if tx == nil { return nil, fmt.Errorf("transaction %x not found", txHash) } - block := s.eth.BlockChain().GetBlock(blockHash) + block := api.eth.BlockChain().GetBlock(blockHash) if block == nil { return nil, fmt.Errorf("block %x not found", blockHash) } // Create the state database to mutate and eventually trace - parent := s.eth.BlockChain().GetBlock(block.ParentHash()) + parent := api.eth.BlockChain().GetBlock(block.ParentHash()) if parent == nil { return nil, fmt.Errorf("block parent %x not found", block.ParentHash()) } - stateDb, err := state.New(parent.Root(), s.eth.ChainDb()) + stateDb, err := state.New(parent.Root(), api.eth.ChainDb()) if err != nil { return nil, err } @@ -1841,7 +1886,7 @@ func (s *PrivateDebugAPI) TraceTransaction(txHash common.Hash, logger *vm.LogCon } // Mutate the state if we haven't reached the tracing transaction yet if uint64(idx) < txIndex { - vmenv := core.NewEnv(stateDb, s.config, s.eth.BlockChain(), msg, block.Header(), vm.Config{}) + vmenv := core.NewEnv(stateDb, api.config, api.eth.BlockChain(), msg, block.Header(), vm.Config{}) _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())) if err != nil { return nil, fmt.Errorf("mutation failed: %v", err) @@ -1849,7 +1894,7 @@ func (s *PrivateDebugAPI) TraceTransaction(txHash common.Hash, logger *vm.LogCon continue } // Otherwise trace the transaction and return - vmenv := core.NewEnv(stateDb, s.config, s.eth.BlockChain(), msg, block.Header(), vm.Config{Debug: true, Logger: *logger}) + vmenv := core.NewEnv(stateDb, api.config, api.eth.BlockChain(), msg, block.Header(), vm.Config{Debug: true, Logger: *logger}) ret, gas, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())) if err != nil { return nil, fmt.Errorf("tracing failed: %v", err) @@ -1863,6 +1908,7 @@ func (s *PrivateDebugAPI) TraceTransaction(txHash common.Hash, logger *vm.LogCon return nil, errors.New("database inconsistency") } +// TraceCall executes a call and returns the amount of gas, created logs and optionally returned values. func (s *PublicBlockChainAPI) TraceCall(args CallArgs, blockNr rpc.BlockNumber) (*ExecutionResult, error) { // Fetch the state associated with the block number stateDb, block, err := stateAndBlockByNumber(s.miner, s.bc, blockNr, s.chainDb) @@ -1931,12 +1977,12 @@ func (s *PublicNetAPI) Listening() bool { return true // always listening } -// Peercount returns the number of connected peers +// 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. +// Version returns the current ethereum protocol version. func (s *PublicNetAPI) Version() string { return fmt.Sprintf("%d", s.networkVersion) } diff --git a/eth/backend.go b/eth/backend.go index f43dea777568..bb487650b739 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -26,6 +26,7 @@ import ( "path/filepath" "regexp" "strings" + "sync" "time" "github.com/ethereum/ethash" @@ -113,6 +114,7 @@ type Ethereum struct { // Handlers txPool *core.TxPool + txMu sync.Mutex blockchain *core.BlockChain accountManager *accounts.Manager pow *ethash.Ethash @@ -293,7 +295,7 @@ func (s *Ethereum) APIs() []rpc.API { }, { Namespace: "personal", Version: "1.0", - Service: NewPrivateAccountAPI(s.accountManager), + Service: NewPrivateAccountAPI(s), Public: false, }, { Namespace: "eth", diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 64c1b5044ba3..1928913deaac 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -176,20 +176,6 @@ web3._extend({ }); ` -const Personal_JS = ` -web3._extend({ - property: 'personal', - methods: - [ - new web3._extend.Method({ - name: 'importRawKey', - call: 'personal_importRawKey', - params: 2 - }) - ] -}); -` - const Eth_JS = ` web3._extend({ property: 'eth', @@ -244,20 +230,6 @@ web3._extend({ }); ` -const Net_JS = ` -web3._extend({ - property: 'net', - methods: [], - properties: - [ - new web3._extend.Property({ - name: 'version', - getter: 'net_version' - }) - ] -}); -` - const Debug_JS = ` web3._extend({ property: 'debug', @@ -463,6 +435,54 @@ web3._extend({ }); ` +const Net_JS = ` +web3._extend({ + property: 'net', + methods: [], + properties: + [ + new web3._extend.Property({ + name: 'version', + getter: 'net_version' + }) + ] +}); +` + +const Personal_JS = ` +web3._extend({ + property: 'personal', + methods: + [ + new web3._extend.Method({ + name: 'importRawKey', + call: 'personal_importRawKey', + params: 2 + }), + new web3._extend.Method({ + name: 'signAndSendTransaction', + call: 'personal_signAndSendTransaction', + params: 2, + inputFormatter: [web3._extend.formatters.inputTransactionFormatter, null] + }) + ] +}); +` + +const RPC_JS = ` +web3._extend({ + property: 'rpc', + methods: [], + properties: + [ + new web3._extend.Property({ + name: 'modules', + getter: 'rpc_modules' + }) + ] +}); +` + const Shh_JS = ` web3._extend({ property: 'shh', From 66d74dfb75e2001d0040e8b2a2d5add26e51db07 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 13 May 2016 23:59:19 +0200 Subject: [PATCH 2/8] [release 1.4.5] cmd/geth: fix console history exclusion Calls to 'personal' API should be excluded from console history because they can be called with an account passphrase as argument. The check for such calls was inverted and didn't work. (cherry picked from commit 86da6feb40fd366c75236d87fa306576c51ed2a8) Conflicts: cmd/geth/js.go --- cmd/geth/js.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/cmd/geth/js.go b/cmd/geth/js.go index 69c03f4c4011..729cc2fd711e 100644 --- a/cmd/geth/js.go +++ b/cmd/geth/js.go @@ -41,7 +41,6 @@ import ( ) var ( - leadingSpace = regexp.MustCompile("^ ") passwordRegexp = regexp.MustCompile("personal.[nus]") onlyws = regexp.MustCompile("^\\s*$") exit = regexp.MustCompile("^\\s*exit\\s*;*\\s*$") @@ -361,7 +360,7 @@ func (self *jsre) interactive() { str += input + "\n" self.setIndent() if indentCount <= 0 { - if mustLogInHistory(str) { + if !excludeFromHistory(str) { utils.Stdin.AppendHistory(str[:len(str)-1]) } self.parseInput(str) @@ -371,10 +370,8 @@ func (self *jsre) interactive() { } } -func mustLogInHistory(input string) bool { - return len(input) == 0 || - passwordRegexp.MatchString(input) || - !leadingSpace.MatchString(input) +func excludeFromHistory(input string) bool { + return len(input) == 0 || input[0] == ' ' || passwordRegexp.MatchString(input) } func (self *jsre) withHistory(datadir string, op func(*os.File)) { From 7a7a5acc9ffee60ea838c8374741dad8e79e59ef Mon Sep 17 00:00:00 2001 From: Bas van Kervel Date: Tue, 17 May 2016 16:05:12 +0200 Subject: [PATCH 3/8] [release 1.4.5] eth/filter: bugfix which can cause a nil pointer crash when parsing filter arguments (cherry picked from commit 67cd4ee8d26e9ebe2714faaca856e73512236834) --- eth/filters/api.go | 74 ++++++++------- eth/filters/api_test.go | 198 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 234 insertions(+), 38 deletions(-) create mode 100644 eth/filters/api_test.go diff --git a/eth/filters/api.go b/eth/filters/api.go index d9bd4d4b7ff8..7278e20b9496 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -233,6 +233,7 @@ func (s *PublicFilterAPI) newLogFilter(earliest, latest int64, addresses []commo return id, nil } +// Logs creates a subscription that fires for all new log that match the given filter criteria. func (s *PublicFilterAPI) Logs(ctx context.Context, args NewFilterArgs) (rpc.Subscription, error) { notifier, supported := rpc.NotifierFromContext(ctx) if !supported { @@ -291,12 +292,13 @@ type NewFilterArgs struct { Topics [][]common.Hash } +// UnmarshalJSON sets *args fields with given data. func (args *NewFilterArgs) UnmarshalJSON(data []byte) error { type input struct { From *rpc.BlockNumber `json:"fromBlock"` ToBlock *rpc.BlockNumber `json:"toBlock"` Addresses interface{} `json:"address"` - Topics interface{} `json:"topics"` + Topics []interface{} `json:"topics"` } var raw input @@ -321,7 +323,6 @@ func (args *NewFilterArgs) UnmarshalJSON(data []byte) error { if raw.Addresses != nil { // raw.Address can contain a single address or an array of addresses var addresses []common.Address - if strAddrs, ok := raw.Addresses.([]interface{}); ok { for i, addr := range strAddrs { if strAddr, ok := addr.(string); ok { @@ -352,56 +353,53 @@ func (args *NewFilterArgs) UnmarshalJSON(data []byte) error { args.Addresses = addresses } + // helper function which parses a string to a topic hash topicConverter := func(raw string) (common.Hash, error) { if len(raw) == 0 { return common.Hash{}, nil } - if len(raw) >= 2 && raw[0] == '0' && (raw[1] == 'x' || raw[1] == 'X') { raw = raw[2:] } - + if len(raw) != 2 * common.HashLength { + return common.Hash{}, errors.New("invalid topic(s)") + } if decAddr, err := hex.DecodeString(raw); err == nil { return common.BytesToHash(decAddr), nil } - - return common.Hash{}, errors.New("invalid topic given") - } - - // topics is an array consisting of strings or arrays of strings - if raw.Topics != nil { - topics, ok := raw.Topics.([]interface{}) - if ok { - parsedTopics := make([][]common.Hash, len(topics)) - for i, topic := range topics { - if topic == nil { - parsedTopics[i] = []common.Hash{common.StringToHash("")} - } else if strTopic, ok := topic.(string); ok { - if t, err := topicConverter(strTopic); err != nil { - return fmt.Errorf("invalid topic on index %d", i) - } else { - parsedTopics[i] = []common.Hash{t} - } - } else if arrTopic, ok := topic.([]interface{}); ok { - parsedTopics[i] = make([]common.Hash, len(arrTopic)) - for j := 0; j < len(parsedTopics[i]); i++ { - if arrTopic[j] == nil { - parsedTopics[i][j] = common.StringToHash("") - } else if str, ok := arrTopic[j].(string); ok { - if t, err := topicConverter(str); err != nil { - return fmt.Errorf("invalid topic on index %d", i) - } else { - parsedTopics[i] = []common.Hash{t} - } - } else { - return fmt.Errorf("topic[%d][%d] not a string", i, j) + return common.Hash{}, errors.New("invalid topic(s)") + } + + // topics is an array consisting of strings and/or arrays of strings. + // JSON null values are converted to common.Hash{} and ignored by the filter manager. + if len(raw.Topics) > 0 { + args.Topics = make([][]common.Hash, len(raw.Topics)) + for i, t := range raw.Topics { + if t == nil { // ignore topic when matching logs + args.Topics[i] = []common.Hash{common.Hash{}} + } else if topic, ok := t.(string); ok { // match specific topic + top, err := topicConverter(topic) + if err != nil { + return err + } + args.Topics[i] = []common.Hash{top} + } else if topics, ok := t.([]interface{}); ok { // or case e.g. [null, "topic0", "topic1"] + for _, rawTopic := range topics { + if rawTopic == nil { + args.Topics[i] = append(args.Topics[i], common.Hash{}) + } else if topic, ok := rawTopic.(string); ok { + parsed, err := topicConverter(topic) + if err != nil { + return err } + args.Topics[i] = append(args.Topics[i], parsed) + } else { + return fmt.Errorf("invalid topic(s)") } - } else { - return fmt.Errorf("topic[%d] invalid", i) } + } else { + return fmt.Errorf("invalid topic(s)") } - args.Topics = parsedTopics } } diff --git a/eth/filters/api_test.go b/eth/filters/api_test.go new file mode 100644 index 000000000000..9e8edc241368 --- /dev/null +++ b/eth/filters/api_test.go @@ -0,0 +1,198 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package filters_test + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/eth/filters" + "github.com/ethereum/go-ethereum/rpc" +) + +func TestUnmarshalJSONNewFilterArgs(t *testing.T) { + var ( + fromBlock rpc.BlockNumber = 0x123435 + toBlock rpc.BlockNumber = 0xabcdef + address0 = common.StringToAddress("70c87d191324e6712a591f304b4eedef6ad9bb9d") + address1 = common.StringToAddress("9b2055d370f73ec7d8a03e965129118dc8f5bf83") + topic0 = common.HexToHash("3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1ca") + topic1 = common.HexToHash("9084a792d2f8b16a62b882fd56f7860c07bf5fa91dd8a2ae7e809e5180fef0b3") + topic2 = common.HexToHash("6ccae1c4af4152f460ff510e573399795dfab5dcf1fa60d1f33ac8fdc1e480ce") + nullTopic = common.Hash{} + ) + + // default values + var test0 filters.NewFilterArgs + if err := json.Unmarshal([]byte("{}"), &test0); err != nil { + t.Fatal(err) + } + if test0.FromBlock != rpc.LatestBlockNumber { + t.Fatalf("expected %d, got %d", rpc.LatestBlockNumber, test0.FromBlock) + } + if test0.ToBlock != rpc.LatestBlockNumber { + t.Fatalf("expected %d, got %d", rpc.LatestBlockNumber, test0.ToBlock) + } + if len(test0.Addresses) != 0 { + t.Fatalf("expected 0 addresses, got %d", len(test0.Addresses)) + } + if len(test0.Topics) != 0 { + t.Fatalf("expected 0 topics, got %d topics", len(test0.Topics)) + } + + // from, to block number + var test1 filters.NewFilterArgs + vector := fmt.Sprintf(`{"fromBlock":"0x%x","toBlock":"0x%x"}`, fromBlock, toBlock) + if err := json.Unmarshal([]byte(vector), &test1); err != nil { + t.Fatal(err) + } + if test1.FromBlock != fromBlock { + t.Fatalf("expected FromBlock %d, got %d", fromBlock, test1.FromBlock) + } + if test1.ToBlock != toBlock { + t.Fatalf("expected ToBlock %d, got %d", toBlock, test1.ToBlock) + } + + // single address + var test2 filters.NewFilterArgs + vector = fmt.Sprintf(`{"address": "%s"}`, address0.Hex()) + if err := json.Unmarshal([]byte(vector), &test2); err != nil { + t.Fatal(err) + } + if len(test2.Addresses) != 1 { + t.Fatalf("expected 1 address, got %d address(es)", len(test2.Addresses)) + } + if test2.Addresses[0] != address0 { + t.Fatalf("expected address %x, got %x", address0, test2.Addresses[0]) + } + + // multiple address + var test3 filters.NewFilterArgs + vector = fmt.Sprintf(`{"address": ["%s", "%s"]}`, address0.Hex(), address1.Hex()) + if err := json.Unmarshal([]byte(vector), &test3); err != nil { + t.Fatal(err) + } + if len(test3.Addresses) != 2 { + t.Fatalf("expected 2 addresses, got %d address(es)", len(test3.Addresses)) + } + if test3.Addresses[0] != address0 { + t.Fatalf("expected address %x, got %x", address0, test3.Addresses[0]) + } + if test3.Addresses[1] != address1 { + t.Fatalf("expected address %x, got %x", address1, test3.Addresses[1]) + } + + // single topic + var test4 filters.NewFilterArgs + vector = fmt.Sprintf(`{"topics": ["%s"]}`, topic0.Hex()) + if err := json.Unmarshal([]byte(vector), &test4); err != nil { + t.Fatal(err) + } + if len(test4.Topics) != 1 { + t.Fatalf("expected 1 topic, got %d", len(test4.Topics)) + } + if len(test4.Topics[0]) != 1 { + t.Fatalf("expected len(topics[0]) to be 1, got %d", len(test4.Topics[0])) + } + if test4.Topics[0][0] != topic0 { + t.Fatalf("got %x, expected %x", test4.Topics[0][0], topic0) + } + + // test multiple "AND" topics + var test5 filters.NewFilterArgs + vector = fmt.Sprintf(`{"topics": ["%s", "%s"]}`, topic0.Hex(), topic1.Hex()) + if err := json.Unmarshal([]byte(vector), &test5); err != nil { + t.Fatal(err) + } + if len(test5.Topics) != 2 { + t.Fatalf("expected 2 topics, got %d", len(test5.Topics)) + } + if len(test5.Topics[0]) != 1 { + t.Fatalf("expected 1 topic, got %d", len(test5.Topics[0])) + } + if test5.Topics[0][0] != topic0 { + t.Fatalf("got %x, expected %x", test5.Topics[0][0], topic0) + } + if len(test5.Topics[1]) != 1 { + t.Fatalf("expected 1 topic, got %d", len(test5.Topics[1])) + } + if test5.Topics[1][0] != topic1 { + t.Fatalf("got %x, expected %x", test5.Topics[1][0], topic1) + } + + // test optional topic + var test6 filters.NewFilterArgs + vector = fmt.Sprintf(`{"topics": ["%s", null, "%s"]}`, topic0.Hex(), topic2.Hex()) + if err := json.Unmarshal([]byte(vector), &test6); err != nil { + t.Fatal(err) + } + if len(test6.Topics) != 3 { + t.Fatalf("expected 3 topics, got %d", len(test6.Topics)) + } + if len(test6.Topics[0]) != 1 { + t.Fatalf("expected 1 topic, got %d", len(test6.Topics[0])) + } + if test6.Topics[0][0] != topic0 { + t.Fatalf("got %x, expected %x", test6.Topics[0][0], topic0) + } + if len(test6.Topics[1]) != 1 { + t.Fatalf("expected 1 topic, got %d", len(test6.Topics[1])) + } + if test6.Topics[1][0] != nullTopic { + t.Fatalf("got %x, expected empty hash", test6.Topics[1][0]) + } + if len(test6.Topics[2]) != 1 { + t.Fatalf("expected 1 topic, got %d", len(test6.Topics[2])) + } + if test6.Topics[2][0] != topic2 { + t.Fatalf("got %x, expected %x", test6.Topics[2][0], topic2) + } + + // test OR topics + var test7 filters.NewFilterArgs + vector = fmt.Sprintf(`{"topics": [["%s", "%s"], null, ["%s", null]]}`, topic0.Hex(), topic1.Hex(), topic2.Hex()) + if err := json.Unmarshal([]byte(vector), &test7); err != nil { + t.Fatal(err) + } + if len(test7.Topics) != 3 { + t.Fatalf("expected 3 topics, got %d topics", len(test7.Topics)) + } + if len(test7.Topics[0]) != 2 { + t.Fatalf("expected 2 topics, got %d topics", len(test7.Topics[0])) + } + if test7.Topics[0][0] != topic0 || test7.Topics[0][1] != topic1 { + t.Fatalf("invalid topics expected [%x,%x], got [%x,%x]", + topic0, topic1, test7.Topics[0][0], test7.Topics[0][1], + ) + } + if len(test7.Topics[1]) != 1 { + t.Fatalf("expected 1 topic, got %d topics", len(test7.Topics[1])) + } + if test7.Topics[1][0] != nullTopic { + t.Fatalf("expected empty hash, got %x", test7.Topics[1][0]) + } + if len(test7.Topics[2]) != 2 { + t.Fatalf("expected 2 topics, got %d topics", len(test7.Topics[2])) + } + if test7.Topics[2][0] != topic2 || test7.Topics[2][1] != nullTopic { + t.Fatalf("invalid topics expected [%x,%x], got [%x,%x]", + topic2, nullTopic, test7.Topics[2][0], test7.Topics[2][1], + ) + } +} From 0f6e3e873ab378e8ef997adcf5b0a0d78b6c90cc Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Wed, 18 May 2016 17:23:58 +0200 Subject: [PATCH 4/8] [release 1.4.5] eth: fixed regression in eth_signTransaction fixes #2578 Sign transaction returned the unsigned transaction rather than the signed one. (cherry picked from commit 4b1a7d3868e796ee70f62985379e59f933a2aca0) --- eth/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/api.go b/eth/api.go index 1f9fe5d558cb..06f4d5de0222 100644 --- a/eth/api.go +++ b/eth/api.go @@ -1408,7 +1408,7 @@ func (s *PublicTransactionPoolAPI) SignTransaction(args SignTransactionArgs) (*S return nil, err } - return &SignTransactionResult{"0x" + common.Bytes2Hex(data), newTx(tx)}, nil + return &SignTransactionResult{"0x" + common.Bytes2Hex(data), newTx(signedTx)}, nil } // PendingTransactions returns the transactions that are in the transaction pool and have a from address that is one of From 1776c717bfb540f95e1b2233f35ea6f4cc8f57d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 20 May 2016 12:29:28 +0300 Subject: [PATCH 5/8] [release 1.4.5] accounts/abi/bind, eth: rely on getCode for sanity checks, not estimate and call (cherry picked from commit 1580ec180414bce1e37acc614bc2445f778efb75) --- accounts/abi/bind/backend.go | 49 ++++++++++++++++++++++--- accounts/abi/bind/backends/nil.go | 1 + accounts/abi/bind/backends/remote.go | 20 ++++++++++ accounts/abi/bind/backends/simulated.go | 10 +++++ accounts/abi/bind/base.go | 27 ++++++++++++++ eth/api.go | 15 -------- eth/bind.go | 18 +++++---- 7 files changed, 112 insertions(+), 28 deletions(-) diff --git a/accounts/abi/bind/backend.go b/accounts/abi/bind/backend.go index 604e1ef2672d..65806aef4294 100644 --- a/accounts/abi/bind/backend.go +++ b/accounts/abi/bind/backend.go @@ -27,15 +27,16 @@ import ( // ErrNoCode is returned by call and transact operations for which the requested // recipient contract to operate on does not exist in the state db or does not // have any code associated with it (i.e. suicided). -// -// Please note, this error string is part of the RPC API and is expected by the -// native contract bindings to signal this particular error. Do not change this -// as it will break all dependent code! var ErrNoCode = errors.New("no contract code at given address") // ContractCaller defines the methods needed to allow operating with contract on a read // only basis. type ContractCaller interface { + // HasCode checks if the contract at the given address has any code associated + // with it or not. This is needed to differentiate between contract internal + // errors and the local chain being out of sync. + HasCode(contract common.Address, pending bool) (bool, error) + // ContractCall executes an Ethereum contract call with the specified data as // the input. The pending flag requests execution against the pending block, not // the stable head of the chain. @@ -55,6 +56,11 @@ type ContractTransactor interface { // execution of a transaction. SuggestGasPrice() (*big.Int, error) + // HasCode checks if the contract at the given address has any code associated + // with it or not. This is needed to differentiate between contract internal + // errors and the local chain being out of sync. + HasCode(contract common.Address, pending bool) (bool, error) + // EstimateGasLimit tries to estimate the gas needed to execute a specific // transaction based on the current pending state of the backend blockchain. // There is no guarantee that this is the true gas limit requirement as other @@ -68,7 +74,38 @@ type ContractTransactor interface { // ContractBackend defines the methods needed to allow operating with contract // on a read-write basis. +// +// This interface is essentially the union of ContractCaller and ContractTransactor +// but due to a bug in the Go compiler (https://github.com/golang/go/issues/6977), +// we cannot simply list it as the two interfaces. The other solution is to add a +// third interface containing the common methods, but that convolutes the user API +// as it introduces yet another parameter to require for initialization. type ContractBackend interface { - ContractCaller - ContractTransactor + // HasCode checks if the contract at the given address has any code associated + // with it or not. This is needed to differentiate between contract internal + // errors and the local chain being out of sync. + HasCode(contract common.Address, pending bool) (bool, error) + + // ContractCall executes an Ethereum contract call with the specified data as + // the input. The pending flag requests execution against the pending block, not + // the stable head of the chain. + ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error) + + // PendingAccountNonce retrieves the current pending nonce associated with an + // account. + PendingAccountNonce(account common.Address) (uint64, error) + + // SuggestGasPrice retrieves the currently suggested gas price to allow a timely + // execution of a transaction. + SuggestGasPrice() (*big.Int, error) + + // EstimateGasLimit tries to estimate the gas needed to execute a specific + // transaction based on the current pending state of the backend blockchain. + // There is no guarantee that this is the true gas limit requirement as other + // transactions may be added or removed by miners, but it should provide a basis + // for setting a reasonable default. + EstimateGasLimit(sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error) + + // SendTransaction injects the transaction into the pending pool for execution. + SendTransaction(tx *types.Transaction) error } diff --git a/accounts/abi/bind/backends/nil.go b/accounts/abi/bind/backends/nil.go index 3b1e6dce7766..f10bb61acb68 100644 --- a/accounts/abi/bind/backends/nil.go +++ b/accounts/abi/bind/backends/nil.go @@ -38,6 +38,7 @@ func (*nilBackend) ContractCall(common.Address, []byte, bool) ([]byte, error) { func (*nilBackend) EstimateGasLimit(common.Address, *common.Address, *big.Int, []byte) (*big.Int, error) { panic("not implemented") } +func (*nilBackend) HasCode(common.Address, bool) (bool, error) { panic("not implemented") } func (*nilBackend) SuggestGasPrice() (*big.Int, error) { panic("not implemented") } func (*nilBackend) PendingAccountNonce(common.Address) (uint64, error) { panic("not implemented") } func (*nilBackend) SendTransaction(*types.Transaction) error { panic("not implemented") } diff --git a/accounts/abi/bind/backends/remote.go b/accounts/abi/bind/backends/remote.go index 9b3647192a25..d903cbc8f740 100644 --- a/accounts/abi/bind/backends/remote.go +++ b/accounts/abi/bind/backends/remote.go @@ -111,6 +111,26 @@ func (b *rpcBackend) request(method string, params []interface{}) (json.RawMessa return res.Result, nil } +// HasCode implements ContractVerifier.HasCode by retrieving any code associated +// with the contract from the remote node, and checking its size. +func (b *rpcBackend) HasCode(contract common.Address, pending bool) (bool, error) { + // Execute the RPC code retrieval + block := "latest" + if pending { + block = "pending" + } + res, err := b.request("eth_getCode", []interface{}{contract.Hex(), block}) + if err != nil { + return false, err + } + var hex string + if err := json.Unmarshal(res, &hex); err != nil { + return false, err + } + // Convert the response back to a Go byte slice and return + return len(common.FromHex(hex)) > 0, nil +} + // ContractCall implements ContractCaller.ContractCall, delegating the execution of // a contract call to the remote node, returning the reply to for local processing. func (b *rpcBackend) ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error) { diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 4866c4f588b4..54b1ce603211 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -78,6 +78,16 @@ func (b *SimulatedBackend) Rollback() { b.pendingState, _ = state.New(b.pendingBlock.Root(), b.database) } +// HasCode implements ContractVerifier.HasCode, checking whether there is any +// code associated with a certain account in the blockchain. +func (b *SimulatedBackend) HasCode(contract common.Address, pending bool) (bool, error) { + if pending { + return len(b.pendingState.GetCode(contract)) > 0, nil + } + statedb, _ := b.blockchain.State() + return len(statedb.GetCode(contract)) > 0, nil +} + // ContractCall implements ContractCaller.ContractCall, executing the specified // contract with the given input data. func (b *SimulatedBackend) ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error) { diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 06621c5ad35a..75e8d5bc858d 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "math/big" + "sync/atomic" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -56,6 +57,9 @@ type BoundContract struct { abi abi.ABI // Reflect based ABI to access the correct Ethereum methods caller ContractCaller // Read interface to interact with the blockchain transactor ContractTransactor // Write interface to interact with the blockchain + + latestHasCode uint32 // Cached verification that the latest state contains code for this contract + pendingHasCode uint32 // Cached verification that the pending state contains code for this contract } // NewBoundContract creates a low level contract interface through which calls @@ -96,6 +100,19 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, if opts == nil { opts = new(CallOpts) } + // Make sure we have a contract to operate on, and bail out otherwise + if (opts.Pending && atomic.LoadUint32(&c.pendingHasCode) == 0) || (!opts.Pending && atomic.LoadUint32(&c.latestHasCode) == 0) { + if code, err := c.caller.HasCode(c.address, opts.Pending); err != nil { + return err + } else if !code { + return ErrNoCode + } + if opts.Pending { + atomic.StoreUint32(&c.pendingHasCode, 1) + } else { + atomic.StoreUint32(&c.latestHasCode, 1) + } + } // Pack the input, call and unpack the results input, err := c.abi.Pack(method, params...) if err != nil { @@ -153,6 +170,16 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i } gasLimit := opts.GasLimit if gasLimit == nil { + // Gas estimation cannot succeed without code for method invocations + if contract != nil && atomic.LoadUint32(&c.pendingHasCode) == 0 { + if code, err := c.transactor.HasCode(c.address, true); err != nil { + return nil, err + } else if !code { + return nil, ErrNoCode + } + atomic.StoreUint32(&c.pendingHasCode, 1) + } + // If the contract surely has code (or code is not needed), estimate the transaction gasLimit, err = c.transactor.EstimateGasLimit(opts.From, contract, value, input) if err != nil { return nil, fmt.Errorf("failed to exstimate gas needed: %v", err) diff --git a/eth/api.go b/eth/api.go index 06f4d5de0222..d048904f3621 100644 --- a/eth/api.go +++ b/eth/api.go @@ -52,15 +52,6 @@ import ( "golang.org/x/net/context" ) -// errNoCode is returned by call and transact operations for which the requested -// recipient contract to operate on does not exist in the state db or does not -// have any code associated with it (i.e. suicided). -// -// Please note, this error string is part of the RPC API and is expected by the -// native contract bindings to signal this particular error. Do not change this -// as it will break all dependent code! -var errNoCode = errors.New("no contract code at given address") - const defaultGas = uint64(90000) // blockByNumber is a commonly used helper function which retrieves and returns @@ -755,12 +746,6 @@ func (s *PublicBlockChainAPI) doCall(args CallArgs, blockNr rpc.BlockNumber) (st } stateDb = stateDb.Copy() - // If there's no code to interact with, respond with an appropriate error - if args.To != nil { - if code := stateDb.GetCode(*args.To); len(code) == 0 { - return "0x", nil, errNoCode - } - } // Retrieve the account state object to interact with var from *state.StateObject if args.From == (common.Address{}) { diff --git a/eth/bind.go b/eth/bind.go index 3a3eca06237c..fb7f29f601c0 100644 --- a/eth/bind.go +++ b/eth/bind.go @@ -19,7 +19,6 @@ package eth import ( "math/big" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" @@ -49,6 +48,17 @@ func NewContractBackend(eth *Ethereum) *ContractBackend { } } +// HasCode implements bind.ContractVerifier.HasCode by retrieving any code associated +// with the contract from the local API, and checking its size. +func (b *ContractBackend) HasCode(contract common.Address, pending bool) (bool, error) { + block := rpc.LatestBlockNumber + if pending { + block = rpc.PendingBlockNumber + } + out, err := b.bcapi.GetCode(contract, block) + return len(common.FromHex(out)) > 0, err +} + // ContractCall implements bind.ContractCaller executing an Ethereum contract // call with the specified data as the input. The pending flag requests execution // against the pending block, not the stable head of the chain. @@ -64,9 +74,6 @@ func (b *ContractBackend) ContractCall(contract common.Address, data []byte, pen } // Execute the call and convert the output back to Go types out, err := b.bcapi.Call(args, block) - if err == errNoCode { - err = bind.ErrNoCode - } return common.FromHex(out), err } @@ -95,9 +102,6 @@ func (b *ContractBackend) EstimateGasLimit(sender common.Address, contract *comm Value: *rpc.NewHexNumber(value), Data: common.ToHex(data), }) - if err == errNoCode { - err = bind.ErrNoCode - } return out.BigInt(), err } From 68ae6b52e92cfcb54a628b653b90f7d356e5df50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 12 May 2016 16:36:58 +0300 Subject: [PATCH 6/8] [release 1.4.5] accounts/abi: fix abi test for go vet... (cherry picked from commit 251b3c6406aee5876c87016bd088c8cefe7c08f6) --- accounts/abi/abi_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index df89ba13812e..7916e595dd13 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -305,10 +305,10 @@ func TestUnpackSetInterfaceSlice(t *testing.T) { t.Fatal(err) } if *var1 != 1 { - t.Errorf("expected var1 to be 1, got", *var1) + t.Error("expected var1 to be 1, got", *var1) } if *var2 != 2 { - t.Errorf("expected var2 to be 2, got", *var2) + t.Error("expected var2 to be 2, got", *var2) } out = []interface{}{var1} From 27df30f30f700e0b4496024ef26a668fef4ab794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 24 May 2016 11:26:21 +0300 Subject: [PATCH 7/8] VERSION, cmd/geth: bumped version 1.4.5 --- VERSION | 2 +- cmd/geth/main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 1c99cf0e8093..e516bb9d963a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.4.4 +1.4.5 diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 5a2fc628739b..e94b76594d51 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -50,7 +50,7 @@ const ( clientIdentifier = "Geth" // Client identifier to advertise over the network versionMajor = 1 // Major version component of the current release versionMinor = 4 // Minor version component of the current release - versionPatch = 4 // Patch version component of the current release + versionPatch = 5 // Patch version component of the current release versionMeta = "stable" // Version metadata to append to the version string versionOracle = "0xfa7b9770ca4cb04296cac84f37736d4041251cdf" // Ethereum address of the Geth release oracle From 40b4de534610edfe3579031aebbd90c11773556f Mon Sep 17 00:00:00 2001 From: Christopher Franko Date: Thu, 26 May 2016 13:16:58 -0400 Subject: [PATCH 8/8] fixed strange character --- accounts/abi/bind/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accounts/abi/bind/backend.go b/accounts/abi/bind/backend.go index d55cceab0874..77fa18cbe486 100644 --- a/accounts/abi/bind/backend.go +++ b/accounts/abi/bind/backend.go @@ -38,7 +38,7 @@ type ContractCaller interface { HasCode(contract common.Address, pending bool) (bool, error) // ContractCall executes an Expanse contract call with the specified data as -\ // the input. The pending flag requests execution against the pending block, not + // the input. The pending flag requests execution against the pending block, not // the stable head of the chain. ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error) }