Skip to content

Commit 93b1171

Browse files
gitterigballet
authored andcommitted
accounts/abi/backends/simulated: add more API methods (#5) (ethereum#20208)
* Add more functionality to the sim (#5) * backends: implement more of ethclient in sim * backends: add BlockByNumber to simulated backend * backends: make simulated progress function agree with syncprogress interface for client * backends: add more tests * backends: add more comments * backends: fix sim for index in tx and add tests * backends: add lock back to estimategas * backends: goimports * backends: go ci lint * Add more functionality to the sim (#5) * backends: implement more of ethclient in sim * backends: add BlockByNumber to simulated backend * backends: make simulated progress function agree with syncprogress interface for client * backends: add more tests * backends: add more comments * backends: fix sim for index in tx and add tests * backends: add lock back to estimategas * backends: goimports * backends: go ci lint * assert errs
1 parent 6ae9dc1 commit 93b1171

File tree

2 files changed

+910
-6
lines changed

2 files changed

+910
-6
lines changed

accounts/abi/bind/backends/simulated.go

Lines changed: 147 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,17 @@ import (
4646
var _ bind.ContractBackend = (*SimulatedBackend)(nil)
4747

4848
var (
49-
errBlockNumberUnsupported = errors.New("simulatedBackend cannot access blocks other than the latest block")
50-
errGasEstimationFailed = errors.New("gas required exceeds allowance or always failing transaction")
49+
errBlockNumberUnsupported = errors.New("simulatedBackend cannot access blocks other than the latest block")
50+
errBlockDoesNotExist = errors.New("block does not exist in blockchain")
51+
errTransactionDoesNotExist = errors.New("transaction does not exist")
52+
errGasEstimationFailed = errors.New("gas required exceeds allowance or always failing transaction")
5153
)
5254

5355
// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in
5456
// the background. Its main purpose is to allow easily testing contract bindings.
57+
// Simulated backend implements the following interfaces:
58+
// ChainReader, ChainStateReader, ContractBackend, ContractCaller, ContractFilterer, ContractTransactor,
59+
// DeployBackend, GasEstimator, GasPricer, LogFilterer, PendingContractCaller, TransactionReader, and TransactionSender
5560
type SimulatedBackend struct {
5661
database ethdb.Database // In memory database to store our testing data
5762
blockchain *core.BlockChain // Ethereum blockchain to handle the consensus
@@ -173,6 +178,9 @@ func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Addres
173178

174179
// TransactionReceipt returns the receipt of a transaction.
175180
func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
181+
b.mu.Lock()
182+
defer b.mu.Unlock()
183+
176184
receipt, _, _, _ := rawdb.ReadReceipt(b.database, txHash, b.config)
177185
return receipt, nil
178186
}
@@ -196,6 +204,115 @@ func (b *SimulatedBackend) TransactionByHash(ctx context.Context, txHash common.
196204
return nil, false, ethereum.NotFound
197205
}
198206

207+
// BlockByHash retrieves a block based on the block hash
208+
func (b *SimulatedBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
209+
b.mu.Lock()
210+
defer b.mu.Unlock()
211+
212+
if hash == b.pendingBlock.Hash() {
213+
return b.pendingBlock, nil
214+
}
215+
216+
block := b.blockchain.GetBlockByHash(hash)
217+
if block != nil {
218+
return block, nil
219+
}
220+
221+
return nil, errBlockDoesNotExist
222+
}
223+
224+
// BlockByNumber retrieves a block from the database by number, caching it
225+
// (associated with its hash) if found.
226+
func (b *SimulatedBackend) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
227+
b.mu.Lock()
228+
defer b.mu.Unlock()
229+
230+
if number == nil || number.Cmp(b.pendingBlock.Number()) == 0 {
231+
return b.blockchain.CurrentBlock(), nil
232+
}
233+
234+
block := b.blockchain.GetBlockByNumber(uint64(number.Int64()))
235+
if block == nil {
236+
return nil, errBlockDoesNotExist
237+
}
238+
239+
return block, nil
240+
}
241+
242+
// HeaderByHash returns a block header from the current canonical chain.
243+
func (b *SimulatedBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
244+
b.mu.Lock()
245+
defer b.mu.Unlock()
246+
247+
if hash == b.pendingBlock.Hash() {
248+
return b.pendingBlock.Header(), nil
249+
}
250+
251+
header := b.blockchain.GetHeaderByHash(hash)
252+
if header == nil {
253+
return nil, errBlockDoesNotExist
254+
}
255+
256+
return header, nil
257+
}
258+
259+
// HeaderByNumber returns a block header from the current canonical chain. If number is
260+
// nil, the latest known header is returned.
261+
func (b *SimulatedBackend) HeaderByNumber(ctx context.Context, block *big.Int) (*types.Header, error) {
262+
b.mu.Lock()
263+
defer b.mu.Unlock()
264+
265+
if block == nil || block.Cmp(b.pendingBlock.Number()) == 0 {
266+
return b.blockchain.CurrentHeader(), nil
267+
}
268+
269+
return b.blockchain.GetHeaderByNumber(uint64(block.Int64())), nil
270+
}
271+
272+
// TransactionCount returns the number of transactions in a given block
273+
func (b *SimulatedBackend) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) {
274+
b.mu.Lock()
275+
defer b.mu.Unlock()
276+
277+
if blockHash == b.pendingBlock.Hash() {
278+
return uint(b.pendingBlock.Transactions().Len()), nil
279+
}
280+
281+
block := b.blockchain.GetBlockByHash(blockHash)
282+
if block == nil {
283+
return uint(0), errBlockDoesNotExist
284+
}
285+
286+
return uint(block.Transactions().Len()), nil
287+
}
288+
289+
// TransactionInBlock returns the transaction for a specific block at a specific index
290+
func (b *SimulatedBackend) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) {
291+
b.mu.Lock()
292+
defer b.mu.Unlock()
293+
294+
if blockHash == b.pendingBlock.Hash() {
295+
transactions := b.pendingBlock.Transactions()
296+
if uint(len(transactions)) < index+1 {
297+
return nil, errTransactionDoesNotExist
298+
}
299+
300+
return transactions[index], nil
301+
}
302+
303+
block := b.blockchain.GetBlockByHash(blockHash)
304+
if block == nil {
305+
return nil, errBlockDoesNotExist
306+
}
307+
308+
transactions := block.Transactions()
309+
if uint(len(transactions)) < index+1 {
310+
return nil, errTransactionDoesNotExist
311+
}
312+
313+
return transactions[index], nil
314+
}
315+
199316
// PendingCodeAt returns the code associated with an account in the pending state.
200317
func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
201318
b.mu.Lock()
@@ -419,10 +536,38 @@ func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethere
419536
}), nil
420537
}
421538

539+
// SubscribeNewHead returns an event subscription for a new header
540+
func (b *SimulatedBackend) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) {
541+
// subscribe to a new head
542+
sink := make(chan *types.Header)
543+
sub := b.events.SubscribeNewHeads(sink)
544+
545+
return event.NewSubscription(func(quit <-chan struct{}) error {
546+
defer sub.Unsubscribe()
547+
for {
548+
select {
549+
case head := <-sink:
550+
select {
551+
case ch <- head:
552+
case err := <-sub.Err():
553+
return err
554+
case <-quit:
555+
return nil
556+
}
557+
case err := <-sub.Err():
558+
return err
559+
case <-quit:
560+
return nil
561+
}
562+
}
563+
}), nil
564+
}
565+
422566
// AdjustTime adds a time shift to the simulated clock.
423567
func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
424568
b.mu.Lock()
425569
defer b.mu.Unlock()
570+
426571
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
427572
for _, tx := range b.pendingBlock.Transactions() {
428573
block.AddTx(tx)

0 commit comments

Comments
 (0)