aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorPéter Szilágyi <peterke@gmail.com>2015-10-01 00:23:31 +0800
committerPéter Szilágyi <peterke@gmail.com>2015-10-19 15:03:09 +0800
commit832b37c8221e330896c36eb419d92af6b1fdc9dd (patch)
treefdeb701ed7a17ef2db176b7cbf1b1159b5afb417 /core
parent42c8afd44006b170c20159abaadc31cc7545bec2 (diff)
downloaddexon-832b37c8221e330896c36eb419d92af6b1fdc9dd.tar
dexon-832b37c8221e330896c36eb419d92af6b1fdc9dd.tar.gz
dexon-832b37c8221e330896c36eb419d92af6b1fdc9dd.tar.bz2
dexon-832b37c8221e330896c36eb419d92af6b1fdc9dd.tar.lz
dexon-832b37c8221e330896c36eb419d92af6b1fdc9dd.tar.xz
dexon-832b37c8221e330896c36eb419d92af6b1fdc9dd.tar.zst
dexon-832b37c8221e330896c36eb419d92af6b1fdc9dd.zip
core, eth: receipt chain reconstruction
Diffstat (limited to 'core')
-rw-r--r--core/bench_test.go2
-rw-r--r--core/block_processor_test.go16
-rw-r--r--core/blockchain.go210
-rw-r--r--core/blockchain_test.go162
-rw-r--r--core/chain_makers.go16
-rw-r--r--core/chain_makers_test.go2
-rw-r--r--core/chain_pow_test.go6
-rw-r--r--core/chain_util.go24
-rw-r--r--core/chain_util_test.go25
-rw-r--r--core/genesis.go2
-rw-r--r--core/transaction_util.go5
-rw-r--r--core/types/block.go5
-rw-r--r--core/types/receipt.go4
-rw-r--r--core/vm/log.go14
14 files changed, 406 insertions, 87 deletions
diff --git a/core/bench_test.go b/core/bench_test.go
index 27f3e3158..b5eb51803 100644
--- a/core/bench_test.go
+++ b/core/bench_test.go
@@ -163,7 +163,7 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) {
// Generate a chain of b.N blocks using the supplied block
// generator function.
genesis := WriteGenesisBlockForTesting(db, GenesisAccount{benchRootAddr, benchRootFunds})
- chain := GenerateChain(genesis, db, b.N, gen)
+ chain, _ := GenerateChain(genesis, db, b.N, gen)
// Time the insertion of the new chain.
// State and blocks are stored in the same DB.
diff --git a/core/block_processor_test.go b/core/block_processor_test.go
index c2c85ebfa..3050456b4 100644
--- a/core/block_processor_test.go
+++ b/core/block_processor_test.go
@@ -71,14 +71,14 @@ func TestPutReceipt(t *testing.T) {
receipt := new(types.Receipt)
receipt.Logs = vm.Logs{&vm.Log{
- Address: addr,
- Topics: []common.Hash{hash},
- Data: []byte("hi"),
- Number: 42,
- TxHash: hash,
- TxIndex: 0,
- BlockHash: hash,
- Index: 0,
+ Address: addr,
+ Topics: []common.Hash{hash},
+ Data: []byte("hi"),
+ BlockNumber: 42,
+ TxHash: hash,
+ TxIndex: 0,
+ BlockHash: hash,
+ Index: 0,
}}
PutReceipts(db, types.Receipts{receipt})
diff --git a/core/blockchain.go b/core/blockchain.go
index 6b42ea97e..b68e7d3ae 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/logger"
@@ -67,9 +68,10 @@ type BlockChain struct {
chainmu sync.RWMutex
tsmu sync.RWMutex
- checkpoint int // checkpoint counts towards the new checkpoint
- currentHeader *types.Header // Current head of the header chain (may be above the block chain!)
- currentBlock *types.Block // Current head of the block chain
+ checkpoint int // checkpoint counts towards the new checkpoint
+ currentHeader *types.Header // Current head of the header chain (may be above the block chain!)
+ currentBlock *types.Block // Current head of the block chain
+ currentFastBlock *types.Block // Current head of the fast-sync chain (may be above the block chain!)
headerCache *lru.Cache // Cache for the most recent block headers
bodyCache *lru.Cache // Cache for the most recent block bodies
@@ -160,12 +162,21 @@ func (self *BlockChain) loadLastState() error {
self.currentHeader = header
}
}
+ // Restore the last known head fast block
+ self.currentFastBlock = self.currentBlock
+ if head := GetHeadFastBlockHash(self.chainDb); head != (common.Hash{}) {
+ if block := self.GetBlock(head); block != nil {
+ self.currentFastBlock = block
+ }
+ }
// Issue a status log and return
headerTd := self.GetTd(self.currentHeader.Hash())
blockTd := self.GetTd(self.currentBlock.Hash())
+ fastTd := self.GetTd(self.currentFastBlock.Hash())
- glog.V(logger.Info).Infof("Last header: #%d [%x…] TD=%v", self.currentHeader.Number, self.currentHeader.Hash(), headerTd)
- glog.V(logger.Info).Infof("Last block: #%d [%x…] TD=%v", self.currentBlock.Number(), self.currentBlock.Hash(), blockTd)
+ glog.V(logger.Info).Infof("Last header: #%d [%x…] TD=%v", self.currentHeader.Number, self.currentHeader.Hash().Bytes()[:4], headerTd)
+ glog.V(logger.Info).Infof("Fast block: #%d [%x…] TD=%v", self.currentFastBlock.Number(), self.currentFastBlock.Hash().Bytes()[:4], fastTd)
+ glog.V(logger.Info).Infof("Last block: #%d [%x…] TD=%v", self.currentBlock.Number(), self.currentBlock.Hash().Bytes()[:4], blockTd)
return nil
}
@@ -178,23 +189,48 @@ func (bc *BlockChain) SetHead(head uint64) {
bc.mu.Lock()
defer bc.mu.Unlock()
- // Delete everything from the current header head (is above block head)
- for i := bc.currentHeader.Number.Uint64(); i > head; i-- {
- if hash := GetCanonicalHash(bc.chainDb, i); hash != (common.Hash{}) {
- DeleteCanonicalHash(bc.chainDb, i)
- DeleteHeader(bc.chainDb, hash)
- DeleteBody(bc.chainDb, hash)
- DeleteTd(bc.chainDb, hash)
+ // Figure out the highest known canonical assignment
+ height := uint64(0)
+ if bc.currentHeader != nil {
+ if hh := bc.currentHeader.Number.Uint64(); hh > height {
+ height = hh
}
}
- bc.currentHeader = GetHeader(bc.chainDb, GetCanonicalHash(bc.chainDb, head))
-
- // Rewind the block chain until a whole block is found
- for bc.GetBlockByNumber(head) == nil {
- head--
+ if bc.currentBlock != nil {
+ if bh := bc.currentBlock.NumberU64(); bh > height {
+ height = bh
+ }
}
- bc.currentBlock = bc.GetBlockByNumber(head)
+ if bc.currentFastBlock != nil {
+ if fbh := bc.currentFastBlock.NumberU64(); fbh > height {
+ height = fbh
+ }
+ }
+ // Gather all the hashes that need deletion
+ drop := make(map[common.Hash]struct{})
+ for bc.currentHeader != nil && bc.currentHeader.Number.Uint64() > head {
+ drop[bc.currentHeader.Hash()] = struct{}{}
+ bc.currentHeader = bc.GetHeader(bc.currentHeader.ParentHash)
+ }
+ for bc.currentBlock != nil && bc.currentBlock.NumberU64() > head {
+ drop[bc.currentBlock.Hash()] = struct{}{}
+ bc.currentBlock = bc.GetBlock(bc.currentBlock.ParentHash())
+ }
+ for bc.currentFastBlock != nil && bc.currentFastBlock.NumberU64() > head {
+ drop[bc.currentFastBlock.Hash()] = struct{}{}
+ bc.currentFastBlock = bc.GetBlock(bc.currentFastBlock.ParentHash())
+ }
+ // Roll back the canonical chain numbering
+ for i := height; i > head; i-- {
+ DeleteCanonicalHash(bc.chainDb, i)
+ }
+ // Delete everything found by the above rewind
+ for hash, _ := range drop {
+ DeleteHeader(bc.chainDb, hash)
+ DeleteBody(bc.chainDb, hash)
+ DeleteTd(bc.chainDb, hash)
+ }
// Clear out any stale content from the caches
bc.headerCache.Purge()
bc.bodyCache.Purge()
@@ -203,6 +239,9 @@ func (bc *BlockChain) SetHead(head uint64) {
bc.futureBlocks.Purge()
// Update all computed fields to the new head
+ if bc.currentBlock == nil {
+ bc.currentBlock = bc.genesisBlock
+ }
bc.insert(bc.currentBlock)
bc.loadLastState()
}
@@ -222,8 +261,7 @@ func (self *BlockChain) LastBlockHash() common.Hash {
}
// CurrentHeader retrieves the current head header of the canonical chain. The
-// header is retrieved from the chain manager's internal cache, involving no
-// database operations.
+// header is retrieved from the chain manager's internal cache.
func (self *BlockChain) CurrentHeader() *types.Header {
self.mu.RLock()
defer self.mu.RUnlock()
@@ -232,8 +270,7 @@ func (self *BlockChain) CurrentHeader() *types.Header {
}
// CurrentBlock retrieves the current head block of the canonical chain. The
-// block is retrieved from the chain manager's internal cache, involving no
-// database operations.
+// block is retrieved from the chain manager's internal cache.
func (self *BlockChain) CurrentBlock() *types.Block {
self.mu.RLock()
defer self.mu.RUnlock()
@@ -241,6 +278,15 @@ func (self *BlockChain) CurrentBlock() *types.Block {
return self.currentBlock
}
+// CurrentFastBlock retrieves the current fast-sync head block of the canonical
+// chain. The block is retrieved from the chain manager's internal cache.
+func (self *BlockChain) CurrentFastBlock() *types.Block {
+ self.mu.RLock()
+ defer self.mu.RUnlock()
+
+ return self.currentFastBlock
+}
+
func (self *BlockChain) Status() (td *big.Int, currentBlock common.Hash, genesisBlock common.Hash) {
self.mu.RLock()
defer self.mu.RUnlock()
@@ -264,22 +310,12 @@ func (bc *BlockChain) Reset() {
// ResetWithGenesisBlock purges the entire blockchain, restoring it to the
// specified genesis state.
func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) {
+ // Dump the entire block chain and purge the caches
+ bc.SetHead(0)
+
bc.mu.Lock()
defer bc.mu.Unlock()
- // Dump the entire block chain and purge the caches
- for block := bc.currentBlock; block != nil; block = bc.GetBlock(block.ParentHash()) {
- DeleteBlock(bc.chainDb, block.Hash())
- }
- for header := bc.currentHeader; header != nil; header = bc.GetHeader(header.ParentHash) {
- DeleteBlock(bc.chainDb, header.Hash())
- }
- bc.headerCache.Purge()
- bc.bodyCache.Purge()
- bc.bodyRLPCache.Purge()
- bc.blockCache.Purge()
- bc.futureBlocks.Purge()
-
// Prepare the genesis block and reinitialize the chain
if err := WriteTd(bc.chainDb, genesis.Hash(), genesis.Difficulty()); err != nil {
glog.Fatalf("failed to write genesis block TD: %v", err)
@@ -291,6 +327,7 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) {
bc.insert(bc.genesisBlock)
bc.currentBlock = bc.genesisBlock
bc.currentHeader = bc.genesisBlock.Header()
+ bc.currentFastBlock = bc.genesisBlock
}
// Export writes the active chain to the given writer.
@@ -328,8 +365,8 @@ func (self *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error {
// insert injects a new head block into the current block chain. This method
// assumes that the block is indeed a true head. It will also reset the head
-// header to this very same block to prevent the headers from diverging on a
-// different header chain.
+// header and the head fast sync block to this very same block to prevent them
+// from diverging on a different header chain.
//
// Note, this function assumes that the `mu` mutex is held!
func (bc *BlockChain) insert(block *types.Block) {
@@ -343,9 +380,13 @@ func (bc *BlockChain) insert(block *types.Block) {
if err := WriteHeadHeaderHash(bc.chainDb, block.Hash()); err != nil {
glog.Fatalf("failed to insert head header hash: %v", err)
}
+ if err := WriteHeadFastBlockHash(bc.chainDb, block.Hash()); err != nil {
+ glog.Fatalf("failed to insert head fast block hash: %v", err)
+ }
// Update the internal state with the head block
bc.currentBlock = block
bc.currentHeader = block.Header()
+ bc.currentFastBlock = block
}
// Accessors
@@ -634,7 +675,7 @@ func (self *BlockChain) InsertHeaderChain(chain []*types.Header, verify bool) (i
for i, header := range chain {
// Short circuit insertion if shutting down
if atomic.LoadInt32(&self.procInterrupt) == 1 {
- glog.V(logger.Debug).Infoln("Premature abort during header chain processing")
+ glog.V(logger.Debug).Infoln("premature abort during header chain processing")
break
}
hash := header.Hash()
@@ -653,7 +694,7 @@ func (self *BlockChain) InsertHeaderChain(chain []*types.Header, verify bool) (i
}
}
if BadHashes[hash] {
- glog.V(logger.Error).Infof("Bad header %d [%x…], known bad hash", header.Number, hash)
+ glog.V(logger.Error).Infof("bad header %d [%x…], known bad hash", header.Number, hash)
return i, BadHashError(hash)
}
// Write the header to the chain and get the status
@@ -674,6 +715,95 @@ func (self *BlockChain) InsertHeaderChain(chain []*types.Header, verify bool) (i
return 0, nil
}
+// InsertReceiptChain attempts to complete an already existing header chain with
+// transaction and receipt data.
+func (self *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) {
+ self.wg.Add(1)
+ defer self.wg.Done()
+
+ // Make sure only one thread manipulates the chain at once
+ self.chainmu.Lock()
+ defer self.chainmu.Unlock()
+
+ // Collect some import statistics to report on
+ stats := struct{ processed, ignored int }{}
+ start := time.Now()
+
+ // Iterate over the blocks and receipts, inserting any new ones
+ for i := 0; i < len(blockChain) && i < len(receiptChain); i++ {
+ block, receipts := blockChain[i], receiptChain[i]
+
+ // Short circuit insertion if shutting down
+ if atomic.LoadInt32(&self.procInterrupt) == 1 {
+ glog.V(logger.Debug).Infoln("premature abort during receipt chain processing")
+ break
+ }
+ // Short circuit if the owner header is unknown
+ if !self.HasHeader(block.Hash()) {
+ glog.V(logger.Debug).Infof("containing header #%d [%x…] unknown", block.Number(), block.Hash().Bytes()[:4])
+ return i, fmt.Errorf("containing header #%d [%x…] unknown", block.Number(), block.Hash().Bytes()[:4])
+ }
+ // Skip if the entire data is already known
+ if self.HasBlock(block.Hash()) {
+ stats.ignored++
+ continue
+ }
+ // Compute all the non-consensus fields of the receipts
+ transactions, logIndex := block.Transactions(), uint(0)
+ for j := 0; j < len(receipts); j++ {
+ // The transaction hash can be retrieved from the transaction itself
+ receipts[j].TxHash = transactions[j].Hash()
+
+ // The contract address can be derived from the transaction itself
+ if MessageCreatesContract(transactions[j]) {
+ from, _ := transactions[j].From()
+ receipts[j].ContractAddress = crypto.CreateAddress(from, transactions[j].Nonce())
+ }
+ // The used gas can be calculated based on previous receipts
+ if j == 0 {
+ receipts[j].GasUsed = new(big.Int).Set(receipts[j].CumulativeGasUsed)
+ } else {
+ receipts[j].GasUsed = new(big.Int).Sub(receipts[j].CumulativeGasUsed, receipts[j-1].CumulativeGasUsed)
+ }
+ // The derived log fields can simply be set from the block and transaction
+ for k := 0; k < len(receipts[j].Logs); k++ {
+ receipts[j].Logs[k].BlockNumber = block.NumberU64()
+ receipts[j].Logs[k].BlockHash = block.Hash()
+ receipts[j].Logs[k].TxHash = receipts[j].TxHash
+ receipts[j].Logs[k].TxIndex = uint(j)
+ receipts[j].Logs[k].Index = logIndex
+ logIndex++
+ }
+ }
+ // Write all the data out into the database
+ if err := WriteBody(self.chainDb, block.Hash(), &types.Body{block.Transactions(), block.Uncles()}); err != nil {
+ glog.Fatalf("failed to write block body: %v", err)
+ return i, err
+ }
+ if err := PutBlockReceipts(self.chainDb, block.Hash(), receipts); err != nil {
+ glog.Fatalf("failed to write block receipts: %v", err)
+ return i, err
+ }
+ // Update the head fast sync block if better
+ self.mu.Lock()
+ if self.GetTd(self.currentFastBlock.Hash()).Cmp(self.GetTd(block.Hash())) < 0 {
+ if err := WriteHeadFastBlockHash(self.chainDb, block.Hash()); err != nil {
+ glog.Fatalf("failed to update head fast block hash: %v", err)
+ }
+ self.currentFastBlock = block
+ }
+ self.mu.Unlock()
+
+ stats.processed++
+ }
+ // Report some public statistics so the user has a clue what's going on
+ first, last := blockChain[0], blockChain[len(blockChain)-1]
+ glog.V(logger.Info).Infof("imported %d receipt(s) (%d ignored) in %v. #%d [%x… / %x…]", stats.processed, stats.ignored,
+ time.Since(start), last.Number(), first.Hash().Bytes()[:4], last.Hash().Bytes()[:4])
+
+ return 0, nil
+}
+
// WriteBlock writes the block to the chain.
func (self *BlockChain) WriteBlock(block *types.Block) (status writeStatus, err error) {
self.wg.Add(1)
@@ -799,7 +929,7 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
return i, err
}
- if err := PutBlockReceipts(self.chainDb, block, receipts); err != nil {
+ if err := PutBlockReceipts(self.chainDb, block.Hash(), receipts); err != nil {
glog.V(logger.Warn).Infoln("error writing block receipts:", err)
}
diff --git a/core/blockchain_test.go b/core/blockchain_test.go
index 4d0f13ef1..93c2128bc 100644
--- a/core/blockchain_test.go
+++ b/core/blockchain_test.go
@@ -430,9 +430,12 @@ func makeBlockChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.B
var chain []*types.Block
for i, difficulty := range d {
header := &types.Header{
- Coinbase: common.Address{seed},
- Number: big.NewInt(int64(i + 1)),
- Difficulty: big.NewInt(int64(difficulty)),
+ Coinbase: common.Address{seed},
+ Number: big.NewInt(int64(i + 1)),
+ Difficulty: big.NewInt(int64(difficulty)),
+ UncleHash: types.EmptyUncleHash,
+ TxHash: types.EmptyRootHash,
+ ReceiptHash: types.EmptyRootHash,
}
if i == 0 {
header.ParentHash = genesis.Hash()
@@ -668,6 +671,155 @@ func testInsertNonceError(t *testing.T, full bool) {
}
}
+// Tests that fast importing a block chain produces the same chain data as the
+// classical full block processing.
+func TestFastVsFullChains(t *testing.T) {
+ // Configure and generate a sample block chain
+ var (
+ gendb, _ = ethdb.NewMemDatabase()
+ key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+ address = crypto.PubkeyToAddress(key.PublicKey)
+ funds = big.NewInt(1000000000)
+ genesis = GenesisBlockForTesting(gendb, address, funds)
+ )
+ blocks, receipts := GenerateChain(genesis, gendb, 1024, func(i int, block *BlockGen) {
+ block.SetCoinbase(common.Address{0x00})
+
+ // If the block number is multiple of 3, send a few bonus transactions to the miner
+ if i%3 == 2 {
+ for j := 0; j < i%4+1; j++ {
+ tx, err := types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key)
+ if err != nil {
+ panic(err)
+ }
+ block.AddTx(tx)
+ }
+ }
+ // If the block number is a multiple of 5, add a few bonus uncles to the block
+ if i%5 == 5 {
+ block.AddUncle(&types.Header{ParentHash: block.PrevBlock(i - 1).Hash(), Number: big.NewInt(int64(i - 1))})
+ }
+ })
+ // Import the chain as an archive node for the comparison baseline
+ archiveDb, _ := ethdb.NewMemDatabase()
+ WriteGenesisBlockForTesting(archiveDb, GenesisAccount{address, funds})
+
+ archive, _ := NewBlockChain(archiveDb, FakePow{}, new(event.TypeMux))
+ archive.SetProcessor(NewBlockProcessor(archiveDb, FakePow{}, archive, new(event.TypeMux)))
+
+ if n, err := archive.InsertChain(blocks); err != nil {
+ t.Fatalf("failed to process block %d: %v", n, err)
+ }
+ // Fast import the chain as a non-archive node to test
+ fastDb, _ := ethdb.NewMemDatabase()
+ WriteGenesisBlockForTesting(fastDb, GenesisAccount{address, funds})
+ fast, _ := NewBlockChain(fastDb, FakePow{}, new(event.TypeMux))
+
+ headers := make([]*types.Header, len(blocks))
+ for i, block := range blocks {
+ headers[i] = block.Header()
+ }
+ if n, err := fast.InsertHeaderChain(headers, true); err != nil {
+ t.Fatalf("failed to insert header %d: %v", n, err)
+ }
+ if n, err := fast.InsertReceiptChain(blocks, receipts); err != nil {
+ t.Fatalf("failed to insert receipt %d: %v", n, err)
+ }
+ // Iterate over all chain data components, and cross reference
+ for i := 0; i < len(blocks); i++ {
+ num, hash := blocks[i].NumberU64(), blocks[i].Hash()
+
+ if ftd, atd := fast.GetTd(hash), archive.GetTd(hash); ftd.Cmp(atd) != 0 {
+ t.Errorf("block #%d [%x]: td mismatch: have %v, want %v", num, hash, ftd, atd)
+ }
+ if fheader, aheader := fast.GetHeader(hash), archive.GetHeader(hash); fheader.Hash() != aheader.Hash() {
+ t.Errorf("block #%d [%x]: header mismatch: have %v, want %v", num, hash, fheader, aheader)
+ }
+ if fblock, ablock := fast.GetBlock(hash), archive.GetBlock(hash); fblock.Hash() != ablock.Hash() {
+ t.Errorf("block #%d [%x]: block mismatch: have %v, want %v", num, hash, fblock, ablock)
+ } else if types.DeriveSha(fblock.Transactions()) != types.DeriveSha(ablock.Transactions()) {
+ t.Errorf("block #%d [%x]: transactions mismatch: have %v, want %v", num, hash, fblock.Transactions(), ablock.Transactions())
+ } else if types.CalcUncleHash(fblock.Uncles()) != types.CalcUncleHash(ablock.Uncles()) {
+ t.Errorf("block #%d [%x]: uncles mismatch: have %v, want %v", num, hash, fblock.Uncles(), ablock.Uncles())
+ }
+ if freceipts, areceipts := GetBlockReceipts(fastDb, hash), GetBlockReceipts(archiveDb, hash); types.DeriveSha(freceipts) != types.DeriveSha(areceipts) {
+ t.Errorf("block #%d [%x]: receipts mismatch: have %v, want %v", num, hash, freceipts, areceipts)
+ }
+ }
+ // Check that the canonical chains are the same between the databases
+ for i := 0; i < len(blocks)+1; i++ {
+ if fhash, ahash := GetCanonicalHash(fastDb, uint64(i)), GetCanonicalHash(archiveDb, uint64(i)); fhash != ahash {
+ t.Errorf("block #%d: canonical hash mismatch: have %v, want %v", i, fhash, ahash)
+ }
+ }
+}
+
+// Tests that various import methods move the chain head pointers to the correct
+// positions.
+func TestLightVsFastVsFullChainHeads(t *testing.T) {
+ // Configure and generate a sample block chain
+ var (
+ gendb, _ = ethdb.NewMemDatabase()
+ key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+ address = crypto.PubkeyToAddress(key.PublicKey)
+ funds = big.NewInt(1000000000)
+ genesis = GenesisBlockForTesting(gendb, address, funds)
+ )
+ height := uint64(1024)
+ blocks, receipts := GenerateChain(genesis, gendb, int(height), nil)
+
+ // Create a small assertion method to check the three heads
+ assert := func(t *testing.T, kind string, chain *BlockChain, header uint64, fast uint64, block uint64) {
+ if num := chain.CurrentBlock().NumberU64(); num != block {
+ t.Errorf("%s head block mismatch: have #%v, want #%v", kind, num, block)
+ }
+ if num := chain.CurrentFastBlock().NumberU64(); num != fast {
+ t.Errorf("%s head fast-block mismatch: have #%v, want #%v", kind, num, fast)
+ }
+ if num := chain.CurrentHeader().Number.Uint64(); num != header {
+ t.Errorf("%s head header mismatch: have #%v, want #%v", kind, num, header)
+ }
+ }
+ // Import the chain as an archive node and ensure all pointers are updated
+ archiveDb, _ := ethdb.NewMemDatabase()
+ WriteGenesisBlockForTesting(archiveDb, GenesisAccount{address, funds})
+
+ archive, _ := NewBlockChain(archiveDb, FakePow{}, new(event.TypeMux))
+ archive.SetProcessor(NewBlockProcessor(archiveDb, FakePow{}, archive, new(event.TypeMux)))
+
+ if n, err := archive.InsertChain(blocks); err != nil {
+ t.Fatalf("failed to process block %d: %v", n, err)
+ }
+ assert(t, "archive", archive, height, height, height)
+
+ // Import the chain as a non-archive node and ensure all pointers are updated
+ fastDb, _ := ethdb.NewMemDatabase()
+ WriteGenesisBlockForTesting(fastDb, GenesisAccount{address, funds})
+ fast, _ := NewBlockChain(fastDb, FakePow{}, new(event.TypeMux))
+
+ headers := make([]*types.Header, len(blocks))
+ for i, block := range blocks {
+ headers[i] = block.Header()
+ }
+ if n, err := fast.InsertHeaderChain(headers, true); err != nil {
+ t.Fatalf("failed to insert header %d: %v", n, err)
+ }
+ if n, err := fast.InsertReceiptChain(blocks, receipts); err != nil {
+ t.Fatalf("failed to insert receipt %d: %v", n, err)
+ }
+ assert(t, "fast", fast, height, height, 0)
+
+ // Import the chain as a light node and ensure all pointers are updated
+ lightDb, _ := ethdb.NewMemDatabase()
+ WriteGenesisBlockForTesting(lightDb, GenesisAccount{address, funds})
+ light, _ := NewBlockChain(lightDb, FakePow{}, new(event.TypeMux))
+
+ if n, err := light.InsertHeaderChain(headers, true); err != nil {
+ t.Fatalf("failed to insert header %d: %v", n, err)
+ }
+ assert(t, "light", light, height, 0, 0)
+}
+
// Tests that chain reorganizations handle transaction removals and reinsertions.
func TestChainTxReorgs(t *testing.T) {
params.MinGasLimit = big.NewInt(125000) // Minimum the gas limit may ever be.
@@ -704,7 +856,7 @@ func TestChainTxReorgs(t *testing.T) {
// - futureAdd: transaction added after the reorg has already finished
var pastAdd, freshAdd, futureAdd *types.Transaction
- chain := GenerateChain(genesis, db, 3, func(i int, gen *BlockGen) {
+ chain, _ := GenerateChain(genesis, db, 3, func(i int, gen *BlockGen) {
switch i {
case 0:
pastDrop, _ = types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key2)
@@ -730,7 +882,7 @@ func TestChainTxReorgs(t *testing.T) {
}
// overwrite the old chain
- chain = GenerateChain(genesis, db, 5, func(i int, gen *BlockGen) {
+ chain, _ = GenerateChain(genesis, db, 5, func(i int, gen *BlockGen) {
switch i {
case 0:
pastAdd, _ = types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key3)
diff --git a/core/chain_makers.go b/core/chain_makers.go
index be6ba04e4..31b2627af 100644
--- a/core/chain_makers.go
+++ b/core/chain_makers.go
@@ -164,13 +164,13 @@ func (b *BlockGen) OffsetTime(seconds int64) {
// Blocks created by GenerateChain do not contain valid proof of work
// values. Inserting them into BlockChain requires use of FakePow or
// a similar non-validating proof of work implementation.
-func GenerateChain(parent *types.Block, db ethdb.Database, n int, gen func(int, *BlockGen)) []*types.Block {
+func GenerateChain(parent *types.Block, db ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts) {
statedb, err := state.New(parent.Root(), db)
if err != nil {
panic(err)
}
- blocks := make(types.Blocks, n)
- genblock := func(i int, h *types.Header) *types.Block {
+ blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
+ genblock := func(i int, h *types.Header) (*types.Block, types.Receipts) {
b := &BlockGen{parent: parent, i: i, chain: blocks, header: h, statedb: statedb}
if gen != nil {
gen(i, b)
@@ -181,15 +181,16 @@ func GenerateChain(parent *types.Block, db ethdb.Database, n int, gen func(int,
panic(fmt.Sprintf("state write error: %v", err))
}
h.Root = root
- return types.NewBlock(h, b.txs, b.uncles, b.receipts)
+ return types.NewBlock(h, b.txs, b.uncles, b.receipts), b.receipts
}
for i := 0; i < n; i++ {
header := makeHeader(parent, statedb)
- block := genblock(i, header)
+ block, receipt := genblock(i, header)
blocks[i] = block
+ receipts[i] = receipt
parent = block
}
- return blocks
+ return blocks, receipts
}
func makeHeader(parent *types.Block, state *state.StateDB) *types.Header {
@@ -254,7 +255,8 @@ func makeHeaderChain(parent *types.Header, n int, db ethdb.Database, seed int) [
// makeBlockChain creates a deterministic chain of blocks rooted at parent.
func makeBlockChain(parent *types.Block, n int, db ethdb.Database, seed int) []*types.Block {
- return GenerateChain(parent, db, n, func(i int, b *BlockGen) {
+ blocks, _ := GenerateChain(parent, db, n, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)})
})
+ return blocks
}
diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go
index 63825c261..7f47cf288 100644
--- a/core/chain_makers_test.go
+++ b/core/chain_makers_test.go
@@ -47,7 +47,7 @@ func ExampleGenerateChain() {
// This call generates a chain of 5 blocks. The function runs for
// each block and adds different features to gen based on the
// block index.
- chain := GenerateChain(genesis, db, 5, func(i int, gen *BlockGen) {
+ chain, _ := GenerateChain(genesis, db, 5, func(i int, gen *BlockGen) {
switch i {
case 0:
// In block 1, addr1 sends addr2 some ether.
diff --git a/core/chain_pow_test.go b/core/chain_pow_test.go
index 5aa8ed8a0..d2b0bd144 100644
--- a/core/chain_pow_test.go
+++ b/core/chain_pow_test.go
@@ -60,7 +60,7 @@ func TestPowVerification(t *testing.T) {
var (
testdb, _ = ethdb.NewMemDatabase()
genesis = GenesisBlockForTesting(testdb, common.Address{}, new(big.Int))
- blocks = GenerateChain(genesis, testdb, 8, nil)
+ blocks, _ = GenerateChain(genesis, testdb, 8, nil)
)
headers := make([]*types.Header, len(blocks))
for i, block := range blocks {
@@ -115,7 +115,7 @@ func testPowConcurrentVerification(t *testing.T, threads int) {
var (
testdb, _ = ethdb.NewMemDatabase()
genesis = GenesisBlockForTesting(testdb, common.Address{}, new(big.Int))
- blocks = GenerateChain(genesis, testdb, 8, nil)
+ blocks, _ = GenerateChain(genesis, testdb, 8, nil)
)
headers := make([]*types.Header, len(blocks))
for i, block := range blocks {
@@ -186,7 +186,7 @@ func testPowConcurrentAbortion(t *testing.T, threads int) {
var (
testdb, _ = ethdb.NewMemDatabase()
genesis = GenesisBlockForTesting(testdb, common.Address{}, new(big.Int))
- blocks = GenerateChain(genesis, testdb, 1024, nil)
+ blocks, _ = GenerateChain(genesis, testdb, 1024, nil)
)
headers := make([]*types.Header, len(blocks))
for i, block := range blocks {
diff --git a/core/chain_util.go b/core/chain_util.go
index 42b6a5be2..907e6668c 100644
--- a/core/chain_util.go
+++ b/core/chain_util.go
@@ -34,6 +34,7 @@ import (
var (
headHeaderKey = []byte("LastHeader")
headBlockKey = []byte("LastBlock")
+ headFastKey = []byte("LastFast")
blockPrefix = []byte("block-")
blockNumPrefix = []byte("block-num-")
@@ -129,7 +130,7 @@ func GetCanonicalHash(db ethdb.Database, number uint64) common.Hash {
// header. The difference between this and GetHeadBlockHash is that whereas the
// last block hash is only updated upon a full block import, the last header
// hash is updated already at header import, allowing head tracking for the
-// fast synchronization mechanism.
+// light synchronization mechanism.
func GetHeadHeaderHash(db ethdb.Database) common.Hash {
data, _ := db.Get(headHeaderKey)
if len(data) == 0 {
@@ -147,6 +148,18 @@ func GetHeadBlockHash(db ethdb.Database) common.Hash {
return common.BytesToHash(data)
}
+// GetHeadFastBlockHash retrieves the hash of the current canonical head block during
+// fast synchronization. The difference between this and GetHeadBlockHash is that
+// whereas the last block hash is only updated upon a full block import, the last
+// fast hash is updated when importing pre-processed blocks.
+func GetHeadFastBlockHash(db ethdb.Database) common.Hash {
+ data, _ := db.Get(headFastKey)
+ if len(data) == 0 {
+ return common.Hash{}
+ }
+ return common.BytesToHash(data)
+}
+
// GetHeaderRLP retrieves a block header in its raw RLP database encoding, or nil
// if the header's not found.
func GetHeaderRLP(db ethdb.Database, hash common.Hash) rlp.RawValue {
@@ -249,6 +262,15 @@ func WriteHeadBlockHash(db ethdb.Database, hash common.Hash) error {
return nil
}
+// WriteHeadFastBlockHash stores the fast head block's hash.
+func WriteHeadFastBlockHash(db ethdb.Database, hash common.Hash) error {
+ if err := db.Put(headFastKey, hash.Bytes()); err != nil {
+ glog.Fatalf("failed to store last fast block's hash into database: %v", err)
+ return err
+ }
+ return nil
+}
+
// WriteHeader serializes a block header into the database.
func WriteHeader(db ethdb.Database, header *types.Header) error {
data, err := rlp.EncodeToBytes(header)
diff --git a/core/chain_util_test.go b/core/chain_util_test.go
index 62b73a064..bc5aa9776 100644
--- a/core/chain_util_test.go
+++ b/core/chain_util_test.go
@@ -163,7 +163,12 @@ func TestBlockStorage(t *testing.T) {
db, _ := ethdb.NewMemDatabase()
// Create a test block to move around the database and make sure it's really new
- block := types.NewBlockWithHeader(&types.Header{Extra: []byte("test block")})
+ block := types.NewBlockWithHeader(&types.Header{
+ Extra: []byte("test block"),
+ UncleHash: types.EmptyUncleHash,
+ TxHash: types.EmptyRootHash,
+ ReceiptHash: types.EmptyRootHash,
+ })
if entry := GetBlock(db, block.Hash()); entry != nil {
t.Fatalf("Non existent block returned: %v", entry)
}
@@ -208,8 +213,12 @@ func TestBlockStorage(t *testing.T) {
// Tests that partial block contents don't get reassembled into full blocks.
func TestPartialBlockStorage(t *testing.T) {
db, _ := ethdb.NewMemDatabase()
- block := types.NewBlockWithHeader(&types.Header{Extra: []byte("test block")})
-
+ block := types.NewBlockWithHeader(&types.Header{
+ Extra: []byte("test block"),
+ UncleHash: types.EmptyUncleHash,
+ TxHash: types.EmptyRootHash,
+ ReceiptHash: types.EmptyRootHash,
+ })
// Store a header and check that it's not recognized as a block
if err := WriteHeader(db, block.Header()); err != nil {
t.Fatalf("Failed to write header into database: %v", err)
@@ -298,6 +307,7 @@ func TestHeadStorage(t *testing.T) {
blockHead := types.NewBlockWithHeader(&types.Header{Extra: []byte("test block header")})
blockFull := types.NewBlockWithHeader(&types.Header{Extra: []byte("test block full")})
+ blockFast := types.NewBlockWithHeader(&types.Header{Extra: []byte("test block fast")})
// Check that no head entries are in a pristine database
if entry := GetHeadHeaderHash(db); entry != (common.Hash{}) {
@@ -306,6 +316,9 @@ func TestHeadStorage(t *testing.T) {
if entry := GetHeadBlockHash(db); entry != (common.Hash{}) {
t.Fatalf("Non head block entry returned: %v", entry)
}
+ if entry := GetHeadFastBlockHash(db); entry != (common.Hash{}) {
+ t.Fatalf("Non fast head block entry returned: %v", entry)
+ }
// Assign separate entries for the head header and block
if err := WriteHeadHeaderHash(db, blockHead.Hash()); err != nil {
t.Fatalf("Failed to write head header hash: %v", err)
@@ -313,6 +326,9 @@ func TestHeadStorage(t *testing.T) {
if err := WriteHeadBlockHash(db, blockFull.Hash()); err != nil {
t.Fatalf("Failed to write head block hash: %v", err)
}
+ if err := WriteHeadFastBlockHash(db, blockFast.Hash()); err != nil {
+ t.Fatalf("Failed to write fast head block hash: %v", err)
+ }
// Check that both heads are present, and different (i.e. two heads maintained)
if entry := GetHeadHeaderHash(db); entry != blockHead.Hash() {
t.Fatalf("Head header hash mismatch: have %v, want %v", entry, blockHead.Hash())
@@ -320,6 +336,9 @@ func TestHeadStorage(t *testing.T) {
if entry := GetHeadBlockHash(db); entry != blockFull.Hash() {
t.Fatalf("Head block hash mismatch: have %v, want %v", entry, blockFull.Hash())
}
+ if entry := GetHeadFastBlockHash(db); entry != blockFast.Hash() {
+ t.Fatalf("Fast head block hash mismatch: have %v, want %v", entry, blockFast.Hash())
+ }
}
func TestMipmapBloom(t *testing.T) {
diff --git a/core/genesis.go b/core/genesis.go
index 16c1598c2..dac5de92f 100644
--- a/core/genesis.go
+++ b/core/genesis.go
@@ -103,7 +103,7 @@ func WriteGenesisBlock(chainDb ethdb.Database, reader io.Reader) (*types.Block,
if err := WriteBlock(chainDb, block); err != nil {
return nil, err
}
- if err := PutBlockReceipts(chainDb, block, nil); err != nil {
+ if err := PutBlockReceipts(chainDb, block.Hash(), nil); err != nil {
return nil, err
}
if err := WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()); err != nil {
diff --git a/core/transaction_util.go b/core/transaction_util.go
index dbda4cfe7..1a3681341 100644
--- a/core/transaction_util.go
+++ b/core/transaction_util.go
@@ -155,7 +155,7 @@ func GetBlockReceipts(db ethdb.Database, hash common.Hash) types.Receipts {
// PutBlockReceipts stores the block's transactions associated receipts
// and stores them by block hash in a single slice. This is required for
// forks and chain reorgs
-func PutBlockReceipts(db ethdb.Database, block *types.Block, receipts types.Receipts) error {
+func PutBlockReceipts(db ethdb.Database, hash common.Hash, receipts types.Receipts) error {
rs := make([]*types.ReceiptForStorage, len(receipts))
for i, receipt := range receipts {
rs[i] = (*types.ReceiptForStorage)(receipt)
@@ -164,12 +164,9 @@ func PutBlockReceipts(db ethdb.Database, block *types.Block, receipts types.Rece
if err != nil {
return err
}
-
- hash := block.Hash()
err = db.Put(append(blockReceiptsPre, hash[:]...), bytes)
if err != nil {
return err
}
-
return nil
}
diff --git a/core/types/block.go b/core/types/block.go
index c4377ffa1..1d1cfa515 100644
--- a/core/types/block.go
+++ b/core/types/block.go
@@ -128,7 +128,6 @@ type Block struct {
header *Header
uncles []*Header
transactions Transactions
- receipts Receipts
// caches
hash atomic.Value
@@ -200,8 +199,6 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*
} else {
b.header.ReceiptHash = DeriveSha(Receipts(receipts))
b.header.Bloom = CreateBloom(receipts)
- b.receipts = make([]*Receipt, len(receipts))
- copy(b.receipts, receipts)
}
if len(uncles) == 0 {
@@ -299,7 +296,6 @@ func (b *StorageBlock) DecodeRLP(s *rlp.Stream) error {
// TODO: copies
func (b *Block) Uncles() []*Header { return b.uncles }
func (b *Block) Transactions() Transactions { return b.transactions }
-func (b *Block) Receipts() Receipts { return b.receipts }
func (b *Block) Transaction(hash common.Hash) *Transaction {
for _, transaction := range b.transactions {
@@ -364,7 +360,6 @@ func (b *Block) WithMiningResult(nonce uint64, mixDigest common.Hash) *Block {
return &Block{
header: &cpy,
transactions: b.transactions,
- receipts: b.receipts,
uncles: b.uncles,
}
}
diff --git a/core/types/receipt.go b/core/types/receipt.go
index d85fe16cf..aea5b3e91 100644
--- a/core/types/receipt.go
+++ b/core/types/receipt.go
@@ -41,8 +41,8 @@ type Receipt struct {
}
// NewReceipt creates a barebone transaction receipt, copying the init fields.
-func NewReceipt(root []byte, cumalativeGasUsed *big.Int) *Receipt {
- return &Receipt{PostState: common.CopyBytes(root), CumulativeGasUsed: new(big.Int).Set(cumalativeGasUsed)}
+func NewReceipt(root []byte, cumulativeGasUsed *big.Int) *Receipt {
+ return &Receipt{PostState: common.CopyBytes(root), CumulativeGasUsed: new(big.Int).Set(cumulativeGasUsed)}
}
// EncodeRLP implements rlp.Encoder, and flattens the consensus fields of a receipt
diff --git a/core/vm/log.go b/core/vm/log.go
index 822476f85..526221e43 100644
--- a/core/vm/log.go
+++ b/core/vm/log.go
@@ -25,19 +25,21 @@ import (
)
type Log struct {
+ // Consensus fields
Address common.Address
Topics []common.Hash
Data []byte
- Number uint64
- TxHash common.Hash
- TxIndex uint
- BlockHash common.Hash
- Index uint
+ // Derived fields (don't reorder!)
+ BlockNumber uint64
+ TxHash common.Hash
+ TxIndex uint
+ BlockHash common.Hash
+ Index uint
}
func NewLog(address common.Address, topics []common.Hash, data []byte, number uint64) *Log {
- return &Log{Address: address, Topics: topics, Data: data, Number: number}
+ return &Log{Address: address, Topics: topics, Data: data, BlockNumber: number}
}
func (l *Log) EncodeRLP(w io.Writer) error {