core: better side-chain importing
This commit is contained in:
		
				
					committed by
					
						 Péter Szilágyi
						Péter Szilágyi
					
				
			
			
				
	
			
			
			
						parent
						
							3d997b6dec
						
					
				
				
					commit
					493903eede
				
			| @@ -1048,6 +1048,80 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) { | |||||||
| 	return n, err | 	return n, err | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // addFutureBlock checks if the block is within the max allowed window to get accepted for future processing, and | ||||||
|  | // returns an error if the block is too far ahead and was not added. | ||||||
|  | func (bc *BlockChain) addFutureBlock(block *types.Block) error { | ||||||
|  | 	max := big.NewInt(time.Now().Unix() + maxTimeFutureBlocks) | ||||||
|  | 	if block.Time().Cmp(max) > 0 { | ||||||
|  | 		return fmt.Errorf("future block: %v > %v", block.Time(), max) | ||||||
|  | 	} | ||||||
|  | 	bc.futureBlocks.Add(block.Hash(), block) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // importBatch is a helper function to assist during chain import | ||||||
|  | type importBatch struct { | ||||||
|  | 	chain     types.Blocks | ||||||
|  | 	results   <-chan error | ||||||
|  | 	index     int | ||||||
|  | 	validator Validator | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // newBatch creates a new batch based on the given blocks, which are assumed to be a contiguous chain | ||||||
|  | func newBatch(chain types.Blocks, results <-chan error, validator Validator) *importBatch { | ||||||
|  | 	return &importBatch{ | ||||||
|  | 		chain:     chain, | ||||||
|  | 		results:   results, | ||||||
|  | 		index:     -1, | ||||||
|  | 		validator: validator, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // next returns the next block in the batch, along with any potential validation error for that block | ||||||
|  | // When the end is reached, it will return (nil, nil), but Current() will always return the last element. | ||||||
|  | func (batch *importBatch) next() (*types.Block, error) { | ||||||
|  | 	if batch.index+1 >= len(batch.chain) { | ||||||
|  | 		return nil, nil | ||||||
|  | 	} | ||||||
|  | 	batch.index++ | ||||||
|  | 	if err := <-batch.results; err != nil { | ||||||
|  | 		return batch.chain[batch.index], err | ||||||
|  | 	} | ||||||
|  | 	return batch.chain[batch.index], batch.validator.ValidateBody(batch.chain[batch.index]) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // current returns the current block that's being processed. Even after the next() has progressed the entire | ||||||
|  | // chain, current will always return the last element | ||||||
|  | func (batch *importBatch) current() *types.Block { | ||||||
|  | 	if batch.index < 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return batch.chain[batch.index] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // previous returns the previous block was being processed, or nil | ||||||
|  | func (batch *importBatch) previous() *types.Block { | ||||||
|  | 	if batch.index < 1 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return batch.chain[batch.index-1] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // first returns the first block in the batch | ||||||
|  | func (batch *importBatch) first() *types.Block { | ||||||
|  | 	return batch.chain[0] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // remaining returns the number of remaining blocks | ||||||
|  | func (batch *importBatch) remaining() int { | ||||||
|  | 	return len(batch.chain) - batch.index | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // processed returns the number of processed blocks | ||||||
|  | func (batch *importBatch) processed() int { | ||||||
|  | 	return batch.index + 1 | ||||||
|  | } | ||||||
|  |  | ||||||
| // insertChain will execute the actual chain insertion and event aggregation. The | // insertChain will execute the actual chain insertion and event aggregation. The | ||||||
| // only reason this method exists as a separate one is to make locking cleaner | // only reason this method exists as a separate one is to make locking cleaner | ||||||
| // with deferred statements. | // with deferred statements. | ||||||
| @@ -1067,12 +1141,27 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty | |||||||
| 				chain[i-1].Hash().Bytes()[:4], i, chain[i].NumberU64(), chain[i].Hash().Bytes()[:4], chain[i].ParentHash().Bytes()[:4]) | 				chain[i-1].Hash().Bytes()[:4], i, chain[i].NumberU64(), chain[i].Hash().Bytes()[:4], chain[i].ParentHash().Bytes()[:4]) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	log.Info("insertChain", "from", chain[0].NumberU64(), "to", chain[len(chain)-1].NumberU64()) | ||||||
|  |  | ||||||
| 	// Pre-checks passed, start the full block imports | 	// Pre-checks passed, start the full block imports | ||||||
| 	bc.wg.Add(1) | 	bc.wg.Add(1) | ||||||
| 	defer bc.wg.Done() | 	defer bc.wg.Done() | ||||||
|  |  | ||||||
| 	bc.chainmu.Lock() | 	bc.chainmu.Lock() | ||||||
| 	defer bc.chainmu.Unlock() | 	defer bc.chainmu.Unlock() | ||||||
|  | 	return bc.insertChainInternal(chain, true) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | //insertChainInternal is the internal implementation of insertChain, which assumes that | ||||||
|  | // 1. chains are contiguous, and | ||||||
|  | // 2. The `chainMu` lock is held | ||||||
|  | // This method is split out so that import batches that require re-injecting historical blocks can do | ||||||
|  | // so without releasing the lock, which could lead to racey behaviour. If a sidechain import is in progress, | ||||||
|  | // and the historic state is imported, but then new canon-head is added before the actual sidechain completes, | ||||||
|  | // then the historic state could be pruned again | ||||||
|  | func (bc *BlockChain) insertChainInternal(chain types.Blocks, verifySeals bool) (int, []interface{}, []*types.Log, error) { | ||||||
|  |  | ||||||
|  | 	// Start a parallel signature recovery (signer will fluke on fork transition, minimal perf loss) | ||||||
|  | 	senderCacher.recoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number()), chain) | ||||||
|  |  | ||||||
| 	// A queued approach to delivering events. This is generally | 	// A queued approach to delivering events. This is generally | ||||||
| 	// faster than direct delivery and requires much less mutex | 	// faster than direct delivery and requires much less mutex | ||||||
| @@ -1082,6 +1171,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty | |||||||
| 		events        = make([]interface{}, 0, len(chain)) | 		events        = make([]interface{}, 0, len(chain)) | ||||||
| 		lastCanon     *types.Block | 		lastCanon     *types.Block | ||||||
| 		coalescedLogs []*types.Log | 		coalescedLogs []*types.Log | ||||||
|  | 		block         *types.Block | ||||||
|  | 		err           error | ||||||
| 	) | 	) | ||||||
| 	// Start the parallel header verifier | 	// Start the parallel header verifier | ||||||
| 	headers := make([]*types.Header, len(chain)) | 	headers := make([]*types.Header, len(chain)) | ||||||
| @@ -1089,16 +1180,57 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty | |||||||
|  |  | ||||||
| 	for i, block := range chain { | 	for i, block := range chain { | ||||||
| 		headers[i] = block.Header() | 		headers[i] = block.Header() | ||||||
| 		seals[i] = true | 		seals[i] = verifySeals | ||||||
| 	} | 	} | ||||||
| 	abort, results := bc.engine.VerifyHeaders(bc, headers, seals) | 	abort, results := bc.engine.VerifyHeaders(bc, headers, seals) | ||||||
| 	defer close(abort) | 	defer close(abort) | ||||||
|  |  | ||||||
| 	// Start a parallel signature recovery (signer will fluke on fork transition, minimal perf loss) | 	// Peek the error for the first block | ||||||
| 	senderCacher.recoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number()), chain) | 	batch := newBatch(chain, results, bc.Validator()) | ||||||
|  | 	if block, err = batch.next(); err != nil { | ||||||
|  | 		if err == consensus.ErrPrunedAncestor { | ||||||
|  | 			return bc.insertSidechainInternal(batch, err) | ||||||
|  | 		} else if err == consensus.ErrFutureBlock || | ||||||
|  | 			(err == consensus.ErrUnknownAncestor && bc.futureBlocks.Contains(batch.first().ParentHash())) { | ||||||
|  |  | ||||||
| 	// Iterate over the blocks and insert when the verifier permits | 			// The first block is a future block | ||||||
| 	for i, block := range chain { | 			// We can shove that one and any child blocks (that fail because of UnknownAncestor) into the future-queue | ||||||
|  | 			for block != nil && (batch.index == 0 || err == consensus.ErrUnknownAncestor) { | ||||||
|  | 				block := batch.current() | ||||||
|  | 				if futureError := bc.addFutureBlock(block); futureError != nil { | ||||||
|  | 					return batch.index, events, coalescedLogs, futureError | ||||||
|  | 				} | ||||||
|  | 				block, err = batch.next() | ||||||
|  | 			} | ||||||
|  | 			stats.queued += batch.processed() | ||||||
|  | 			stats.ignored += batch.remaining() | ||||||
|  |  | ||||||
|  | 			// If there are any still remaining, mark as ignored | ||||||
|  | 			return batch.index, events, coalescedLogs, err | ||||||
|  | 		} else if err == ErrKnownBlock { | ||||||
|  |  | ||||||
|  | 			// Block and state both already known -- there can be two explanations. | ||||||
|  | 			// 1. We did a roll-back, and should now do a re-import | ||||||
|  | 			// 2. The block is stored as a sidechain, and is lying about it's stateroot, and passes a stateroot | ||||||
|  | 			// 	    from the canonical chain, which has not been verified. | ||||||
|  |  | ||||||
|  | 			// Skip all known blocks that are blocks behind us | ||||||
|  | 			currentNum := bc.CurrentBlock().NumberU64() | ||||||
|  | 			for block != nil && err == ErrKnownBlock && currentNum >= block.NumberU64() { | ||||||
|  | 				// We ignore these | ||||||
|  | 				stats.ignored++ | ||||||
|  | 				block, err = batch.next() | ||||||
|  | 			} | ||||||
|  | 			// Falls through to the block import | ||||||
|  | 		} else { | ||||||
|  | 			// Some other error | ||||||
|  | 			stats.ignored += len(batch.chain) | ||||||
|  | 			bc.reportBlock(block, nil, err) | ||||||
|  | 			return batch.index, events, coalescedLogs, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	// No validation errors | ||||||
|  | 	for ; block != nil && err == nil; block, err = batch.next() { | ||||||
| 		// If the chain is terminating, stop processing blocks | 		// If the chain is terminating, stop processing blocks | ||||||
| 		if atomic.LoadInt32(&bc.procInterrupt) == 1 { | 		if atomic.LoadInt32(&bc.procInterrupt) == 1 { | ||||||
| 			log.Debug("Premature abort during blocks processing") | 			log.Debug("Premature abort during blocks processing") | ||||||
| @@ -1107,115 +1239,45 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty | |||||||
| 		// If the header is a banned one, straight out abort | 		// If the header is a banned one, straight out abort | ||||||
| 		if BadHashes[block.Hash()] { | 		if BadHashes[block.Hash()] { | ||||||
| 			bc.reportBlock(block, nil, ErrBlacklistedHash) | 			bc.reportBlock(block, nil, ErrBlacklistedHash) | ||||||
| 			return i, events, coalescedLogs, ErrBlacklistedHash | 			return batch.index, events, coalescedLogs, ErrBlacklistedHash | ||||||
| 		} | 		} | ||||||
| 		// Wait for the block's verification to complete |  | ||||||
| 		bstart := time.Now() | 		bstart := time.Now() | ||||||
|  |  | ||||||
| 		err := <-results |  | ||||||
| 		if err == nil { |  | ||||||
| 			err = bc.Validator().ValidateBody(block) |  | ||||||
| 		} |  | ||||||
| 		switch { |  | ||||||
| 		case err == ErrKnownBlock: |  | ||||||
| 			// Block and state both already known. However if the current block is below |  | ||||||
| 			// this number we did a rollback and we should reimport it nonetheless. |  | ||||||
| 			if bc.CurrentBlock().NumberU64() >= block.NumberU64() { |  | ||||||
| 				stats.ignored++ |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 		case err == consensus.ErrFutureBlock: |  | ||||||
| 			// Allow up to MaxFuture second in the future blocks. If this limit is exceeded |  | ||||||
| 			// the chain is discarded and processed at a later time if given. |  | ||||||
| 			max := big.NewInt(time.Now().Unix() + maxTimeFutureBlocks) |  | ||||||
| 			if block.Time().Cmp(max) > 0 { |  | ||||||
| 				return i, events, coalescedLogs, fmt.Errorf("future block: %v > %v", block.Time(), max) |  | ||||||
| 			} |  | ||||||
| 			bc.futureBlocks.Add(block.Hash(), block) |  | ||||||
| 			stats.queued++ |  | ||||||
| 			continue |  | ||||||
|  |  | ||||||
| 		case err == consensus.ErrUnknownAncestor && bc.futureBlocks.Contains(block.ParentHash()): |  | ||||||
| 			bc.futureBlocks.Add(block.Hash(), block) |  | ||||||
| 			stats.queued++ |  | ||||||
| 			continue |  | ||||||
|  |  | ||||||
| 		case err == consensus.ErrPrunedAncestor: |  | ||||||
| 			// Block competing with the canonical chain, store in the db, but don't process |  | ||||||
| 			// until the competitor TD goes above the canonical TD |  | ||||||
| 			currentBlock := bc.CurrentBlock() |  | ||||||
| 			localTd := bc.GetTd(currentBlock.Hash(), currentBlock.NumberU64()) |  | ||||||
| 			externTd := new(big.Int).Add(bc.GetTd(block.ParentHash(), block.NumberU64()-1), block.Difficulty()) |  | ||||||
| 			if localTd.Cmp(externTd) > 0 { |  | ||||||
| 				if err = bc.WriteBlockWithoutState(block, externTd); err != nil { |  | ||||||
| 					return i, events, coalescedLogs, err |  | ||||||
| 				} |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			// Competitor chain beat canonical, gather all blocks from the common ancestor |  | ||||||
| 			var winner []*types.Block |  | ||||||
|  |  | ||||||
| 			parent := bc.GetBlock(block.ParentHash(), block.NumberU64()-1) |  | ||||||
| 			for !bc.HasState(parent.Root()) { |  | ||||||
| 				winner = append(winner, parent) |  | ||||||
| 				parent = bc.GetBlock(parent.ParentHash(), parent.NumberU64()-1) |  | ||||||
| 			} |  | ||||||
| 			for j := 0; j < len(winner)/2; j++ { |  | ||||||
| 				winner[j], winner[len(winner)-1-j] = winner[len(winner)-1-j], winner[j] |  | ||||||
| 			} |  | ||||||
| 			// Import all the pruned blocks to make the state available |  | ||||||
| 			bc.chainmu.Unlock() |  | ||||||
| 			_, evs, logs, err := bc.insertChain(winner) |  | ||||||
| 			bc.chainmu.Lock() |  | ||||||
| 			events, coalescedLogs = evs, logs |  | ||||||
|  |  | ||||||
| 			if err != nil { |  | ||||||
| 				return i, events, coalescedLogs, err |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 		case err != nil: |  | ||||||
| 			bc.reportBlock(block, nil, err) |  | ||||||
| 			return i, events, coalescedLogs, err |  | ||||||
| 		} |  | ||||||
| 		// Create a new statedb using the parent block and report an |  | ||||||
| 		// error if it fails. |  | ||||||
| 		var parent *types.Block | 		var parent *types.Block | ||||||
| 		if i == 0 { | 		parent = batch.previous() | ||||||
|  | 		if parent == nil { | ||||||
| 			parent = bc.GetBlock(block.ParentHash(), block.NumberU64()-1) | 			parent = bc.GetBlock(block.ParentHash(), block.NumberU64()-1) | ||||||
| 		} else { |  | ||||||
| 			parent = chain[i-1] |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		state, err := state.New(parent.Root(), bc.stateCache) | 		state, err := state.New(parent.Root(), bc.stateCache) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return i, events, coalescedLogs, err | 			return batch.index, events, coalescedLogs, err | ||||||
| 		} | 		} | ||||||
| 		// Process block using the parent state as reference point. | 		// Process block using the parent state as reference point. | ||||||
| 		receipts, logs, usedGas, err := bc.processor.Process(block, state, bc.vmConfig) | 		receipts, logs, usedGas, err := bc.processor.Process(block, state, bc.vmConfig) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			bc.reportBlock(block, receipts, err) | 			bc.reportBlock(block, receipts, err) | ||||||
| 			return i, events, coalescedLogs, err | 			return batch.index, events, coalescedLogs, err | ||||||
| 		} | 		} | ||||||
| 		// Validate the state using the default validator | 		// Validate the state using the default validator | ||||||
| 		err = bc.Validator().ValidateState(block, parent, state, receipts, usedGas) | 		if err := bc.Validator().ValidateState(block, parent, state, receipts, usedGas); err != nil { | ||||||
| 		if err != nil { |  | ||||||
| 			bc.reportBlock(block, receipts, err) | 			bc.reportBlock(block, receipts, err) | ||||||
| 			return i, events, coalescedLogs, err | 			return batch.index, events, coalescedLogs, err | ||||||
| 		} | 		} | ||||||
| 		proctime := time.Since(bstart) | 		proctime := time.Since(bstart) | ||||||
|  |  | ||||||
| 		// Write the block to the chain and get the status. | 		// Write the block to the chain and get the status. | ||||||
| 		status, err := bc.WriteBlockWithState(block, receipts, state) | 		status, err := bc.WriteBlockWithState(block, receipts, state) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return i, events, coalescedLogs, err | 			return batch.index, events, coalescedLogs, err | ||||||
| 		} | 		} | ||||||
| 		switch status { | 		switch status { | ||||||
| 		case CanonStatTy: | 		case CanonStatTy: | ||||||
| 			log.Debug("Inserted new block", "number", block.Number(), "hash", block.Hash(), "uncles", len(block.Uncles()), | 			log.Debug("Inserted new block", "number", block.Number(), "hash", block.Hash(), | ||||||
| 				"txs", len(block.Transactions()), "gas", block.GasUsed(), "elapsed", common.PrettyDuration(time.Since(bstart))) | 				"uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(), | ||||||
|  | 				"elapsed", common.PrettyDuration(time.Since(bstart)), | ||||||
|  | 				"root", block.Root().String()) | ||||||
|  |  | ||||||
| 			coalescedLogs = append(coalescedLogs, logs...) | 			coalescedLogs = append(coalescedLogs, logs...) | ||||||
| 			blockInsertTimer.UpdateSince(bstart) |  | ||||||
| 			events = append(events, ChainEvent{block, block.Hash(), logs}) | 			events = append(events, ChainEvent{block, block.Hash(), logs}) | ||||||
| 			lastCanon = block | 			lastCanon = block | ||||||
|  |  | ||||||
| @@ -1223,23 +1285,138 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty | |||||||
| 			bc.gcproc += proctime | 			bc.gcproc += proctime | ||||||
|  |  | ||||||
| 		case SideStatTy: | 		case SideStatTy: | ||||||
| 			log.Debug("Inserted forked block", "number", block.Number(), "hash", block.Hash(), "diff", block.Difficulty(), "elapsed", | 			log.Debug("Inserted forked block", "number", block.Number(), "hash", block.Hash(), | ||||||
| 				common.PrettyDuration(time.Since(bstart)), "txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles())) | 				"diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(bstart)), | ||||||
|  | 				"txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()), | ||||||
| 			blockInsertTimer.UpdateSince(bstart) | 				"root", block.Root().String()) | ||||||
| 			events = append(events, ChainSideEvent{block}) | 			events = append(events, ChainSideEvent{block}) | ||||||
| 		} | 		} | ||||||
|  | 		blockInsertTimer.UpdateSince(bstart) | ||||||
| 		stats.processed++ | 		stats.processed++ | ||||||
| 		stats.usedGas += usedGas | 		stats.usedGas += usedGas | ||||||
|  |  | ||||||
| 		cache, _ := bc.stateCache.TrieDB().Size() | 		cache, _ := bc.stateCache.TrieDB().Size() | ||||||
| 		stats.report(chain, i, cache) | 		stats.report(chain, batch.index, cache) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Any blocks remaining here? If so, the only ones we need to care about are | ||||||
|  | 	// shoving future blocks into queue | ||||||
|  | 	if block != nil && err == consensus.ErrFutureBlock { | ||||||
|  | 		if futureErr := bc.addFutureBlock(block); futureErr != nil { | ||||||
|  | 			return batch.index, events, coalescedLogs, futureErr | ||||||
|  | 		} | ||||||
|  | 		for block, err = batch.next(); block != nil && err == consensus.ErrUnknownAncestor; { | ||||||
|  | 			if futureErr := bc.addFutureBlock(block); futureErr != nil { | ||||||
|  | 				return batch.index, events, coalescedLogs, futureErr | ||||||
|  | 			} | ||||||
|  | 			stats.queued++ | ||||||
|  | 			block, err = batch.next() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	stats.ignored += batch.remaining() | ||||||
| 	// Append a single chain head event if we've progressed the chain | 	// Append a single chain head event if we've progressed the chain | ||||||
| 	if lastCanon != nil && bc.CurrentBlock().Hash() == lastCanon.Hash() { | 	if lastCanon != nil && bc.CurrentBlock().Hash() == lastCanon.Hash() { | ||||||
| 		events = append(events, ChainHeadEvent{lastCanon}) | 		events = append(events, ChainHeadEvent{lastCanon}) | ||||||
| 	} | 	} | ||||||
| 	return 0, events, coalescedLogs, nil | 	return 0, events, coalescedLogs, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // insertSidechainInternal should be called when an import batch hits upon a pruned ancestor error, which happens when | ||||||
|  | // an sidechain with a sufficiently old fork-block is found. It writes all (header-and-body-valid) blocks to disk, then | ||||||
|  | // tries to switch over to the new chain if the TD exceeded the current chain. | ||||||
|  | // It assumes that relevant locks are held already (hence 'Internal') | ||||||
|  | func (bc *BlockChain) insertSidechainInternal(batch *importBatch, err error) (int, []interface{}, []*types.Log, error) { | ||||||
|  | 	// If we're given a chain of blocks, and the first one is pruned, that means we're getting a | ||||||
|  | 	// sidechain imported. On the sidechain, we validate headers, but do not validate body and state | ||||||
|  | 	// (and actually import them) until the sidechain reaches a higher TD. | ||||||
|  | 	// Until then, we store them in the database (assuming that the header PoW check works out) | ||||||
|  | 	var ( | ||||||
|  | 		externTd        *big.Int | ||||||
|  | 		canonHeadNumber = bc.CurrentBlock().NumberU64() | ||||||
|  | 		events          = make([]interface{}, 0) | ||||||
|  | 		coalescedLogs   []*types.Log | ||||||
|  | 	) | ||||||
|  | 	// The first sidechain block error is already verified to be ErrPrunedAncestor. Since we don't import | ||||||
|  | 	// them here, we expect ErrUnknownAncestor for the remaining ones. Any other errors means that | ||||||
|  | 	// the block is invalid, and should not be written to disk. | ||||||
|  | 	block := batch.current() | ||||||
|  | 	for block != nil && (err == consensus.ErrPrunedAncestor) { | ||||||
|  | 		// Check the canonical stateroot for that number | ||||||
|  | 		if remoteNum := block.NumberU64(); canonHeadNumber >= remoteNum { | ||||||
|  | 			canonBlock := bc.GetBlockByNumber(remoteNum) | ||||||
|  | 			if canonBlock != nil && canonBlock.Root() == block.Root() { | ||||||
|  | 				// This is most likely a shadow-state attack. | ||||||
|  | 				// When a fork is imported into the database, and it eventually reaches a block height which is | ||||||
|  | 				// not pruned, we just found that the state already exist! This means that the sidechain block | ||||||
|  | 				// refers to a state which already exists in our canon chain. | ||||||
|  | 				// If left unchecked, we would now proceed importing the blocks, without actually having verified | ||||||
|  | 				// the state of the previous blocks. | ||||||
|  | 				log.Warn("Sidechain ghost-state attack detected", "blocknum", block.NumberU64(), | ||||||
|  | 					"sidechain root", block.Root(), "canon root", canonBlock.Root()) | ||||||
|  | 				// If someone legitimately side-mines blocks, they would still be imported as usual. However, | ||||||
|  | 				// we cannot risk writing unverified blocks to disk when they obviously target the pruning | ||||||
|  | 				// mechanism. | ||||||
|  | 				return batch.index, events, coalescedLogs, fmt.Errorf("sidechain ghost-state attack detected") | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if externTd == nil { | ||||||
|  | 			externTd = bc.GetTd(block.ParentHash(), block.NumberU64()-1) | ||||||
|  | 		} | ||||||
|  | 		externTd = new(big.Int).Add(externTd, block.Difficulty()) | ||||||
|  | 		if !bc.HasBlock(block.Hash(), block.NumberU64()) { | ||||||
|  | 			if err := bc.WriteBlockWithoutState(block, externTd); err != nil { | ||||||
|  | 				return batch.index, events, coalescedLogs, err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		block, err = batch.next() | ||||||
|  | 	} | ||||||
|  | 	// At this point, we've written all sidechain blocks to database. Loop ended either on some other error, | ||||||
|  | 	// or all were processed. If there was some other error, we can ignore the rest of those blocks. | ||||||
|  | 	// | ||||||
|  | 	// If the externTd was larger than our local TD, we now need to reimport the previous | ||||||
|  | 	// blocks to regenerate the required state | ||||||
|  | 	currentBlock := bc.CurrentBlock() | ||||||
|  | 	localTd := bc.GetTd(currentBlock.Hash(), currentBlock.NumberU64()) | ||||||
|  | 	// don't process until the competitor TD goes above the canonical TD | ||||||
|  | 	if localTd.Cmp(externTd) > 0 { | ||||||
|  | 		// If we have hit a sidechain, we may have to reimport pruned blocks | ||||||
|  | 		log.Info("Sidechain stored", "start", batch.first().NumberU64(), "end", batch.current().NumberU64(), "sidechain TD", externTd, "local TD", localTd) | ||||||
|  | 		return batch.index, events, coalescedLogs, err | ||||||
|  | 	} | ||||||
|  | 	// Competitor chain beat canonical. Before we reprocess to get the common ancestor, investigate if | ||||||
|  | 	// any blocks in the chain are 'known bad' blocks. | ||||||
|  | 	for index, b := range batch.chain { | ||||||
|  | 		if bc.badBlocks.Contains(b.Hash()) { | ||||||
|  | 			log.Info("Sidechain import aborted, bad block found", "index", index, "hash", b.Hash()) | ||||||
|  | 			return index, events, coalescedLogs, fmt.Errorf("known bad block %d 0x%x", b.NumberU64(), b.Hash()) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	// gather all blocks from the common ancestor | ||||||
|  | 	var parents []*types.Block | ||||||
|  | 	// Import all the pruned blocks to make the state available | ||||||
|  | 	parent := bc.GetBlock(batch.first().ParentHash(), batch.first().NumberU64()-1) | ||||||
|  | 	for !bc.HasState(parent.Root()) { | ||||||
|  | 		if bc.badBlocks.Contains(parent.Hash()) { | ||||||
|  | 			log.Info("Sidechain parent processing aborted, bad block found", "number", parent.NumberU64(), "hash", parent.Hash()) | ||||||
|  | 			return 0, events, coalescedLogs, fmt.Errorf("known bad block %d 0x%x", parent.NumberU64(), parent.Hash()) | ||||||
|  | 		} | ||||||
|  | 		parents = append(parents, parent) | ||||||
|  | 		parent = bc.GetBlock(parent.ParentHash(), parent.NumberU64()-1) | ||||||
|  | 	} | ||||||
|  | 	for j := 0; j < len(parents)/2; j++ { | ||||||
|  | 		parents[j], parents[len(parents)-1-j] = parents[len(parents)-1-j], parents[j] | ||||||
|  | 	} | ||||||
|  | 	// Import all the pruned blocks to make the state available | ||||||
|  | 	// During re-import, we can disable PoW-verification, since these are already verified | ||||||
|  | 	log.Info("Inserting parent blocks for reprocessing", "first", parents[0].NumberU64(), "count", len(parents), "last", parents[len(parents)-1].NumberU64) | ||||||
|  | 	_, evs, logs, err := bc.insertChainInternal(parents, false) | ||||||
|  | 	events, coalescedLogs = evs, logs | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, events, coalescedLogs, err | ||||||
|  | 	} | ||||||
|  | 	log.Info("Inserting sidechain blocks for processing") | ||||||
|  | 	errindex, events, coalescedLogs, err := bc.insertChainInternal(batch.chain[0:batch.index], false) | ||||||
|  | 	return errindex, events, coalescedLogs, err | ||||||
| } | } | ||||||
|  |  | ||||||
| // insertStats tracks and reports on block insertion. | // insertStats tracks and reports on block insertion. | ||||||
|   | |||||||
| @@ -287,7 +287,7 @@ func (n *Node) startInProc(apis []rpc.API) error { | |||||||
| 		if err := handler.RegisterName(api.Namespace, api.Service); err != nil { | 		if err := handler.RegisterName(api.Namespace, api.Service); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		n.log.Debug("InProc registered", "service", api.Service, "namespace", api.Namespace) | 		n.log.Debug("InProc registered", "namespace", api.Namespace) | ||||||
| 	} | 	} | ||||||
| 	n.inprocHandler = handler | 	n.inprocHandler = handler | ||||||
| 	return nil | 	return nil | ||||||
|   | |||||||
| @@ -434,7 +434,7 @@ func (tab *Table) loadSeedNodes() { | |||||||
| 	for i := range seeds { | 	for i := range seeds { | ||||||
| 		seed := seeds[i] | 		seed := seeds[i] | ||||||
| 		age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.LastPongReceived(seed.ID())) }} | 		age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.LastPongReceived(seed.ID())) }} | ||||||
| 		log.Debug("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age) | 		log.Trace("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age) | ||||||
| 		tab.add(seed) | 		tab.add(seed) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user