aboutsummaryrefslogtreecommitdiffstats
path: root/accounts/abi/bind/backends/simulated.go
diff options
context:
space:
mode:
authorFelix Lange <fjl@twurst.com>2016-08-23 05:20:13 +0800
committerFelix Lange <fjl@twurst.com>2016-08-23 05:20:13 +0800
commitc97df052a96820742fffdbb3ef5e77dbf1397637 (patch)
tree363e7e1a3439424e00a4e7e72a701b97f737e6aa /accounts/abi/bind/backends/simulated.go
parentd62d5fe59aedf9635a175d663632512b1cbbf2c3 (diff)
downloaddexon-c97df052a96820742fffdbb3ef5e77dbf1397637.tar
dexon-c97df052a96820742fffdbb3ef5e77dbf1397637.tar.gz
dexon-c97df052a96820742fffdbb3ef5e77dbf1397637.tar.bz2
dexon-c97df052a96820742fffdbb3ef5e77dbf1397637.tar.lz
dexon-c97df052a96820742fffdbb3ef5e77dbf1397637.tar.xz
dexon-c97df052a96820742fffdbb3ef5e77dbf1397637.tar.zst
dexon-c97df052a96820742fffdbb3ef5e77dbf1397637.zip
accounts/abi/bind: add utilities for waiting on transactions
The need for these functions comes up in code that actually deploys and uses contracts. As of this commit, they can be used with both SimulatedBackend and ethclient. SimulatedBackend gains some additional methods in the process and is now safe for concurrent use.
Diffstat (limited to 'accounts/abi/bind/backends/simulated.go')
-rw-r--r--accounts/abi/bind/backends/simulated.go103
1 files changed, 88 insertions, 15 deletions
diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go
index c2542f40e..29b4e8ea3 100644
--- a/accounts/abi/bind/backends/simulated.go
+++ b/accounts/abi/bind/backends/simulated.go
@@ -17,8 +17,10 @@
package backends
import (
+ "errors"
"fmt"
"math/big"
+ "sync"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
@@ -38,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
}
@@ -54,53 +59,109 @@ 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)
}
-// CodeAt implements ChainStateReader.CodeAt, returning the code associated with
-// a certain account at a given block number in the blockchain.
+// 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, fmt.Errorf("SimulatedBackend cannot access blocks other than the latest block")
+ return nil, errBlockNumberUnsupported
}
statedb, _ := b.blockchain.State()
return statedb.GetCode(contract), nil
}
-// PendingCodeAt implements PendingStateReader.PendingCodeAt, returning the
-// code associated with a certain account in the pending state of the blockchain.
+// 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
+ }
+ 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
+ }
+ 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()
+
+ if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
+ return nil, errBlockNumberUnsupported
+ }
+ 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, fmt.Errorf("SimulatedBackend cannot access blocks other than the latest block")
+ return nil, errBlockNumberUnsupported
}
state, err := b.blockchain.State()
if err != nil {
@@ -112,6 +173,9 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallM
// 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
}
@@ -119,6 +183,9 @@ func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereu
// PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving
// the nonce currently pending for the account.
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
}
@@ -131,6 +198,9 @@ func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error
// 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()
+
_, gas, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState.Copy())
return gas, err
}
@@ -162,6 +232,9 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
// 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))