diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index df89ba1381..7916e595dd 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} diff --git a/accounts/abi/bind/backend.go b/accounts/abi/bind/backend.go index 604e1ef267..65806aef42 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 3b1e6dce77..f10bb61acb 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 9b3647192a..d903cbc8f7 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 4866c4f588..54b1ce6032 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 06621c5ad3..75e8d5bc85 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/accounts/account_manager.go b/accounts/account_manager.go index 3afadf6b22..bfb7556d6a 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 829cf39683..2e5f2b44a4 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 2b64303b2b..729cc2fd71 100644 --- a/cmd/geth/js.go +++ b/cmd/geth/js.go @@ -41,8 +41,7 @@ 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*$") ) @@ -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)) { diff --git a/eth/api.go b/eth/api.go index 1d66f53fe5..d048904f36 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 @@ -148,7 +139,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 +208,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 +413,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 +451,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 +483,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 +674,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 +728,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"` @@ -717,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{}) { @@ -911,7 +934,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 +948,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 +1148,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 +1159,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 +1209,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 +1257,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 +1284,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 +1325,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 +1334,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 +1377,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)) @@ -1353,14 +1393,14 @@ 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 // 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 +1410,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 +1450,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 +1651,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 +1686,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 +1791,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 +1832,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 +1871,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 +1879,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 +1893,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 +1962,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 f43dea7775..bb487650b7 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/eth/bind.go b/eth/bind.go index 3a3eca0623..fb7f29f601 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 } diff --git a/eth/filters/api.go b/eth/filters/api.go index d9bd4d4b7f..7278e20b94 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") + return common.Hash{}, errors.New("invalid topic(s)") } - // 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) - } - } - } else { - return fmt.Errorf("topic[%d] invalid", i) + // 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("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 0000000000..9e8edc2413 --- /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], + ) + } +} diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 64c1b5044b..1928913dea 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',