diff options
author | Péter Szilágyi <peterke@gmail.com> | 2016-08-25 21:20:38 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-08-25 21:20:38 +0800 |
commit | 5fc032a9d16ac6ea1dc020f06e44c24c94a361a7 (patch) | |
tree | 363e7e1a3439424e00a4e7e72a701b97f737e6aa /accounts/abi/bind/backends/simulated.go | |
parent | 806e3cd0751e7769bddbb2d4250b4520d912df42 (diff) | |
parent | c97df052a96820742fffdbb3ef5e77dbf1397637 (diff) | |
download | dexon-5fc032a9d16ac6ea1dc020f06e44c24c94a361a7.tar dexon-5fc032a9d16ac6ea1dc020f06e44c24c94a361a7.tar.gz dexon-5fc032a9d16ac6ea1dc020f06e44c24c94a361a7.tar.bz2 dexon-5fc032a9d16ac6ea1dc020f06e44c24c94a361a7.tar.lz dexon-5fc032a9d16ac6ea1dc020f06e44c24c94a361a7.tar.xz dexon-5fc032a9d16ac6ea1dc020f06e44c24c94a361a7.tar.zst dexon-5fc032a9d16ac6ea1dc020f06e44c24c94a361a7.zip |
Merge pull request #2930 from fjl/ethclient
Stable Go API, part 1
Diffstat (limited to 'accounts/abi/bind/backends/simulated.go')
-rw-r--r-- | accounts/abi/bind/backends/simulated.go | 253 |
1 files changed, 156 insertions, 97 deletions
diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 687a31bf1..29b4e8ea3 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -17,8 +17,12 @@ package backends import ( + "errors" + "fmt" "math/big" + "sync" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" @@ -36,12 +40,15 @@ var chainConfig = &core.ChainConfig{HomesteadBlock: big.NewInt(0)} // This nil assignment ensures compile time that SimulatedBackend implements bind.ContractBackend. var _ bind.ContractBackend = (*SimulatedBackend)(nil) +var errBlockNumberUnsupported = errors.New("SimulatedBackend cannot access blocks other than the latest block") + // SimulatedBackend implements bind.ContractBackend, simulating a blockchain in // the background. Its main purpose is to allow easily testing contract bindings. type SimulatedBackend struct { database ethdb.Database // In memory database to store our testing data blockchain *core.BlockChain // Ethereum blockchain to handle the consensus + mu sync.Mutex pendingBlock *types.Block // Currently pending block that will be imported on request pendingState *state.StateDB // Currently pending state that will be the active on on request } @@ -52,85 +59,133 @@ func NewSimulatedBackend(accounts ...core.GenesisAccount) *SimulatedBackend { database, _ := ethdb.NewMemDatabase() core.WriteGenesisBlockForTesting(database, accounts...) blockchain, _ := core.NewBlockChain(database, chainConfig, new(core.FakePow), new(event.TypeMux)) - - backend := &SimulatedBackend{ - database: database, - blockchain: blockchain, - } - backend.Rollback() - + backend := &SimulatedBackend{database: database, blockchain: blockchain} + backend.rollback() return backend } // Commit imports all the pending transactions as a single block and starts a // fresh new state. func (b *SimulatedBackend) Commit() { + b.mu.Lock() + defer b.mu.Unlock() + if _, err := b.blockchain.InsertChain([]*types.Block{b.pendingBlock}); err != nil { panic(err) // This cannot happen unless the simulator is wrong, fail in that case } - b.Rollback() + b.rollback() } // Rollback aborts all pending transactions, reverting to the last committed state. func (b *SimulatedBackend) Rollback() { - blocks, _ := core.GenerateChain(nil, b.blockchain.CurrentBlock(), b.database, 1, func(int, *core.BlockGen) {}) + b.mu.Lock() + defer b.mu.Unlock() + + b.rollback() +} +func (b *SimulatedBackend) rollback() { + blocks, _ := core.GenerateChain(nil, b.blockchain.CurrentBlock(), b.database, 1, func(int, *core.BlockGen) {}) b.pendingBlock = blocks[0] 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(ctx context.Context, contract common.Address, pending bool) (bool, error) { - if pending { - return len(b.pendingState.GetCode(contract)) > 0, nil +// CodeAt returns the code associated with a certain account in the blockchain. +func (b *SimulatedBackend) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { + b.mu.Lock() + defer b.mu.Unlock() + + if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 { + return nil, errBlockNumberUnsupported } 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(ctx context.Context, contract common.Address, data []byte, pending bool) ([]byte, error) { - // Create a copy of the current state db to screw around with - var ( - block *types.Block - statedb *state.StateDB - ) - if pending { - block, statedb = b.pendingBlock, b.pendingState.Copy() - } else { - block = b.blockchain.CurrentBlock() - statedb, _ = b.blockchain.State() + return statedb.GetCode(contract), nil +} + +// BalanceAt returns the wei balance of a certain account in the blockchain. +func (b *SimulatedBackend) BalanceAt(ctx context.Context, contract common.Address, blockNumber *big.Int) (*big.Int, error) { + b.mu.Lock() + defer b.mu.Unlock() + + if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 { + return nil, errBlockNumberUnsupported } - // If there's no code to interact with, respond with an appropriate error - if code := statedb.GetCode(contract); len(code) == 0 { - return nil, bind.ErrNoCode + statedb, _ := b.blockchain.State() + return statedb.GetBalance(contract), nil +} + +// NonceAt returns the nonce of a certain account in the blockchain. +func (b *SimulatedBackend) NonceAt(ctx context.Context, contract common.Address, blockNumber *big.Int) (uint64, error) { + b.mu.Lock() + defer b.mu.Unlock() + + if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 { + return 0, errBlockNumberUnsupported } - // Set infinite balance to the a fake caller account - from := statedb.GetOrNewStateObject(common.Address{}) - from.SetBalance(common.MaxBig) + statedb, _ := b.blockchain.State() + return statedb.GetNonce(contract), nil +} + +// StorageAt returns the value of key in the storage of an account in the blockchain. +func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { + b.mu.Lock() + defer b.mu.Unlock() - // Assemble the call invocation to measure the gas usage - msg := callmsg{ - from: from, - to: &contract, - gasPrice: new(big.Int), - gasLimit: common.MaxBig, - value: new(big.Int), - data: data, + if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 { + return nil, errBlockNumberUnsupported } - // Execute the call and return - vmenv := core.NewEnv(statedb, chainConfig, b.blockchain, msg, block.Header(), vm.Config{}) - gaspool := new(core.GasPool).AddGas(common.MaxBig) + statedb, _ := b.blockchain.State() + if obj := statedb.GetStateObject(contract); obj != nil { + val := obj.GetState(key) + return val[:], nil + } + return nil, nil +} + +// TransactionReceipt returns the receipt of a transaction. +func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { + return core.GetReceipt(b.database, txHash), nil +} + +// PendingCodeAt returns the code associated with an account in the pending state. +func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) { + b.mu.Lock() + defer b.mu.Unlock() + + return b.pendingState.GetCode(contract), nil +} + +// CallContract executes a contract call. +func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + b.mu.Lock() + defer b.mu.Unlock() + + if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 { + return nil, errBlockNumberUnsupported + } + state, err := b.blockchain.State() + if err != nil { + return nil, err + } + rval, _, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state) + return rval, err +} - out, _, err := core.ApplyMessage(vmenv, msg, gaspool) - return out, err +// PendingCallContract executes a contract call on the pending state. +func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) { + b.mu.Lock() + defer b.mu.Unlock() + + rval, _, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState.Copy()) + return rval, err } -// PendingAccountNonce implements ContractTransactor.PendingAccountNonce, retrieving +// PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving // the nonce currently pending for the account. -func (b *SimulatedBackend) PendingAccountNonce(ctx context.Context, account common.Address) (uint64, error) { +func (b *SimulatedBackend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + b.mu.Lock() + defer b.mu.Unlock() + return b.pendingState.GetOrNewStateObject(account).Nonce(), nil } @@ -140,45 +195,55 @@ func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error return big.NewInt(1), nil } -// EstimateGasLimit implements ContractTransactor.EstimateGasLimit, executing the -// requested code against the currently pending block/state and returning the used -// gas. -func (b *SimulatedBackend) EstimateGasLimit(ctx context.Context, sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error) { - // Create a copy of the currently pending state db to screw around with - var ( - block = b.pendingBlock - statedb = b.pendingState.Copy() - ) - // If there's no code to interact with, respond with an appropriate error - if contract != nil { - if code := statedb.GetCode(*contract); len(code) == 0 { - return nil, bind.ErrNoCode - } - } - // Set infinite balance to the a fake caller account - from := statedb.GetOrNewStateObject(sender) - from.SetBalance(common.MaxBig) +// EstimateGas executes the requested code against the currently pending block/state and +// returns the used amount of gas. +func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMsg) (*big.Int, error) { + b.mu.Lock() + defer b.mu.Unlock() - // Assemble the call invocation to measure the gas usage - msg := callmsg{ - from: from, - to: contract, - gasPrice: new(big.Int), - gasLimit: common.MaxBig, - value: value, - data: data, + _, gas, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState.Copy()) + return gas, err +} + +// callContract implemens common code between normal and pending contract calls. +// state is modified during execution, make sure to copy it if necessary. +func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) ([]byte, *big.Int, error) { + // Ensure message is initialized properly. + if call.GasPrice == nil { + call.GasPrice = big.NewInt(1) + } + if call.Gas == nil || call.Gas.BitLen() == 0 { + call.Gas = big.NewInt(50000000) } - // Execute the call and return + if call.Value == nil { + call.Value = new(big.Int) + } + // Set infinite balance to the fake caller account. + from := statedb.GetOrNewStateObject(call.From) + from.SetBalance(common.MaxBig) + // Execute the call. + msg := callmsg{call} vmenv := core.NewEnv(statedb, chainConfig, b.blockchain, msg, block.Header(), vm.Config{}) gaspool := new(core.GasPool).AddGas(common.MaxBig) - - _, gas, _, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb() - return gas, err + ret, gasUsed, _, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb() + return ret, gasUsed, err } -// SendTransaction implements ContractTransactor.SendTransaction, delegating the raw -// transaction injection to the remote node. +// SendTransaction updates the pending block to include the given transaction. +// It panics if the transaction is invalid. func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error { + b.mu.Lock() + defer b.mu.Unlock() + + sender, err := tx.From() + if err != nil { + panic(fmt.Errorf("invalid transaction: %v", err)) + } + nonce := b.pendingState.GetNonce(sender) + if tx.Nonce() != nonce { + panic(fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce)) + } + blocks, _ := core.GenerateChain(nil, b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) { for _, tx := range b.pendingBlock.Transactions() { block.AddTx(tx) @@ -187,26 +252,20 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa }) b.pendingBlock = blocks[0] b.pendingState, _ = state.New(b.pendingBlock.Root(), b.database) - return nil } // callmsg implements core.Message to allow passing it as a transaction simulator. type callmsg struct { - from *state.StateObject - to *common.Address - gasLimit *big.Int - gasPrice *big.Int - value *big.Int - data []byte + ethereum.CallMsg } -func (m callmsg) From() (common.Address, error) { return m.from.Address(), nil } -func (m callmsg) FromFrontier() (common.Address, error) { return m.from.Address(), nil } +func (m callmsg) From() (common.Address, error) { return m.CallMsg.From, nil } +func (m callmsg) FromFrontier() (common.Address, error) { return m.CallMsg.From, nil } func (m callmsg) Nonce() uint64 { return 0 } func (m callmsg) CheckNonce() bool { return false } -func (m callmsg) To() *common.Address { return m.to } -func (m callmsg) GasPrice() *big.Int { return m.gasPrice } -func (m callmsg) Gas() *big.Int { return m.gasLimit } -func (m callmsg) Value() *big.Int { return m.value } -func (m callmsg) Data() []byte { return m.data } +func (m callmsg) To() *common.Address { return m.CallMsg.To } +func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } +func (m callmsg) Gas() *big.Int { return m.CallMsg.Gas } +func (m callmsg) Value() *big.Int { return m.CallMsg.Value } +func (m callmsg) Data() []byte { return m.CallMsg.Data } |