Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

light: fix txpool causes panic on reorgOnNewHead #54

Merged
merged 6 commits into from
Mar 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions core/headerchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,9 @@ type WhCallback func(*types.Header) error
func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) (int, error) {
// Do a sanity check that the provided chain is actually ordered and linked
for i := 1; i < len(chain); i++ {
if chain[i] == nil {
return 0, fmt.Errorf("header was nil")
}
if chain[i].Number.Uint64() != chain[i-1].Number.Uint64()+1 || chain[i].ParentHash != chain[i-1].Hash() {
// Chain broke ancestry, log a message (programming error) and skip insertion
log.Error("Non contiguous header insert", "number", chain[i].Number, "hash", chain[i].Hash(),
Expand Down Expand Up @@ -291,6 +294,9 @@ func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, writeHeader WhCa
log.Debug("Premature abort during headers import")
return i, errors.New("aborted")
}
if header == nil {
return i, fmt.Errorf("nil header")
}
// If the header's already known, skip it, otherwise store
hash := header.Hash()
if hc.HasHeader(hash, header.Number.Uint64()) {
Expand Down
19 changes: 18 additions & 1 deletion light/txpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,15 @@ func (pool *TxPool) rollbackTxs(hash common.Hash, txc txStateChanges) {
func (pool *TxPool) reorgOnNewHead(ctx context.Context, newHeader *types.Header) (txStateChanges, error) {
txc := make(txStateChanges)
oldh := pool.chain.GetHeaderByHash(pool.head)
if oldh == nil {
current := pool.chain.CurrentHeader()
if current.Number.Uint64() > 0 {
oldh = pool.chain.GetHeaderByHash(current.ParentHash)
} else {
oldh = current
}
pool.head = oldh.Hash()
}
newh := newHeader
// find common ancestor, create list of rolled back and new block hashes
var oldHashes, newHashes []common.Hash
Expand Down Expand Up @@ -308,7 +317,15 @@ func (pool *TxPool) setNewHead(head *types.Header) {
ctx, cancel := context.WithTimeout(context.Background(), blockCheckTimeout)
defer cancel()

txc, _ := pool.reorgOnNewHead(ctx, head)
if head == nil {
return
}

txc, err := pool.reorgOnNewHead(ctx, head)
if err != nil {
log.Info("light.txpool reorg failed", "error", err)
return
}
m, r := txc.getLists()
pool.relay.NewHead(pool.head, m, r)
pool.eip2f = pool.config.IsEnabled(pool.config.GetEthashEIP2Transition, head.Number)
Expand Down
41 changes: 30 additions & 11 deletions light/txpool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,33 +53,33 @@ func (self *testTxRelay) Discard(hashes []common.Hash) {
self.discard <- len(hashes)
}

const poolTestTxs = 1000
const poolTestBlocks = 100
const poolTestTxsN = 1000
const poolTestBlocksN = 100

// test tx 0..n-1
var testTx [poolTestTxs]*types.Transaction
var testTxSet [poolTestTxsN]*types.Transaction

// txs sent before block i
func sentTx(i int) int {
return int(math.Pow(float64(i)/float64(poolTestBlocks), 0.9) * poolTestTxs)
return int(math.Pow(float64(i)/float64(poolTestBlocksN), 0.9) * poolTestTxsN)
}

// txs included in block i or before that (minedTx(i) <= sentTx(i))
func minedTx(i int) int {
return int(math.Pow(float64(i)/float64(poolTestBlocks), 1.1) * poolTestTxs)
return int(math.Pow(float64(i)/float64(poolTestBlocksN), 1.1) * poolTestTxsN)
}

func txPoolTestChainGen(i int, block *core.BlockGen) {
s := minedTx(i)
e := minedTx(i + 1)
for i := s; i < e; i++ {
block.AddTx(testTx[i])
block.AddTx(testTxSet[i])
}
}

func TestTxPool(t *testing.T) {
for i := range testTx {
testTx[i], _ = types.SignTx(types.NewTransaction(uint64(i), acc1Addr, big.NewInt(10000), vars.TxGas, nil, nil), types.HomesteadSigner{}, testBankKey)
for i := range testTxSet {
testTxSet[i], _ = types.SignTx(types.NewTransaction(uint64(i), acc1Addr, big.NewInt(10000), vars.TxGas, nil, nil), types.HomesteadSigner{}, testBankKey)
}

var (
Expand All @@ -91,7 +91,7 @@ func TestTxPool(t *testing.T) {
core.MustCommitGenesis(ldb, &gspec)
// Assemble the test environment
blockchain, _ := core.NewBlockChain(sdb, nil, params.TestChainConfig, ethash.NewFullFaker(), vm.Config{}, nil)
gchain, _ := core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), sdb, poolTestBlocks, txPoolTestChainGen)
gchain, _ := core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), sdb, poolTestBlocksN, txPoolTestChainGen)
if _, err := blockchain.InsertChain(gchain); err != nil {
panic(err)
}
Expand All @@ -113,16 +113,35 @@ func TestTxPool(t *testing.T) {
s := sentTx(i - 1)
e := sentTx(i)
for i := s; i < e; i++ {
pool.Add(ctx, testTx[i])
pool.Add(ctx, testTxSet[i])
got := <-relay.send
exp := 1
if got != exp {
t.Errorf("relay.Send expected len = %d, got %d", exp, got)
}
}
if ii == len(gchain)/4 {
// Fuck up pool head
// This is an edge case that I'm not sure could really happen (hopefully not),
// but checking anyways. Call it sanity.
t.Log("Setting pool head to empty hash")
pool.head = common.Hash{}
}

if ii == len(gchain)/2 {
// Attempt to insert a nil header into the headerchain
t.Log("Inserting nil header into header chain")
if _, err := lightchain.InsertHeaderChain([]*types.Header{nil}, 1); err == nil {
t.Fatal("insert nil header error should not be errorless")
}
}
if ii == len(gchain)/4*3 {
var h *types.Header
t.Log("Setting pool head to a nil header", h.Hash().Hex())
pool.setNewHead(h)
}
if _, err := lightchain.InsertHeaderChain([]*types.Header{block.Header()}, 1); err != nil {
panic(err)
t.Fatal(err)
}

got := <-relay.mined
Expand Down