Skip to content

Commit 1d57f22

Browse files
ggwpezkaralabe
andauthored
accounts/abi/bind/backends: add simulated reorgs (#22624)
* accounts/abi/bind/backends: add blockByHashNoLock Signed-off-by: Oliver Tale-Yazdi <[email protected]> * accounts/abi/bind/backends: add 'parent' arg to rollback Signed-off-by: Oliver Tale-Yazdi <[email protected]> * accounts/abi/bind/backends: add simulated forks Signed-off-by: Oliver Tale-Yazdi <[email protected]> * accounts/abi/bind/backends: minor nitpicks * accounts/abi/bind/backends: don't add defensive panics Co-authored-by: Péter Szilágyi <[email protected]>
1 parent ccf53da commit 1d57f22

File tree

2 files changed

+241
-34
lines changed

2 files changed

+241
-34
lines changed

accounts/abi/bind/backends/simulated.go

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.Genesis
8686
config: genesis.Config,
8787
events: filters.NewEventSystem(&filterBackend{database, blockchain}, false),
8888
}
89-
backend.rollback()
89+
backend.rollback(blockchain.CurrentBlock())
9090
return backend
9191
}
9292

@@ -112,30 +112,59 @@ func (b *SimulatedBackend) Commit() {
112112
if _, err := b.blockchain.InsertChain([]*types.Block{b.pendingBlock}); err != nil {
113113
panic(err) // This cannot happen unless the simulator is wrong, fail in that case
114114
}
115-
b.rollback()
115+
// Using the last inserted block here makes it possible to build on a side
116+
// chain after a fork.
117+
b.rollback(b.pendingBlock)
116118
}
117119

118120
// Rollback aborts all pending transactions, reverting to the last committed state.
119121
func (b *SimulatedBackend) Rollback() {
120122
b.mu.Lock()
121123
defer b.mu.Unlock()
122124

123-
b.rollback()
125+
b.rollback(b.blockchain.CurrentBlock())
124126
}
125127

126-
func (b *SimulatedBackend) rollback() {
127-
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {})
128+
func (b *SimulatedBackend) rollback(parent *types.Block) {
129+
blocks, _ := core.GenerateChain(b.config, parent, ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {})
128130

129131
b.pendingBlock = blocks[0]
130132
b.pendingState, _ = state.New(b.pendingBlock.Root(), b.blockchain.StateCache(), nil)
131133
}
132134

135+
// Fork creates a side-chain that can be used to simulate reorgs.
136+
//
137+
// This function should be called with the ancestor block where the new side
138+
// chain should be started. Transactions (old and new) can then be applied on
139+
// top and Commit-ed.
140+
//
141+
// Note, the side-chain will only become canonical (and trigger the events) when
142+
// it becomes longer. Until then CallContract will still operate on the current
143+
// canonical chain.
144+
//
145+
// There is a % chance that the side chain becomes canonical at the same length
146+
// to simulate live network behavior.
147+
func (b *SimulatedBackend) Fork(ctx context.Context, parent common.Hash) error {
148+
b.mu.Lock()
149+
defer b.mu.Unlock()
150+
151+
if len(b.pendingBlock.Transactions()) != 0 {
152+
return errors.New("pending block dirty")
153+
}
154+
block, err := b.blockByHash(ctx, parent)
155+
if err != nil {
156+
return err
157+
}
158+
b.rollback(block)
159+
return nil
160+
}
161+
133162
// stateByBlockNumber retrieves a state by a given blocknumber.
134163
func (b *SimulatedBackend) stateByBlockNumber(ctx context.Context, blockNumber *big.Int) (*state.StateDB, error) {
135164
if blockNumber == nil || blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) == 0 {
136165
return b.blockchain.State()
137166
}
138-
block, err := b.blockByNumberNoLock(ctx, blockNumber)
167+
block, err := b.blockByNumber(ctx, blockNumber)
139168
if err != nil {
140169
return nil, err
141170
}
@@ -228,6 +257,11 @@ func (b *SimulatedBackend) BlockByHash(ctx context.Context, hash common.Hash) (*
228257
b.mu.Lock()
229258
defer b.mu.Unlock()
230259

260+
return b.blockByHash(ctx, hash)
261+
}
262+
263+
// blockByHash retrieves a block based on the block hash without Locking.
264+
func (b *SimulatedBackend) blockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
231265
if hash == b.pendingBlock.Hash() {
232266
return b.pendingBlock, nil
233267
}
@@ -246,12 +280,12 @@ func (b *SimulatedBackend) BlockByNumber(ctx context.Context, number *big.Int) (
246280
b.mu.Lock()
247281
defer b.mu.Unlock()
248282

249-
return b.blockByNumberNoLock(ctx, number)
283+
return b.blockByNumber(ctx, number)
250284
}
251285

252-
// blockByNumberNoLock retrieves a block from the database by number, caching it
286+
// blockByNumber retrieves a block from the database by number, caching it
253287
// (associated with its hash) if found without Lock.
254-
func (b *SimulatedBackend) blockByNumberNoLock(ctx context.Context, number *big.Int) (*types.Block, error) {
288+
func (b *SimulatedBackend) blockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
255289
if number == nil || number.Cmp(b.pendingBlock.Number()) == 0 {
256290
return b.blockchain.CurrentBlock(), nil
257291
}
@@ -559,8 +593,12 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
559593
b.mu.Lock()
560594
defer b.mu.Unlock()
561595

562-
// Check transaction validity.
563-
block := b.blockchain.CurrentBlock()
596+
// Get the last block
597+
block, err := b.blockByHash(ctx, b.pendingBlock.ParentHash())
598+
if err != nil {
599+
panic("could not fetch parent")
600+
}
601+
// Check transaction validity
564602
signer := types.MakeSigner(b.blockchain.Config(), block.Number())
565603
sender, err := types.Sender(signer, tx)
566604
if err != nil {
@@ -570,8 +608,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
570608
if tx.Nonce() != nonce {
571609
panic(fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce))
572610
}
573-
574-
// Include tx in chain.
611+
// Include tx in chain
575612
blocks, _ := core.GenerateChain(b.config, block, ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
576613
for _, tx := range b.pendingBlock.Transactions() {
577614
block.AddTxWithChain(b.blockchain, tx)

0 commit comments

Comments
 (0)