Skip to content

Commit 7bf8e94

Browse files
committed
Merge pull request ethereum#1669 from obscuren/tx-pool-auto-resend
core, xeth: chain reorg move missing transactions to transaction pool
2 parents be76a68 + eaa4473 commit 7bf8e94

13 files changed

+231
-57
lines changed

cmd/geth/js_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *eth
9292

9393
db, _ := ethdb.NewMemDatabase()
9494

95-
core.WriteGenesisBlockForTesting(db, common.HexToAddress(testAddress), common.String2Big(testBalance))
95+
core.WriteGenesisBlockForTesting(db, core.GenesisAccount{common.HexToAddress(testAddress), common.String2Big(testBalance)})
9696
ks := crypto.NewKeyStorePlain(filepath.Join(tmp, "keystore"))
9797
am := accounts.NewManager(ks)
9898
conf := &eth.Config{

common/natspec/natspec_e2e_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ func testEth(t *testing.T) (ethereum *eth.Ethereum, err error) {
134134

135135
db, _ := ethdb.NewMemDatabase()
136136
// set up mock genesis with balance on the testAddress
137-
core.WriteGenesisBlockForTesting(db, common.HexToAddress(testAddress), common.String2Big(testBalance))
137+
core.WriteGenesisBlockForTesting(db, core.GenesisAccount{common.HexToAddress(testAddress), common.String2Big(testBalance)})
138138

139139
// only use minimalistic stack with no networking
140140
ethereum, err = eth.New(&eth.Config{

core/bench_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) {
162162

163163
// Generate a chain of b.N blocks using the supplied block
164164
// generator function.
165-
genesis := WriteGenesisBlockForTesting(db, benchRootAddr, benchRootFunds)
165+
genesis := WriteGenesisBlockForTesting(db, GenesisAccount{benchRootAddr, benchRootFunds})
166166
chain := GenerateChain(genesis, db, b.N, gen)
167167

168168
// Time the insertion of the new chain.

core/chain_makers_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func ExampleGenerateChain() {
4242
)
4343

4444
// Ensure that key1 has some funds in the genesis block.
45-
genesis := WriteGenesisBlockForTesting(db, addr1, big.NewInt(1000000))
45+
genesis := WriteGenesisBlockForTesting(db, GenesisAccount{addr1, big.NewInt(1000000)})
4646

4747
// This call generates a chain of 5 blocks. The function runs for
4848
// each block and adds different features to gen based on the

core/chain_manager.go

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -569,18 +569,17 @@ func (self *ChainManager) WriteBlock(block *types.Block) (status writeStatus, er
569569
// chain fork
570570
if block.ParentHash() != cblock.Hash() {
571571
// during split we merge two different chains and create the new canonical chain
572-
err := self.merge(cblock, block)
572+
err := self.reorg(cblock, block)
573573
if err != nil {
574574
return NonStatTy, err
575575
}
576-
status = SplitStatTy
577576
}
577+
status = CanonStatTy
578+
578579
self.mu.Lock()
579580
self.setTotalDifficulty(td)
580581
self.insert(block)
581582
self.mu.Unlock()
582-
583-
status = CanonStatTy
584583
} else {
585584
status = SideStatTy
586585
}
@@ -681,9 +680,11 @@ func (self *ChainManager) InsertChain(chain types.Blocks) (int, error) {
681680

682681
return i, err
683682
}
683+
if err := PutBlockReceipts(self.chainDb, block, receipts); err != nil {
684+
glog.V(logger.Warn).Infoln("error writing block receipts:", err)
685+
}
684686

685687
txcount += len(block.Transactions())
686-
687688
// write the block to the chain and get the status
688689
status, err := self.WriteBlock(block)
689690
if err != nil {
@@ -711,10 +712,6 @@ func (self *ChainManager) InsertChain(chain types.Blocks) (int, error) {
711712
queue[i] = ChainSplitEvent{block, logs}
712713
queueEvent.splitCount++
713714
}
714-
if err := PutBlockReceipts(self.chainDb, block, receipts); err != nil {
715-
glog.V(logger.Warn).Infoln("error writing block receipts:", err)
716-
}
717-
718715
stats.processed++
719716
}
720717

@@ -729,20 +726,26 @@ func (self *ChainManager) InsertChain(chain types.Blocks) (int, error) {
729726
return 0, nil
730727
}
731728

732-
// diff takes two blocks, an old chain and a new chain and will reconstruct the blocks and inserts them
733-
// to be part of the new canonical chain.
734-
func (self *ChainManager) diff(oldBlock, newBlock *types.Block) (types.Blocks, error) {
729+
// reorgs takes two blocks, an old chain and a new chain and will reconstruct the blocks and inserts them
730+
// to be part of the new canonical chain and accumulates potential missing transactions and post an
731+
// event about them
732+
func (self *ChainManager) reorg(oldBlock, newBlock *types.Block) error {
733+
self.mu.Lock()
734+
defer self.mu.Unlock()
735+
735736
var (
736737
newChain types.Blocks
737738
commonBlock *types.Block
738739
oldStart = oldBlock
739740
newStart = newBlock
741+
deletedTxs types.Transactions
740742
)
741743

742744
// first reduce whoever is higher bound
743745
if oldBlock.NumberU64() > newBlock.NumberU64() {
744746
// reduce old chain
745747
for oldBlock = oldBlock; oldBlock != nil && oldBlock.NumberU64() != newBlock.NumberU64(); oldBlock = self.GetBlock(oldBlock.ParentHash()) {
748+
deletedTxs = append(deletedTxs, oldBlock.Transactions()...)
746749
}
747750
} else {
748751
// reduce new chain and append new chain blocks for inserting later on
@@ -751,10 +754,10 @@ func (self *ChainManager) diff(oldBlock, newBlock *types.Block) (types.Blocks, e
751754
}
752755
}
753756
if oldBlock == nil {
754-
return nil, fmt.Errorf("Invalid old chain")
757+
return fmt.Errorf("Invalid old chain")
755758
}
756759
if newBlock == nil {
757-
return nil, fmt.Errorf("Invalid new chain")
760+
return fmt.Errorf("Invalid new chain")
758761
}
759762

760763
numSplit := newBlock.Number()
@@ -764,13 +767,14 @@ func (self *ChainManager) diff(oldBlock, newBlock *types.Block) (types.Blocks, e
764767
break
765768
}
766769
newChain = append(newChain, newBlock)
770+
deletedTxs = append(deletedTxs, oldBlock.Transactions()...)
767771

768772
oldBlock, newBlock = self.GetBlock(oldBlock.ParentHash()), self.GetBlock(newBlock.ParentHash())
769773
if oldBlock == nil {
770-
return nil, fmt.Errorf("Invalid old chain")
774+
return fmt.Errorf("Invalid old chain")
771775
}
772776
if newBlock == nil {
773-
return nil, fmt.Errorf("Invalid new chain")
777+
return fmt.Errorf("Invalid new chain")
774778
}
775779
}
776780

@@ -779,27 +783,27 @@ func (self *ChainManager) diff(oldBlock, newBlock *types.Block) (types.Blocks, e
779783
glog.Infof("Chain split detected @ %x. Reorganising chain from #%v %x to %x", commonHash[:4], numSplit, oldStart.Hash().Bytes()[:4], newStart.Hash().Bytes()[:4])
780784
}
781785

782-
return newChain, nil
783-
}
784-
785-
// merge merges two different chain to the new canonical chain
786-
func (self *ChainManager) merge(oldBlock, newBlock *types.Block) error {
787-
newChain, err := self.diff(oldBlock, newBlock)
788-
if err != nil {
789-
return fmt.Errorf("chain reorg failed: %v", err)
790-
}
791-
786+
var addedTxs types.Transactions
792787
// insert blocks. Order does not matter. Last block will be written in ImportChain itself which creates the new head properly
793-
self.mu.Lock()
794788
for _, block := range newChain {
795789
// insert the block in the canonical way, re-writing history
796790
self.insert(block)
797791
// write canonical receipts and transactions
798792
PutTransactions(self.chainDb, block, block.Transactions())
799793
PutReceipts(self.chainDb, GetBlockReceipts(self.chainDb, block.Hash()))
800794

795+
addedTxs = append(addedTxs, block.Transactions()...)
796+
}
797+
798+
// calculate the difference between deleted and added transactions
799+
diff := types.TxDifference(deletedTxs, addedTxs)
800+
// When transactions get deleted from the database that means the
801+
// receipts that were created in the fork must also be deleted
802+
for _, tx := range diff {
803+
DeleteReceipt(self.chainDb, tx.Hash())
804+
DeleteTransaction(self.chainDb, tx.Hash())
801805
}
802-
self.mu.Unlock()
806+
self.eventMux.Post(RemovedTransactionEvent{diff})
803807

804808
return nil
805809
}

core/chain_manager_test.go

Lines changed: 111 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ import (
3030
"github.com/ethereum/go-ethereum/common"
3131
"github.com/ethereum/go-ethereum/core/state"
3232
"github.com/ethereum/go-ethereum/core/types"
33+
"github.com/ethereum/go-ethereum/crypto"
3334
"github.com/ethereum/go-ethereum/ethdb"
3435
"github.com/ethereum/go-ethereum/event"
36+
"github.com/ethereum/go-ethereum/params"
3537
"github.com/ethereum/go-ethereum/pow"
3638
"github.com/ethereum/go-ethereum/rlp"
3739
"github.com/hashicorp/golang-lru"
@@ -483,19 +485,115 @@ func TestInsertNonceError(t *testing.T) {
483485
}
484486
}
485487

486-
/*
487-
func TestGenesisMismatch(t *testing.T) {
488-
db, _ := ethdb.NewMemDatabase()
489-
var mux event.TypeMux
490-
genesis := GenesisBlock(0, db)
491-
_, err := NewChainManager(genesis, db, db, db, thePow(), &mux)
492-
if err != nil {
493-
t.Error(err)
488+
// Tests that chain reorganizations handle transaction removals and reinsertions.
489+
func TestChainTxReorgs(t *testing.T) {
490+
params.MinGasLimit = big.NewInt(125000) // Minimum the gas limit may ever be.
491+
params.GenesisGasLimit = big.NewInt(3141592) // Gas limit of the Genesis block.
492+
493+
var (
494+
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
495+
key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
496+
key3, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
497+
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
498+
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
499+
addr3 = crypto.PubkeyToAddress(key3.PublicKey)
500+
db, _ = ethdb.NewMemDatabase()
501+
)
502+
genesis := WriteGenesisBlockForTesting(db,
503+
GenesisAccount{addr1, big.NewInt(1000000)},
504+
GenesisAccount{addr2, big.NewInt(1000000)},
505+
GenesisAccount{addr3, big.NewInt(1000000)},
506+
)
507+
// Create two transactions shared between the chains:
508+
// - postponed: transaction included at a later block in the forked chain
509+
// - swapped: transaction included at the same block number in the forked chain
510+
postponed, _ := types.NewTransaction(0, addr1, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key1)
511+
swapped, _ := types.NewTransaction(1, addr1, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key1)
512+
513+
// Create two transactions that will be dropped by the forked chain:
514+
// - pastDrop: transaction dropped retroactively from a past block
515+
// - freshDrop: transaction dropped exactly at the block where the reorg is detected
516+
var pastDrop, freshDrop *types.Transaction
517+
518+
// Create three transactions that will be added in the forked chain:
519+
// - pastAdd: transaction added before the reorganiztion is detected
520+
// - freshAdd: transaction added at the exact block the reorg is detected
521+
// - futureAdd: transaction added after the reorg has already finished
522+
var pastAdd, freshAdd, futureAdd *types.Transaction
523+
524+
chain := GenerateChain(genesis, db, 3, func(i int, gen *BlockGen) {
525+
switch i {
526+
case 0:
527+
pastDrop, _ = types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key2)
528+
529+
gen.AddTx(pastDrop) // This transaction will be dropped in the fork from below the split point
530+
gen.AddTx(postponed) // This transaction will be postponed till block #3 in the fork
531+
532+
case 2:
533+
freshDrop, _ = types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key2)
534+
535+
gen.AddTx(freshDrop) // This transaction will be dropped in the fork from exactly at the split point
536+
gen.AddTx(swapped) // This transaction will be swapped out at the exact height
537+
538+
gen.OffsetTime(9) // Lower the block difficulty to simulate a weaker chain
539+
}
540+
})
541+
// Import the chain. This runs all block validation rules.
542+
evmux := &event.TypeMux{}
543+
chainman, _ := NewChainManager(db, FakePow{}, evmux)
544+
chainman.SetProcessor(NewBlockProcessor(db, FakePow{}, chainman, evmux))
545+
if i, err := chainman.InsertChain(chain); err != nil {
546+
t.Fatalf("failed to insert original chain[%d]: %v", i, err)
547+
}
548+
549+
// overwrite the old chain
550+
chain = GenerateChain(genesis, db, 5, func(i int, gen *BlockGen) {
551+
switch i {
552+
case 0:
553+
pastAdd, _ = types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key3)
554+
gen.AddTx(pastAdd) // This transaction needs to be injected during reorg
555+
556+
case 2:
557+
gen.AddTx(postponed) // This transaction was postponed from block #1 in the original chain
558+
gen.AddTx(swapped) // This transaction was swapped from the exact current spot in the original chain
559+
560+
freshAdd, _ = types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key3)
561+
gen.AddTx(freshAdd) // This transaction will be added exactly at reorg time
562+
563+
case 3:
564+
futureAdd, _ = types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key3)
565+
gen.AddTx(futureAdd) // This transaction will be added after a full reorg
566+
}
567+
})
568+
if _, err := chainman.InsertChain(chain); err != nil {
569+
t.Fatalf("failed to insert forked chain: %v", err)
494570
}
495-
genesis = GenesisBlock(1, db)
496-
_, err = NewChainManager(genesis, db, db, db, thePow(), &mux)
497-
if err == nil {
498-
t.Error("expected genesis mismatch error")
571+
572+
// removed tx
573+
for i, tx := range (types.Transactions{pastDrop, freshDrop}) {
574+
if GetTransaction(db, tx.Hash()) != nil {
575+
t.Errorf("drop %d: tx found while shouldn't have been", i)
576+
}
577+
if GetReceipt(db, tx.Hash()) != nil {
578+
t.Errorf("drop %d: receipt found while shouldn't have been", i)
579+
}
580+
}
581+
// added tx
582+
for i, tx := range (types.Transactions{pastAdd, freshAdd, futureAdd}) {
583+
if GetTransaction(db, tx.Hash()) == nil {
584+
t.Errorf("add %d: expected tx to be found", i)
585+
}
586+
if GetReceipt(db, tx.Hash()) == nil {
587+
t.Errorf("add %d: expected receipt to be found", i)
588+
}
589+
}
590+
// shared tx
591+
for i, tx := range (types.Transactions{postponed, swapped}) {
592+
if GetTransaction(db, tx.Hash()) == nil {
593+
t.Errorf("share %d: expected tx to be found", i)
594+
}
595+
if GetReceipt(db, tx.Hash()) == nil {
596+
t.Errorf("share %d: expected receipt to be found", i)
597+
}
499598
}
500599
}
501-
*/

core/events.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ type NewBlockEvent struct{ Block *types.Block }
3636
// NewMinedBlockEvent is posted when a block has been imported.
3737
type NewMinedBlockEvent struct{ Block *types.Block }
3838

39+
// RemovedTransactionEvent is posted when a reorg happens
40+
type RemovedTransactionEvent struct{ Txs types.Transactions }
41+
3942
// ChainSplit is posted when a new head is detected
4043
type ChainSplitEvent struct {
4144
Block *types.Block

core/genesis.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,15 +125,27 @@ func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big
125125
return block
126126
}
127127

128-
func WriteGenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big.Int) *types.Block {
128+
type GenesisAccount struct {
129+
Address common.Address
130+
Balance *big.Int
131+
}
132+
133+
func WriteGenesisBlockForTesting(db ethdb.Database, accounts ...GenesisAccount) *types.Block {
134+
accountJson := "{"
135+
for i, account := range accounts {
136+
if i != 0 {
137+
accountJson += ","
138+
}
139+
accountJson += fmt.Sprintf(`"0x%x":{"balance":"0x%x"}`, account.Address, account.Balance.Bytes())
140+
}
141+
accountJson += "}"
142+
129143
testGenesis := fmt.Sprintf(`{
130144
"nonce":"0x%x",
131145
"gasLimit":"0x%x",
132146
"difficulty":"0x%x",
133-
"alloc": {
134-
"0x%x":{"balance":"0x%x"}
135-
}
136-
}`, types.EncodeNonce(0), params.GenesisGasLimit.Bytes(), params.GenesisDifficulty.Bytes(), addr, balance.Bytes())
147+
"alloc": %s
148+
}`, types.EncodeNonce(0), params.GenesisGasLimit.Bytes(), params.GenesisDifficulty.Bytes(), accountJson)
137149
block, _ := WriteGenesisBlock(db, strings.NewReader(testGenesis))
138150
return block
139151
}

core/transaction_pool.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func NewTxPool(eventMux *event.TypeMux, currentStateFn stateFn, gasLimitFn func(
8181
gasLimit: gasLimitFn,
8282
minGasPrice: new(big.Int),
8383
pendingState: state.ManageState(currentStateFn()),
84-
events: eventMux.Subscribe(ChainHeadEvent{}, GasPriceChanged{}),
84+
events: eventMux.Subscribe(ChainHeadEvent{}, GasPriceChanged{}, RemovedTransactionEvent{}),
8585
}
8686
go pool.eventLoop()
8787

@@ -93,16 +93,18 @@ func (pool *TxPool) eventLoop() {
9393
// we need to know the new state. The new state will help us determine
9494
// the nonces in the managed state
9595
for ev := range pool.events.Chan() {
96-
pool.mu.Lock()
97-
9896
switch ev := ev.(type) {
9997
case ChainHeadEvent:
98+
pool.mu.Lock()
10099
pool.resetState()
100+
pool.mu.Unlock()
101101
case GasPriceChanged:
102+
pool.mu.Lock()
102103
pool.minGasPrice = ev.Price
104+
pool.mu.Unlock()
105+
case RemovedTransactionEvent:
106+
pool.AddTransactions(ev.Txs)
103107
}
104-
105-
pool.mu.Unlock()
106108
}
107109
}
108110

0 commit comments

Comments
 (0)