diff options
-rw-r--r-- | VERSION | 2 | ||||
-rw-r--r-- | accounts/abi/bind/backends/simulated.go | 3 | ||||
-rw-r--r-- | consensus/ethash/consensus.go | 81 | ||||
-rw-r--r-- | core/blockchain.go | 33 | ||||
-rw-r--r-- | core/blockchain_test.go | 8 | ||||
-rw-r--r-- | core/chain_makers.go | 1 | ||||
-rw-r--r-- | core/database_util.go | 194 | ||||
-rw-r--r-- | core/database_util_test.go | 74 | ||||
-rw-r--r-- | core/gen_genesis.go | 10 | ||||
-rw-r--r-- | core/genesis.go | 1 | ||||
-rw-r--r-- | core/state_processor.go | 10 | ||||
-rw-r--r-- | core/types/gen_receipt_json.go | 9 | ||||
-rw-r--r-- | core/types/receipt.go | 75 | ||||
-rw-r--r-- | eth/backend.go | 6 | ||||
-rw-r--r-- | eth/backend_test.go | 9 | ||||
-rw-r--r-- | eth/db_upgrade.go | 279 | ||||
-rw-r--r-- | eth/filters/filter_test.go | 12 | ||||
-rw-r--r-- | ethclient/ethclient.go | 2 | ||||
-rw-r--r-- | internal/ethapi/api.go | 214 | ||||
-rw-r--r-- | light/txpool.go | 66 | ||||
-rw-r--r-- | miner/worker.go | 4 | ||||
-rw-r--r-- | params/version.go | 4 |
22 files changed, 428 insertions, 669 deletions
@@ -1 +1 @@ -1.6.8 +1.7.0 diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 7ac8b5820..14bf7bd75 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -144,7 +144,8 @@ func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Addres // TransactionReceipt returns the receipt of a transaction. func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { - return core.GetReceipt(b.database, txHash), nil + receipt, _, _, _ := core.GetReceipt(b.database, txHash) + return receipt, nil } // PendingCodeAt returns the code associated with an account in the pending state. diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index dd9c81fd4..01d97a470 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -287,8 +287,10 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent * // given the parent block's time and difficulty. // TODO (karalabe): Move the chain maker into this package and make this private! func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int { - next := new(big.Int).Add(parent.Number, common.Big1) + next := new(big.Int).Add(parent.Number, big1) switch { + case config.IsMetropolis(next): + return calcDifficultyMetropolis(time, parent) case config.IsHomestead(next): return calcDifficultyHomestead(time, parent) default: @@ -299,10 +301,65 @@ func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Heade // Some weird constants to avoid constant memory allocs for them. var ( expDiffPeriod = big.NewInt(100000) + big1 = big.NewInt(1) + big2 = big.NewInt(2) + big9 = big.NewInt(9) big10 = big.NewInt(10) bigMinus99 = big.NewInt(-99) ) +// calcDifficultyMetropolis is the difficulty adjustment algorithm. It returns +// the difficulty that a new block should have when created at time given the +// parent block's time and difficulty. The calculation uses the Metropolis rules. +func calcDifficultyMetropolis(time uint64, parent *types.Header) *big.Int { + // https://github.com/ethereum/EIPs/issues/100. + // algorithm: + // diff = (parent_diff + + // (parent_diff / 2048 * max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99)) + // ) + 2^(periodCount - 2) + + bigTime := new(big.Int).SetUint64(time) + bigParentTime := new(big.Int).Set(parent.Time) + + // holds intermediate values to make the algo easier to read & audit + x := new(big.Int) + y := new(big.Int) + + // (2 if len(parent_uncles) else 1) - (block_timestamp - parent_timestamp) // 9 + x.Sub(bigTime, bigParentTime) + x.Div(x, big9) + if parent.UncleHash == types.EmptyUncleHash { + x.Sub(big1, x) + } else { + x.Sub(big2, x) + } + // max((2 if len(parent_uncles) else 1) - (block_timestamp - parent_timestamp) // 9, -99) + if x.Cmp(bigMinus99) < 0 { + x.Set(bigMinus99) + } + // (parent_diff + parent_diff // 2048 * max(1 - (block_timestamp - parent_timestamp) // 10, -99)) + y.Div(parent.Difficulty, params.DifficultyBoundDivisor) + x.Mul(y, x) + x.Add(parent.Difficulty, x) + + // minimum difficulty can ever be (before exponential factor) + if x.Cmp(params.MinimumDifficulty) < 0 { + x.Set(params.MinimumDifficulty) + } + // for the exponential factor + periodCount := new(big.Int).Add(parent.Number, big1) + periodCount.Div(periodCount, expDiffPeriod) + + // the exponential factor, commonly referred to as "the bomb" + // diff = diff + 2^(periodCount - 2) + if periodCount.Cmp(big1) > 0 { + y.Sub(periodCount, big2) + y.Exp(big2, y, nil) + x.Add(x, y) + } + return x +} + // calcDifficultyHomestead is the difficulty adjustment algorithm. It returns // the difficulty that a new block should have when created at time given the // parent block's time and difficulty. The calculation uses the Homestead rules. @@ -320,12 +377,12 @@ func calcDifficultyHomestead(time uint64, parent *types.Header) *big.Int { x := new(big.Int) y := new(big.Int) - // 1 - (block_timestamp -parent_timestamp) // 10 + // 1 - (block_timestamp - parent_timestamp) // 10 x.Sub(bigTime, bigParentTime) x.Div(x, big10) - x.Sub(common.Big1, x) + x.Sub(big1, x) - // max(1 - (block_timestamp - parent_timestamp) // 10, -99))) + // max(1 - (block_timestamp - parent_timestamp) // 10, -99) if x.Cmp(bigMinus99) < 0 { x.Set(bigMinus99) } @@ -339,14 +396,14 @@ func calcDifficultyHomestead(time uint64, parent *types.Header) *big.Int { x.Set(params.MinimumDifficulty) } // for the exponential factor - periodCount := new(big.Int).Add(parent.Number, common.Big1) + periodCount := new(big.Int).Add(parent.Number, big1) periodCount.Div(periodCount, expDiffPeriod) // the exponential factor, commonly referred to as "the bomb" // diff = diff + 2^(periodCount - 2) - if periodCount.Cmp(common.Big1) > 0 { - y.Sub(periodCount, common.Big2) - y.Exp(common.Big2, y, nil) + if periodCount.Cmp(big1) > 0 { + y.Sub(periodCount, big2) + y.Exp(big2, y, nil) x.Add(x, y) } return x @@ -373,12 +430,12 @@ func calcDifficultyFrontier(time uint64, parent *types.Header) *big.Int { diff.Set(params.MinimumDifficulty) } - periodCount := new(big.Int).Add(parent.Number, common.Big1) + periodCount := new(big.Int).Add(parent.Number, big1) periodCount.Div(periodCount, expDiffPeriod) - if periodCount.Cmp(common.Big1) > 0 { + if periodCount.Cmp(big1) > 0 { // diff = diff + 2^(periodCount - 2) - expDiff := periodCount.Sub(periodCount, common.Big2) - expDiff.Exp(common.Big2, expDiff, nil) + expDiff := periodCount.Sub(periodCount, big2) + expDiff.Exp(big2, expDiff, nil) diff.Add(diff, expDiff) diff = math.BigMax(diff, params.MinimumDifficulty) } diff --git a/core/blockchain.go b/core/blockchain.go index 6772ea284..bb1c14f43 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -759,16 +759,10 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ log.Crit("Failed to write log blooms", "err", err) return } - if err := WriteTransactions(bc.chainDb, block); err != nil { - errs[index] = fmt.Errorf("failed to write individual transactions: %v", err) + if err := WriteTxLookupEntries(bc.chainDb, block); err != nil { + errs[index] = fmt.Errorf("failed to write lookup metadata: %v", err) atomic.AddInt32(&failed, 1) - log.Crit("Failed to write individual transactions", "err", err) - return - } - if err := WriteReceipts(bc.chainDb, receipts); err != nil { - errs[index] = fmt.Errorf("failed to write individual receipts: %v", err) - atomic.AddInt32(&failed, 1) - log.Crit("Failed to write individual receipts", "err", err) + log.Crit("Failed to write lookup metadata", "err", err) return } atomic.AddInt32(&stats.processed, 1) @@ -1002,12 +996,8 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) { blockInsertTimer.UpdateSince(bstart) events = append(events, ChainEvent{block, block.Hash(), logs}) - // This puts transactions in a extra db for rpc - if err := WriteTransactions(bc.chainDb, block); err != nil { - return i, err - } - // store the receipts - if err := WriteReceipts(bc.chainDb, receipts); err != nil { + // Write the positional metadata for transaction and receipt lookups + if err := WriteTxLookupEntries(bc.chainDb, block); err != nil { return i, err } // Write map map bloom filters @@ -1167,16 +1157,12 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { for _, block := range newChain { // insert the block in the canonical way, re-writing history bc.insert(block) - // write canonical receipts and transactions - if err := WriteTransactions(bc.chainDb, block); err != nil { - return err - } - receipts := GetBlockReceipts(bc.chainDb, block.Hash(), block.NumberU64()) - // write receipts - if err := WriteReceipts(bc.chainDb, receipts); err != nil { + // write lookup entries for hash based transaction/receipt searches + if err := WriteTxLookupEntries(bc.chainDb, block); err != nil { return err } // Write map map bloom filters + receipts := GetBlockReceipts(bc.chainDb, block.Hash(), block.NumberU64()) if err := WriteMipmapBloom(bc.chainDb, block.NumberU64(), receipts); err != nil { return err } @@ -1188,8 +1174,7 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { // When transactions get deleted from the database that means the // receipts that were created in the fork must also be deleted for _, tx := range diff { - DeleteReceipt(bc.chainDb, tx.Hash()) - DeleteTransaction(bc.chainDb, tx.Hash()) + DeleteTxLookupEntry(bc.chainDb, tx.Hash()) } // Must be posted in a goroutine because of the transaction pool trying // to acquire the chain manager lock diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 371522ab7..5fa671e2b 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -806,8 +806,8 @@ func TestChainTxReorgs(t *testing.T) { if txn, _, _, _ := GetTransaction(db, tx.Hash()); txn != nil { t.Errorf("drop %d: tx %v found while shouldn't have been", i, txn) } - if GetReceipt(db, tx.Hash()) != nil { - t.Errorf("drop %d: receipt found while shouldn't have been", i) + if rcpt, _, _, _ := GetReceipt(db, tx.Hash()); rcpt != nil { + t.Errorf("drop %d: receipt %v found while shouldn't have been", i, rcpt) } } // added tx @@ -815,7 +815,7 @@ func TestChainTxReorgs(t *testing.T) { if txn, _, _, _ := GetTransaction(db, tx.Hash()); txn == nil { t.Errorf("add %d: expected tx to be found", i) } - if GetReceipt(db, tx.Hash()) == nil { + if rcpt, _, _, _ := GetReceipt(db, tx.Hash()); rcpt == nil { t.Errorf("add %d: expected receipt to be found", i) } } @@ -824,7 +824,7 @@ func TestChainTxReorgs(t *testing.T) { if txn, _, _, _ := GetTransaction(db, tx.Hash()); txn == nil { t.Errorf("share %d: expected tx to be found", i) } - if GetReceipt(db, tx.Hash()) == nil { + if rcpt, _, _, _ := GetReceipt(db, tx.Hash()); rcpt == nil { t.Errorf("share %d: expected receipt to be found", i) } } diff --git a/core/chain_makers.go b/core/chain_makers.go index 38a69d42a..976a8114d 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -218,6 +218,7 @@ func makeHeader(config *params.ChainConfig, parent *types.Block, state *state.St Number: parent.Number(), Time: new(big.Int).Sub(time, big.NewInt(10)), Difficulty: parent.Difficulty(), + UncleHash: parent.UncleHash(), }), GasLimit: CalcGasLimit(parent), GasUsed: new(big.Int), diff --git a/core/database_util.go b/core/database_util.go index b4a230c9c..697111394 100644 --- a/core/database_util.go +++ b/core/database_util.go @@ -45,24 +45,17 @@ var ( blockHashPrefix = []byte("H") // blockHashPrefix + hash -> num (uint64 big endian) bodyPrefix = []byte("b") // bodyPrefix + num (uint64 big endian) + hash -> block body blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts + lookupPrefix = []byte("l") // lookupPrefix + hash -> transaction/receipt lookup metadata preimagePrefix = "secure-key-" // preimagePrefix + hash -> preimage - txMetaSuffix = []byte{0x01} - receiptsPrefix = []byte("receipts-") - mipmapPre = []byte("mipmap-log-bloom-") MIPMapLevels = []uint64{1000000, 500000, 100000, 50000, 1000} configPrefix = []byte("ethereum-config-") // config prefix for the db - // used by old (non-sequential keys) db, now only used for conversion - oldBlockPrefix = []byte("block-") - oldHeaderSuffix = []byte("-header") - oldTdSuffix = []byte("-td") // headerPrefix + num (uint64 big endian) + hash + tdSuffix -> td - oldBodySuffix = []byte("-body") - oldBlockNumPrefix = []byte("block-num-") - oldBlockReceiptsPrefix = []byte("receipts-block-") - oldBlockHashPrefix = []byte("block-hash-") // [deprecated by the header/block split, remove eventually] + // used by old db, now only used for conversion + oldReceiptsPrefix = []byte("receipts-") + oldTxMetaSuffix = []byte{0x01} ErrChainConfigNotFound = errors.New("ChainConfig not found") // general config not found error @@ -72,6 +65,14 @@ var ( preimageHitCounter = metrics.NewCounter("db/preimage/hits") ) +// txLookupEntry is a positional metadata to help looking up the data content of +// a transaction or receipt given only its hash. +type txLookupEntry struct { + BlockHash common.Hash + BlockIndex uint64 + Index uint64 +} + // encodeBlockNumber encodes a block number as big endian uint64 func encodeBlockNumber(number uint64) []byte { enc := make([]byte, 8) @@ -83,10 +84,7 @@ func encodeBlockNumber(number uint64) []byte { func GetCanonicalHash(db ethdb.Database, number uint64) common.Hash { data, _ := db.Get(append(append(headerPrefix, encodeBlockNumber(number)...), numSuffix...)) if len(data) == 0 { - data, _ = db.Get(append(oldBlockNumPrefix, big.NewInt(int64(number)).Bytes()...)) - if len(data) == 0 { - return common.Hash{} - } + return common.Hash{} } return common.BytesToHash(data) } @@ -100,15 +98,7 @@ const missingNumber = uint64(0xffffffffffffffff) func GetBlockNumber(db ethdb.Database, hash common.Hash) uint64 { data, _ := db.Get(append(blockHashPrefix, hash.Bytes()...)) if len(data) != 8 { - data, _ := db.Get(append(append(oldBlockPrefix, hash.Bytes()...), oldHeaderSuffix...)) - if len(data) == 0 { - return missingNumber - } - header := new(types.Header) - if err := rlp.Decode(bytes.NewReader(data), header); err != nil { - log.Crit("Failed to decode block header", "err", err) - } - return header.Number.Uint64() + return missingNumber } return binary.BigEndian.Uint64(data) } @@ -151,9 +141,6 @@ func GetHeadFastBlockHash(db ethdb.Database) common.Hash { // if the header's not found. func GetHeaderRLP(db ethdb.Database, hash common.Hash, number uint64) rlp.RawValue { data, _ := db.Get(append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...)) - if len(data) == 0 { - data, _ = db.Get(append(append(oldBlockPrefix, hash.Bytes()...), oldHeaderSuffix...)) - } return data } @@ -175,9 +162,6 @@ func GetHeader(db ethdb.Database, hash common.Hash, number uint64) *types.Header // GetBodyRLP retrieves the block body (transactions and uncles) in RLP encoding. func GetBodyRLP(db ethdb.Database, hash common.Hash, number uint64) rlp.RawValue { data, _ := db.Get(append(append(bodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...)) - if len(data) == 0 { - data, _ = db.Get(append(append(oldBlockPrefix, hash.Bytes()...), oldBodySuffix...)) - } return data } @@ -201,10 +185,7 @@ func GetBody(db ethdb.Database, hash common.Hash, number uint64) *types.Body { func GetTd(db ethdb.Database, hash common.Hash, number uint64) *big.Int { data, _ := db.Get(append(append(append(headerPrefix, encodeBlockNumber(number)...), hash[:]...), tdSuffix...)) if len(data) == 0 { - data, _ = db.Get(append(append(oldBlockPrefix, hash.Bytes()...), oldTdSuffix...)) - if len(data) == 0 { - return nil - } + return nil } td := new(big.Int) if err := rlp.Decode(bytes.NewReader(data), td); err != nil { @@ -239,10 +220,7 @@ func GetBlock(db ethdb.Database, hash common.Hash, number uint64) *types.Block { func GetBlockReceipts(db ethdb.Database, hash common.Hash, number uint64) types.Receipts { data, _ := db.Get(append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash[:]...)) if len(data) == 0 { - data, _ = db.Get(append(oldBlockReceiptsPrefix, hash.Bytes()...)) - if len(data) == 0 { - return nil - } + return nil } storageReceipts := []*types.ReceiptForStorage{} if err := rlp.DecodeBytes(data, &storageReceipts); err != nil { @@ -256,10 +234,38 @@ func GetBlockReceipts(db ethdb.Database, hash common.Hash, number uint64) types. return receipts } +// GetTxLookupEntry retrieves the positional metadata associated with a transaction +// hash to allow retrieving the transaction or receipt by hash. +func GetTxLookupEntry(db ethdb.Database, hash common.Hash) (common.Hash, uint64, uint64) { + // Load the positional metadata from disk and bail if it fails + data, _ := db.Get(append(lookupPrefix, hash.Bytes()...)) + if len(data) == 0 { + return common.Hash{}, 0, 0 + } + // Parse and return the contents of the lookup entry + var entry txLookupEntry + if err := rlp.DecodeBytes(data, &entry); err != nil { + log.Error("Invalid lookup entry RLP", "hash", hash, "err", err) + return common.Hash{}, 0, 0 + } + return entry.BlockHash, entry.BlockIndex, entry.Index +} + // GetTransaction retrieves a specific transaction from the database, along with // its added positional metadata. func GetTransaction(db ethdb.Database, hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) { - // Retrieve the transaction itself from the database + // Retrieve the lookup metadata and resolve the transaction from the body + blockHash, blockNumber, txIndex := GetTxLookupEntry(db, hash) + + if blockHash != (common.Hash{}) { + body := GetBody(db, blockHash, blockNumber) + if body == nil || len(body.Transactions) <= int(txIndex) { + log.Error("Transaction referenced missing", "number", blockNumber, "hash", blockHash, "index", txIndex) + return nil, common.Hash{}, 0, 0 + } + return body.Transactions[txIndex], blockHash, blockNumber, txIndex + } + // Old transaction representation, load the transaction and it's metadata separately data, _ := db.Get(hash.Bytes()) if len(data) == 0 { return nil, common.Hash{}, 0, 0 @@ -269,33 +275,42 @@ func GetTransaction(db ethdb.Database, hash common.Hash) (*types.Transaction, co return nil, common.Hash{}, 0, 0 } // Retrieve the blockchain positional metadata - data, _ = db.Get(append(hash.Bytes(), txMetaSuffix...)) + data, _ = db.Get(append(hash.Bytes(), oldTxMetaSuffix...)) if len(data) == 0 { return nil, common.Hash{}, 0, 0 } - var meta struct { - BlockHash common.Hash - BlockIndex uint64 - Index uint64 - } - if err := rlp.DecodeBytes(data, &meta); err != nil { + var entry txLookupEntry + if err := rlp.DecodeBytes(data, &entry); err != nil { return nil, common.Hash{}, 0, 0 } - return &tx, meta.BlockHash, meta.BlockIndex, meta.Index + return &tx, entry.BlockHash, entry.BlockIndex, entry.Index } -// GetReceipt returns a receipt by hash -func GetReceipt(db ethdb.Database, hash common.Hash) *types.Receipt { - data, _ := db.Get(append(receiptsPrefix, hash[:]...)) +// GetReceipt retrieves a specific transaction receipt from the database, along with +// its added positional metadata. +func GetReceipt(db ethdb.Database, hash common.Hash) (*types.Receipt, common.Hash, uint64, uint64) { + // Retrieve the lookup metadata and resolve the receipt from the receipts + blockHash, blockNumber, receiptIndex := GetTxLookupEntry(db, hash) + + if blockHash != (common.Hash{}) { + receipts := GetBlockReceipts(db, blockHash, blockNumber) + if len(receipts) <= int(receiptIndex) { + log.Error("Receipt refereced missing", "number", blockNumber, "hash", blockHash, "index", receiptIndex) + return nil, common.Hash{}, 0, 0 + } + return receipts[receiptIndex], blockHash, blockNumber, receiptIndex + } + // Old receipt representation, load the receipt and set an unknown metadata + data, _ := db.Get(append(oldReceiptsPrefix, hash[:]...)) if len(data) == 0 { - return nil + return nil, common.Hash{}, 0, 0 } var receipt types.ReceiptForStorage err := rlp.DecodeBytes(data, &receipt) if err != nil { log.Error("Invalid receipt RLP", "hash", hash, "err", err) } - return (*types.Receipt)(&receipt) + return (*types.Receipt)(&receipt), common.Hash{}, 0, 0 } // WriteCanonicalHash stores the canonical hash for the given block number. @@ -416,76 +431,29 @@ func WriteBlockReceipts(db ethdb.Database, hash common.Hash, number uint64, rece return nil } -// WriteTransactions stores the transactions associated with a specific block -// into the given database. Beside writing the transaction, the function also -// stores a metadata entry along with the transaction, detailing the position -// of this within the blockchain. -func WriteTransactions(db ethdb.Database, block *types.Block) error { +// WriteTxLookupEntries stores a positional metadata for every transaction from +// a block, enabling hash based transaction and receipt lookups. +func WriteTxLookupEntries(db ethdb.Database, block *types.Block) error { batch := db.NewBatch() - // Iterate over each transaction and encode it with its metadata + // Iterate over each transaction and encode its metadata for i, tx := range block.Transactions() { - // Encode and queue up the transaction for storage - data, err := rlp.EncodeToBytes(tx) - if err != nil { - return err - } - if err = batch.Put(tx.Hash().Bytes(), data); err != nil { - return err - } - // Encode and queue up the transaction metadata for storage - meta := struct { - BlockHash common.Hash - BlockIndex uint64 - Index uint64 - }{ + entry := txLookupEntry{ BlockHash: block.Hash(), BlockIndex: block.NumberU64(), Index: uint64(i), } - data, err = rlp.EncodeToBytes(meta) - if err != nil { - return err - } - if err := batch.Put(append(tx.Hash().Bytes(), txMetaSuffix...), data); err != nil { - return err - } - } - // Write the scheduled data into the database - if err := batch.Write(); err != nil { - log.Crit("Failed to store transactions", "err", err) - } - return nil -} - -// WriteReceipt stores a single transaction receipt into the database. -func WriteReceipt(db ethdb.Database, receipt *types.Receipt) error { - storageReceipt := (*types.ReceiptForStorage)(receipt) - data, err := rlp.EncodeToBytes(storageReceipt) - if err != nil { - return err - } - return db.Put(append(receiptsPrefix, receipt.TxHash.Bytes()...), data) -} - -// WriteReceipts stores a batch of transaction receipts into the database. -func WriteReceipts(db ethdb.Database, receipts types.Receipts) error { - batch := db.NewBatch() - - // Iterate over all the receipts and queue them for database injection - for _, receipt := range receipts { - storageReceipt := (*types.ReceiptForStorage)(receipt) - data, err := rlp.EncodeToBytes(storageReceipt) + data, err := rlp.EncodeToBytes(entry) if err != nil { return err } - if err := batch.Put(append(receiptsPrefix, receipt.TxHash.Bytes()...), data); err != nil { + if err := batch.Put(append(lookupPrefix, tx.Hash().Bytes()...), data); err != nil { return err } } // Write the scheduled data into the database if err := batch.Write(); err != nil { - log.Crit("Failed to store receipts", "err", err) + log.Crit("Failed to store lookup entries", "err", err) } return nil } @@ -524,15 +492,9 @@ func DeleteBlockReceipts(db ethdb.Database, hash common.Hash, number uint64) { db.Delete(append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash.Bytes()...)) } -// DeleteTransaction removes all transaction data associated with a hash. -func DeleteTransaction(db ethdb.Database, hash common.Hash) { - db.Delete(hash.Bytes()) - db.Delete(append(hash.Bytes(), txMetaSuffix...)) -} - -// DeleteReceipt removes all receipt data associated with a transaction hash. -func DeleteReceipt(db ethdb.Database, hash common.Hash) { - db.Delete(append(receiptsPrefix, hash.Bytes()...)) +// DeleteTxLookupEntry removes all transaction data associated with a hash. +func DeleteTxLookupEntry(db ethdb.Database, hash common.Hash) { + db.Delete(append(lookupPrefix, hash.Bytes()...)) } // returns a formatted MIP mapped key by adding prefix, canonical number and level diff --git a/core/database_util_test.go b/core/database_util_test.go index 9f16b660a..e9a6df97b 100644 --- a/core/database_util_test.go +++ b/core/database_util_test.go @@ -290,8 +290,8 @@ func TestHeadStorage(t *testing.T) { } } -// Tests that transactions and associated metadata can be stored and retrieved. -func TestTransactionStorage(t *testing.T) { +// Tests that positional lookup metadata can be stored and retrieved. +func TestLookupStorage(t *testing.T) { db, _ := ethdb.NewMemDatabase() tx1 := types.NewTransaction(1, common.BytesToAddress([]byte{0x11}), big.NewInt(111), big.NewInt(1111), big.NewInt(11111), []byte{0x11, 0x11, 0x11}) @@ -308,7 +308,10 @@ func TestTransactionStorage(t *testing.T) { } } // Insert all the transactions into the database, and verify contents - if err := WriteTransactions(db, block); err != nil { + if err := WriteBlock(db, block); err != nil { + t.Fatalf("failed to write block contents: %v", err) + } + if err := WriteTxLookupEntries(db, block); err != nil { t.Fatalf("failed to write transactions: %v", err) } for i, tx := range txs { @@ -325,72 +328,13 @@ func TestTransactionStorage(t *testing.T) { } // Delete the transactions and check purge for i, tx := range txs { - DeleteTransaction(db, tx.Hash()) + DeleteTxLookupEntry(db, tx.Hash()) if txn, _, _, _ := GetTransaction(db, tx.Hash()); txn != nil { t.Fatalf("tx #%d [%x]: deleted transaction returned: %v", i, tx.Hash(), txn) } } } -// Tests that receipts can be stored and retrieved. -func TestReceiptStorage(t *testing.T) { - db, _ := ethdb.NewMemDatabase() - - receipt1 := &types.Receipt{ - PostState: []byte{0x01}, - CumulativeGasUsed: big.NewInt(1), - Logs: []*types.Log{ - {Address: common.BytesToAddress([]byte{0x11})}, - {Address: common.BytesToAddress([]byte{0x01, 0x11})}, - }, - TxHash: common.BytesToHash([]byte{0x11, 0x11}), - ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}), - GasUsed: big.NewInt(111111), - } - receipt2 := &types.Receipt{ - PostState: []byte{0x02}, - CumulativeGasUsed: big.NewInt(2), - Logs: []*types.Log{ - {Address: common.BytesToAddress([]byte{0x22})}, - {Address: common.BytesToAddress([]byte{0x02, 0x22})}, - }, - TxHash: common.BytesToHash([]byte{0x22, 0x22}), - ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}), - GasUsed: big.NewInt(222222), - } - receipts := []*types.Receipt{receipt1, receipt2} - - // Check that no receipt entries are in a pristine database - for i, receipt := range receipts { - if r := GetReceipt(db, receipt.TxHash); r != nil { - t.Fatalf("receipt #%d [%x]: non existent receipt returned: %v", i, receipt.TxHash, r) - } - } - // Insert all the receipts into the database, and verify contents - if err := WriteReceipts(db, receipts); err != nil { - t.Fatalf("failed to write receipts: %v", err) - } - for i, receipt := range receipts { - if r := GetReceipt(db, receipt.TxHash); r == nil { - t.Fatalf("receipt #%d [%x]: receipt not found", i, receipt.TxHash) - } else { - rlpHave, _ := rlp.EncodeToBytes(r) - rlpWant, _ := rlp.EncodeToBytes(receipt) - - if !bytes.Equal(rlpHave, rlpWant) { - t.Fatalf("receipt #%d [%x]: receipt mismatch: have %v, want %v", i, receipt.TxHash, r, receipt) - } - } - } - // Delete the receipts and check purge - for i, receipt := range receipts { - DeleteReceipt(db, receipt.TxHash) - if r := GetReceipt(db, receipt.TxHash); r != nil { - t.Fatalf("receipt #%d [%x]: deleted receipt returned: %v", i, receipt.TxHash, r) - } - } -} - // Tests that receipts associated with a single block can be stored and retrieved. func TestBlockReceiptStorage(t *testing.T) { db, _ := ethdb.NewMemDatabase() @@ -530,10 +474,6 @@ func TestMipmapChain(t *testing.T) { } // store the receipts - err := WriteReceipts(db, receipts) - if err != nil { - t.Fatal(err) - } WriteMipmapBloom(db, uint64(i+1), receipts) }) for i, block := range chain { diff --git a/core/gen_genesis.go b/core/gen_genesis.go index 1f3b4a8aa..4d75704a6 100644 --- a/core/gen_genesis.go +++ b/core/gen_genesis.go @@ -13,8 +13,6 @@ import ( "github.com/ethereum/go-ethereum/params" ) -var _ = (*genesisSpecMarshaling)(nil) - func (g Genesis) MarshalJSON() ([]byte, error) { type Genesis struct { Config *params.ChainConfig `json:"config"` @@ -26,7 +24,7 @@ func (g Genesis) MarshalJSON() ([]byte, error) { Mixhash common.Hash `json:"mixHash"` Coinbase common.Address `json:"coinbase"` Alloc map[common.UnprefixedAddress]GenesisAccount `json:"alloc" gencodec:"required"` - Number uint64 `json:"number"` + Number math.HexOrDecimal64 `json:"number"` GasUsed math.HexOrDecimal64 `json:"gasUsed"` ParentHash common.Hash `json:"parentHash"` } @@ -45,7 +43,7 @@ func (g Genesis) MarshalJSON() ([]byte, error) { enc.Alloc[common.UnprefixedAddress(k)] = v } } - enc.Number = g.Number + enc.Number = math.HexOrDecimal64(g.Number) enc.GasUsed = math.HexOrDecimal64(g.GasUsed) enc.ParentHash = g.ParentHash return json.Marshal(&enc) @@ -62,7 +60,7 @@ func (g *Genesis) UnmarshalJSON(input []byte) error { Mixhash *common.Hash `json:"mixHash"` Coinbase *common.Address `json:"coinbase"` Alloc map[common.UnprefixedAddress]GenesisAccount `json:"alloc" gencodec:"required"` - Number *uint64 `json:"number"` + Number *math.HexOrDecimal64 `json:"number"` GasUsed *math.HexOrDecimal64 `json:"gasUsed"` ParentHash *common.Hash `json:"parentHash"` } @@ -104,7 +102,7 @@ func (g *Genesis) UnmarshalJSON(input []byte) error { g.Alloc[common.Address(k)] = v } if dec.Number != nil { - g.Number = *dec.Number + g.Number = uint64(*dec.Number) } if dec.GasUsed != nil { g.GasUsed = uint64(*dec.GasUsed) diff --git a/core/genesis.go b/core/genesis.go index a507d522b..fd6ed6115 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -92,6 +92,7 @@ type genesisSpecMarshaling struct { ExtraData hexutil.Bytes GasLimit math.HexOrDecimal64 GasUsed math.HexOrDecimal64 + Number math.HexOrDecimal64 Difficulty *math.HexOrDecimal256 Alloc map[common.UnprefixedAddress]GenesisAccount } diff --git a/core/state_processor.go b/core/state_processor.go index 90f5a4f60..4489cfce2 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -104,11 +104,17 @@ func ApplyTransaction(config *params.ChainConfig, bc *BlockChain, author *common } // Update the state with pending changes + var root []byte + if config.IsMetropolis(header.Number) { + statedb.Finalise() + } else { + root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes() + } usedGas.Add(usedGas, gas) + // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx // based on the eip phase, we're passing wether the root touch-delete accounts. - root := statedb.IntermediateRoot(config.IsEIP158(header.Number)) - receipt := types.NewReceipt(root.Bytes(), usedGas) + receipt := types.NewReceipt(root, usedGas) receipt.TxHash = tx.Hash() receipt.GasUsed = new(big.Int).Set(gas) // if the transaction created a contract, store the creation address in the receipt. diff --git a/core/types/gen_receipt_json.go b/core/types/gen_receipt_json.go index edbd64ba4..eb2e5d42b 100644 --- a/core/types/gen_receipt_json.go +++ b/core/types/gen_receipt_json.go @@ -13,7 +13,7 @@ import ( func (r Receipt) MarshalJSON() ([]byte, error) { type Receipt struct { - PostState hexutil.Bytes `json:"root" gencodec:"required"` + PostState hexutil.Bytes `json:"root"` CumulativeGasUsed *hexutil.Big `json:"cumulativeGasUsed" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"` Logs []*Log `json:"logs" gencodec:"required"` @@ -34,7 +34,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) { func (r *Receipt) UnmarshalJSON(input []byte) error { type Receipt struct { - PostState hexutil.Bytes `json:"root" gencodec:"required"` + PostState hexutil.Bytes `json:"root"` CumulativeGasUsed *hexutil.Big `json:"cumulativeGasUsed" gencodec:"required"` Bloom *Bloom `json:"logsBloom" gencodec:"required"` Logs []*Log `json:"logs" gencodec:"required"` @@ -46,10 +46,9 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { if err := json.Unmarshal(input, &dec); err != nil { return err } - if dec.PostState == nil { - return errors.New("missing required field 'root' for Receipt") + if dec.PostState != nil { + r.PostState = dec.PostState } - r.PostState = dec.PostState if dec.CumulativeGasUsed == nil { return errors.New("missing required field 'cumulativeGasUsed' for Receipt") } diff --git a/core/types/receipt.go b/core/types/receipt.go index ef6f6a2bb..c9906b015 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -31,7 +31,7 @@ import ( // Receipt represents the results of a transaction. type Receipt struct { // Consensus fields - PostState []byte `json:"root" gencodec:"required"` + PostState []byte `json:"root"` CumulativeGasUsed *big.Int `json:"cumulativeGasUsed" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"` Logs []*Log `json:"logs" gencodec:"required"` @@ -48,35 +48,88 @@ type receiptMarshaling struct { GasUsed *hexutil.Big } +// homesteadReceiptRLP contains the receipt's Homestead consensus fields, used +// during RLP serialization. +type homesteadReceiptRLP struct { + PostState []byte + CumulativeGasUsed *big.Int + Bloom Bloom + Logs []*Log +} + +// metropolisReceiptRLP contains the receipt's Metropolis consensus fields, used +// during RLP serialization. +type metropolisReceiptRLP struct { + CumulativeGasUsed *big.Int + Bloom Bloom + Logs []*Log +} + // NewReceipt creates a barebone transaction receipt, copying the init fields. 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 -// into an RLP stream. +// into an RLP stream. If no post state is present, metropolis fork is assumed. func (r *Receipt) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, []interface{}{r.PostState, r.CumulativeGasUsed, r.Bloom, r.Logs}) + if r.PostState == nil { + return rlp.Encode(w, &metropolisReceiptRLP{r.CumulativeGasUsed, r.Bloom, r.Logs}) + } + return rlp.Encode(w, &homesteadReceiptRLP{r.PostState, r.CumulativeGasUsed, r.Bloom, r.Logs}) } // DecodeRLP implements rlp.Decoder, and loads the consensus fields of a receipt // from an RLP stream. func (r *Receipt) DecodeRLP(s *rlp.Stream) error { - var receipt struct { - PostState []byte - CumulativeGasUsed *big.Int - Bloom Bloom - Logs []*Log + // Load the raw bytes since we have multiple possible formats + raw, err := s.Raw() + if err != nil { + return err } - if err := s.Decode(&receipt); err != nil { + list, _, err := rlp.SplitList(raw) + if err != nil { return err } - r.PostState, r.CumulativeGasUsed, r.Bloom, r.Logs = receipt.PostState, receipt.CumulativeGasUsed, receipt.Bloom, receipt.Logs - return nil + items, err := rlp.CountValues(list) + if err != nil { + return err + } + // Deserialize based on the number of content items + switch items { + case 3: + // Metropolis receipts have 3 components + var metro metropolisReceiptRLP + if err := rlp.DecodeBytes(raw, &metro); err != nil { + return err + } + r.CumulativeGasUsed = metro.CumulativeGasUsed + r.Bloom = metro.Bloom + r.Logs = metro.Logs + return nil + + case 4: + // Homestead receipts have 4 components + var home homesteadReceiptRLP + if err := rlp.DecodeBytes(raw, &home); err != nil { + return err + } + r.PostState = home.PostState[:] + r.CumulativeGasUsed = home.CumulativeGasUsed + r.Bloom = home.Bloom + r.Logs = home.Logs + return nil + + default: + return fmt.Errorf("invalid receipt components: %v", items) + } } // String implements the Stringer interface. func (r *Receipt) String() string { + if r.PostState == nil { + return fmt.Sprintf("receipt{cgas=%v bloom=%x logs=%v}", r.CumulativeGasUsed, r.Bloom, r.Logs) + } return fmt.Sprintf("receipt{med=%x cgas=%v bloom=%x logs=%v}", r.PostState, r.CumulativeGasUsed, r.Bloom, r.Logs) } diff --git a/eth/backend.go b/eth/backend.go index 78478e86e..c7df517c0 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -59,8 +59,8 @@ type LesServer interface { type Ethereum struct { chainConfig *params.ChainConfig // Channel for shutting down the service - shutdownChan chan bool // Channel for shutting down the ethereum - stopDbUpgrade func() // stop chain db sequential key upgrade + shutdownChan chan bool // Channel for shutting down the ethereum + stopDbUpgrade func() error // stop chain db sequential key upgrade // Handlers txPool *core.TxPool blockchain *core.BlockChain @@ -103,7 +103,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { if err != nil { return nil, err } - stopDbUpgrade := upgradeSequentialKeys(chainDb) + stopDbUpgrade := upgradeDeduplicateData(chainDb) chainConfig, genesisHash, genesisErr := core.SetupGenesisBlock(chainDb, config.Genesis) if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok { return nil, genesisErr diff --git a/eth/backend_test.go b/eth/backend_test.go index f60e3214c..4351b24cf 100644 --- a/eth/backend_test.go +++ b/eth/backend_test.go @@ -33,24 +33,15 @@ func TestMipmapUpgrade(t *testing.T) { genesis := new(core.Genesis).MustCommit(db) chain, receipts := core.GenerateChain(params.TestChainConfig, genesis, db, 10, func(i int, gen *core.BlockGen) { - var receipts types.Receipts switch i { case 1: receipt := types.NewReceipt(nil, new(big.Int)) receipt.Logs = []*types.Log{{Address: addr}} gen.AddUncheckedReceipt(receipt) - receipts = types.Receipts{receipt} case 2: receipt := types.NewReceipt(nil, new(big.Int)) receipt.Logs = []*types.Log{{Address: addr}} gen.AddUncheckedReceipt(receipt) - receipts = types.Receipts{receipt} - } - - // store the receipts - err := core.WriteReceipts(db, receipts) - if err != nil { - t.Fatal(err) } }) for i, block := range chain { diff --git a/eth/db_upgrade.go b/eth/db_upgrade.go index 82cdd7e55..90111b2b3 100644 --- a/eth/db_upgrade.go +++ b/eth/db_upgrade.go @@ -19,237 +19,120 @@ package eth import ( "bytes" - "encoding/binary" "fmt" - "math/big" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" ) -var useSequentialKeys = []byte("dbUpgrade_20160530sequentialKeys") +var deduplicateData = []byte("dbUpgrade_20170714deduplicateData") -// upgradeSequentialKeys checks the chain database version and +// upgradeDeduplicateData checks the chain database version and // starts a background process to make upgrades if necessary. // Returns a stop function that blocks until the process has // been safely stopped. -func upgradeSequentialKeys(db ethdb.Database) (stopFn func()) { - data, _ := db.Get(useSequentialKeys) +func upgradeDeduplicateData(db ethdb.Database) func() error { + // If the database is already converted or empty, bail out + data, _ := db.Get(deduplicateData) if len(data) > 0 && data[0] == 42 { - return nil // already converted + return nil } - if data, _ := db.Get([]byte("LastHeader")); len(data) == 0 { - db.Put(useSequentialKeys, []byte{42}) - return nil // empty database, nothing to do + db.Put(deduplicateData, []byte{42}) + return nil } - - log.Warn("Upgrading chain database to use sequential keys") - - stopChn := make(chan struct{}) - stoppedChn := make(chan struct{}) + // Start the deduplication upgrade on a new goroutine + log.Warn("Upgrading database to use lookup entries") + stop := make(chan chan error) go func() { - stopFn := func() bool { - select { - case <-time.After(time.Microsecond * 100): // make sure other processes don't get starved - case <-stopChn: - return true - } - return false - } - - err, stopped := upgradeSequentialCanonicalNumbers(db, stopFn) - if err == nil && !stopped { - err, stopped = upgradeSequentialBlocks(db, stopFn) - } - if err == nil && !stopped { - err, stopped = upgradeSequentialOrphanedReceipts(db, stopFn) - } - if err == nil && !stopped { - log.Info("Database conversion successful") - db.Put(useSequentialKeys, []byte{42}) - } - if err != nil { - log.Error("Database conversion failed", "err", err) - } - close(stoppedChn) - }() - - return func() { - close(stopChn) - <-stoppedChn - } -} - -// upgradeSequentialCanonicalNumbers reads all old format canonical numbers from -// the database, writes them in new format and deletes the old ones if successful. -func upgradeSequentialCanonicalNumbers(db ethdb.Database, stopFn func() bool) (error, bool) { - prefix := []byte("block-num-") - it := db.(*ethdb.LDBDatabase).NewIterator() - defer func() { - it.Release() - }() - it.Seek(prefix) - cnt := 0 - for bytes.HasPrefix(it.Key(), prefix) { - keyPtr := it.Key() - if len(keyPtr) < 20 { - cnt++ - if cnt%100000 == 0 { + // Create an iterator to read the entire database and covert old lookup entires + it := db.(*ethdb.LDBDatabase).NewIterator() + defer func() { + if it != nil { it.Release() - it = db.(*ethdb.LDBDatabase).NewIterator() - it.Seek(keyPtr) - log.Info("Converting canonical numbers", "count", cnt) } - number := big.NewInt(0).SetBytes(keyPtr[10:]).Uint64() - newKey := []byte("h12345678n") - binary.BigEndian.PutUint64(newKey[1:9], number) - if err := db.Put(newKey, it.Value()); err != nil { - return err, false + }() + + var ( + converted uint64 + failed error + ) + for failed == nil && it.Next() { + // Skip any entries that don't look like old transaction meta entires (<hash>0x01) + key := it.Key() + if len(key) != common.HashLength+1 || key[common.HashLength] != 0x01 { + continue } - if err := db.Delete(keyPtr); err != nil { - return err, false + // Skip any entries that don't contain metadata (name clash between <hash>0x01 and <some-prefix><hash>) + var meta struct { + BlockHash common.Hash + BlockIndex uint64 + Index uint64 } - } - - if stopFn() { - return nil, true - } - it.Next() - } - if cnt > 0 { - log.Info("converted canonical numbers", "count", cnt) - } - return nil, false -} - -// upgradeSequentialBlocks reads all old format block headers, bodies, TDs and block -// receipts from the database, writes them in new format and deletes the old ones -// if successful. -func upgradeSequentialBlocks(db ethdb.Database, stopFn func() bool) (error, bool) { - prefix := []byte("block-") - it := db.(*ethdb.LDBDatabase).NewIterator() - defer func() { - it.Release() - }() - it.Seek(prefix) - cnt := 0 - for bytes.HasPrefix(it.Key(), prefix) { - keyPtr := it.Key() - if len(keyPtr) >= 38 { - cnt++ - if cnt%10000 == 0 { - it.Release() - it = db.(*ethdb.LDBDatabase).NewIterator() - it.Seek(keyPtr) - log.Info("Converting blocks", "count", cnt) + if err := rlp.DecodeBytes(it.Value(), &meta); err != nil { + continue } - // convert header, body, td and block receipts - var keyPrefix [38]byte - copy(keyPrefix[:], keyPtr[0:38]) - hash := keyPrefix[6:38] - if err := upgradeSequentialBlockData(db, hash); err != nil { - return err, false - } - // delete old db entries belonging to this hash - for bytes.HasPrefix(it.Key(), keyPrefix[:]) { - if err := db.Delete(it.Key()); err != nil { - return err, false + // Skip any already upgraded entries (clash due to <hash> ending with 0x01 (old suffix)) + hash := key[:common.HashLength] + + if hash[0] == byte('l') { + // Potential clash, the "old" `hash` must point to a live transaction. + if tx, _, _, _ := core.GetTransaction(db, common.BytesToHash(hash)); tx == nil || !bytes.Equal(tx.Hash().Bytes(), hash) { + continue } - it.Next() } - if err := db.Delete(append([]byte("receipts-block-"), hash...)); err != nil { - return err, false + // Convert the old metadata to a new lookup entry, delete duplicate data + if failed = db.Put(append([]byte("l"), hash...), it.Value()); failed == nil { // Write the new looku entry + if failed = db.Delete(hash); failed == nil { // Delete the duplicate transaction data + if failed = db.Delete(append([]byte("receipts-"), hash...)); failed == nil { // Delete the duplicate receipt data + if failed = db.Delete(key); failed != nil { // Delete the old transaction metadata + break + } + } + } } - } else { - it.Next() - } - - if stopFn() { - return nil, true - } - } - if cnt > 0 { - log.Info("Converted blocks", "count", cnt) - } - return nil, false -} + // Bump the conversion counter, and recreate the iterator occasionally to + // avoid too high memory consumption. + converted++ + if converted%100000 == 0 { + it.Release() + it = db.(*ethdb.LDBDatabase).NewIterator() + it.Seek(key) -// upgradeSequentialOrphanedReceipts removes any old format block receipts from the -// database that did not have a corresponding block -func upgradeSequentialOrphanedReceipts(db ethdb.Database, stopFn func() bool) (error, bool) { - prefix := []byte("receipts-block-") - it := db.(*ethdb.LDBDatabase).NewIterator() - defer it.Release() - it.Seek(prefix) - cnt := 0 - for bytes.HasPrefix(it.Key(), prefix) { - // phase 2 already converted receipts belonging to existing - // blocks, just remove if there's anything left - cnt++ - if err := db.Delete(it.Key()); err != nil { - return err, false + log.Info("Deduplicating database entries", "deduped", converted) + } + // Check for termination, or continue after a bit of a timeout + select { + case errc := <-stop: + errc <- nil + return + case <-time.After(time.Microsecond * 100): + } } - - if stopFn() { - return nil, true + // Upgrade finished, mark a such and terminate + if failed == nil { + log.Info("Database deduplication successful", "deduped", converted) + db.Put(deduplicateData, []byte{42}) + } else { + log.Error("Database deduplication failed", "deduped", converted, "err", failed) } - it.Next() - } - if cnt > 0 { - log.Info("Removed orphaned block receipts", "count", cnt) - } - return nil, false -} + it.Release() + it = nil -// upgradeSequentialBlockData upgrades the header, body, td and block receipts -// database entries belonging to a single hash (doesn't delete old data). -func upgradeSequentialBlockData(db ethdb.Database, hash []byte) error { - // get old chain data and block number - headerRLP, _ := db.Get(append(append([]byte("block-"), hash...), []byte("-header")...)) - if len(headerRLP) == 0 { - return nil - } - header := new(types.Header) - if err := rlp.Decode(bytes.NewReader(headerRLP), header); err != nil { - return err - } - number := header.Number.Uint64() - bodyRLP, _ := db.Get(append(append([]byte("block-"), hash...), []byte("-body")...)) - tdRLP, _ := db.Get(append(append([]byte("block-"), hash...), []byte("-td")...)) - receiptsRLP, _ := db.Get(append([]byte("receipts-block-"), hash...)) - // store new hash -> number association - encNum := make([]byte, 8) - binary.BigEndian.PutUint64(encNum, number) - if err := db.Put(append([]byte("H"), hash...), encNum); err != nil { - return err - } - // store new chain data - if err := db.Put(append(append([]byte("h"), encNum...), hash...), headerRLP); err != nil { - return err - } - if len(tdRLP) != 0 { - if err := db.Put(append(append(append([]byte("h"), encNum...), hash...), []byte("t")...), tdRLP); err != nil { - return err - } - } - if len(bodyRLP) != 0 { - if err := db.Put(append(append([]byte("b"), encNum...), hash...), bodyRLP); err != nil { - return err - } - } - if len(receiptsRLP) != 0 { - if err := db.Put(append(append([]byte("r"), encNum...), hash...), receiptsRLP); err != nil { - return err - } + errc := <-stop + errc <- failed + }() + // Assembly the cancellation callback + return func() error { + errc := make(chan error) + stop <- errc + return <-errc } - return nil } func addMipmapBloomBins(db ethdb.Database) (err error) { diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go index cd5e7cafd..b6cfd4bbc 100644 --- a/eth/filters/filter_test.go +++ b/eth/filters/filter_test.go @@ -82,12 +82,6 @@ func BenchmarkMipmaps(b *testing.B) { gen.AddUncheckedReceipt(receipt) } - - // store the receipts - err := core.WriteReceipts(db, receipts) - if err != nil { - b.Fatal(err) - } core.WriteMipmapBloom(db, uint64(i+1), receipts) }) for i, block := range chain { @@ -183,12 +177,6 @@ func TestFilters(t *testing.T) { gen.AddUncheckedReceipt(receipt) receipts = types.Receipts{receipt} } - - // store the receipts - err := core.WriteReceipts(db, receipts) - if err != nil { - t.Fatal(err) - } // i is used as block number for the writes but since the i // starts at 0 and block 0 (genesis) is already present increment // by one diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 45bb87322..02df03fff 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -203,8 +203,6 @@ func (ec *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (* if err == nil { if r == nil { return nil, ethereum.NotFound - } else if len(r.PostState) == 0 { - return nil, fmt.Errorf("server returned receipt without post state") } } return r, err diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index c22c56dfb..1b23ac559 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -17,7 +17,6 @@ package ethapi import ( - "bytes" "context" "errors" "fmt" @@ -35,7 +34,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/params" @@ -769,7 +767,7 @@ func (s *PublicBlockChainAPI) rpcOutputBlock(b *types.Block, inclTx bool, fullTx if fullTx { formatTx = func(tx *types.Transaction) (interface{}, error) { - return newRPCTransaction(b, tx.Hash()) + return newRPCTransactionFromBlockHash(b, tx.Hash()), nil } } @@ -812,15 +810,17 @@ type RPCTransaction struct { S *hexutil.Big `json:"s"` } -// newRPCPendingTransaction returns a pending transaction that will serialize to the RPC representation -func newRPCPendingTransaction(tx *types.Transaction) *RPCTransaction { +// newRPCTransaction returns a transaction that will serialize to the RPC +// representation, with the given location metadata set (if available). +func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransaction { var signer types.Signer = types.FrontierSigner{} if tx.Protected() { signer = types.NewEIP155Signer(tx.ChainId()) } from, _ := types.Sender(signer, tx) v, r, s := tx.RawSignatureValues() - return &RPCTransaction{ + + result := &RPCTransaction{ From: from, Gas: (*hexutil.Big)(tx.Gas()), GasPrice: (*hexutil.Big)(tx.GasPrice()), @@ -833,58 +833,46 @@ func newRPCPendingTransaction(tx *types.Transaction) *RPCTransaction { R: (*hexutil.Big)(r), S: (*hexutil.Big)(s), } + if blockHash != (common.Hash{}) { + result.BlockHash = blockHash + result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) + result.TransactionIndex = hexutil.Uint(index) + } + return result } -// newRPCTransaction returns a transaction that will serialize to the RPC representation. -func newRPCTransactionFromBlockIndex(b *types.Block, txIndex uint) (*RPCTransaction, error) { - if txIndex < uint(len(b.Transactions())) { - tx := b.Transactions()[txIndex] - var signer types.Signer = types.FrontierSigner{} - if tx.Protected() { - signer = types.NewEIP155Signer(tx.ChainId()) - } - from, _ := types.Sender(signer, tx) - v, r, s := tx.RawSignatureValues() - return &RPCTransaction{ - BlockHash: b.Hash(), - BlockNumber: (*hexutil.Big)(b.Number()), - From: from, - Gas: (*hexutil.Big)(tx.Gas()), - GasPrice: (*hexutil.Big)(tx.GasPrice()), - Hash: tx.Hash(), - Input: hexutil.Bytes(tx.Data()), - Nonce: hexutil.Uint64(tx.Nonce()), - To: tx.To(), - TransactionIndex: hexutil.Uint(txIndex), - Value: (*hexutil.Big)(tx.Value()), - V: (*hexutil.Big)(v), - R: (*hexutil.Big)(r), - S: (*hexutil.Big)(s), - }, nil - } - - return nil, nil +// newRPCPendingTransaction returns a pending transaction that will serialize to the RPC representation +func newRPCPendingTransaction(tx *types.Transaction) *RPCTransaction { + return newRPCTransaction(tx, common.Hash{}, 0, 0) } -// newRPCRawTransactionFromBlockIndex returns the bytes of a transaction given a block and a transaction index. -func newRPCRawTransactionFromBlockIndex(b *types.Block, txIndex uint) (hexutil.Bytes, error) { - if txIndex < uint(len(b.Transactions())) { - tx := b.Transactions()[txIndex] - return rlp.EncodeToBytes(tx) +// newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation. +func newRPCTransactionFromBlockIndex(b *types.Block, index uint64) *RPCTransaction { + txs := b.Transactions() + if index >= uint64(len(txs)) { + return nil } + return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), index) +} - return nil, nil +// newRPCRawTransactionFromBlockIndex returns the bytes of a transaction given a block and a transaction index. +func newRPCRawTransactionFromBlockIndex(b *types.Block, index uint64) hexutil.Bytes { + txs := b.Transactions() + if index >= uint64(len(txs)) { + return nil + } + blob, _ := rlp.EncodeToBytes(txs[index]) + return blob } -// newRPCTransaction returns a transaction that will serialize to the RPC representation. -func newRPCTransaction(b *types.Block, txHash common.Hash) (*RPCTransaction, error) { +// newRPCTransactionFromBlockHash returns a transaction that will serialize to the RPC representation. +func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransaction { for idx, tx := range b.Transactions() { - if tx.Hash() == txHash { - return newRPCTransactionFromBlockIndex(b, uint(idx)) + if tx.Hash() == hash { + return newRPCTransactionFromBlockIndex(b, uint64(idx)) } } - - return nil, nil + return nil } // PublicTransactionPoolAPI exposes methods for the RPC interface @@ -898,24 +886,6 @@ func NewPublicTransactionPoolAPI(b Backend, nonceLock *AddrLocker) *PublicTransa return &PublicTransactionPoolAPI{b, nonceLock} } -func getTransaction(chainDb ethdb.Database, b Backend, txHash common.Hash) (*types.Transaction, bool, error) { - txData, err := chainDb.Get(txHash.Bytes()) - isPending := false - tx := new(types.Transaction) - - if err == nil && len(txData) > 0 { - if err := rlp.DecodeBytes(txData, tx); err != nil { - return nil, isPending, err - } - } else { - // pending transaction? - tx = b.GetPoolTransaction(txHash) - isPending = true - } - - return tx, isPending, nil -} - // GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number. func (s *PublicTransactionPoolAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint { if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil { @@ -935,35 +905,35 @@ func (s *PublicTransactionPoolAPI) GetBlockTransactionCountByHash(ctx context.Co } // GetTransactionByBlockNumberAndIndex returns the transaction for the given block number and index. -func (s *PublicTransactionPoolAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) (*RPCTransaction, error) { +func (s *PublicTransactionPoolAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) *RPCTransaction { if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil { - return newRPCTransactionFromBlockIndex(block, uint(index)) + return newRPCTransactionFromBlockIndex(block, uint64(index)) } - return nil, nil + return nil } // GetTransactionByBlockHashAndIndex returns the transaction for the given block hash and index. -func (s *PublicTransactionPoolAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) (*RPCTransaction, error) { +func (s *PublicTransactionPoolAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) *RPCTransaction { if block, _ := s.b.GetBlock(ctx, blockHash); block != nil { - return newRPCTransactionFromBlockIndex(block, uint(index)) + return newRPCTransactionFromBlockIndex(block, uint64(index)) } - return nil, nil + return nil } // GetRawTransactionByBlockNumberAndIndex returns the bytes of the transaction for the given block number and index. -func (s *PublicTransactionPoolAPI) GetRawTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) (hexutil.Bytes, error) { +func (s *PublicTransactionPoolAPI) GetRawTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) hexutil.Bytes { if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil { - return newRPCRawTransactionFromBlockIndex(block, uint(index)) + return newRPCRawTransactionFromBlockIndex(block, uint64(index)) } - return nil, nil + return nil } // GetRawTransactionByBlockHashAndIndex returns the bytes of the transaction for the given block hash and index. -func (s *PublicTransactionPoolAPI) GetRawTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) (hexutil.Bytes, error) { +func (s *PublicTransactionPoolAPI) GetRawTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) hexutil.Bytes { if block, _ := s.b.GetBlock(ctx, blockHash); block != nil { - return newRPCRawTransactionFromBlockIndex(block, uint(index)) + return newRPCRawTransactionFromBlockIndex(block, uint64(index)) } - return nil, nil + return nil } // GetTransactionCount returns the number of transactions the given address has sent for the given block number @@ -976,90 +946,42 @@ func (s *PublicTransactionPoolAPI) GetTransactionCount(ctx context.Context, addr return (*hexutil.Uint64)(&nonce), state.Error() } -// getTransactionBlockData fetches the meta data for the given transaction from the chain database. This is useful to -// retrieve block information for a hash. It returns the block hash, block index and transaction index. -func getTransactionBlockData(chainDb ethdb.Database, txHash common.Hash) (common.Hash, uint64, uint64, error) { - var txBlock struct { - BlockHash common.Hash - BlockIndex uint64 - Index uint64 - } - - blockData, err := chainDb.Get(append(txHash.Bytes(), 0x0001)) - if err != nil { - return common.Hash{}, uint64(0), uint64(0), err - } - - reader := bytes.NewReader(blockData) - if err = rlp.Decode(reader, &txBlock); err != nil { - return common.Hash{}, uint64(0), uint64(0), err - } - - return txBlock.BlockHash, txBlock.BlockIndex, txBlock.Index, nil -} - // GetTransactionByHash returns the transaction for the given hash -func (s *PublicTransactionPoolAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) { - var tx *types.Transaction - var isPending bool - var err error - - if tx, isPending, err = getTransaction(s.b.ChainDb(), s.b, hash); err != nil { - log.Debug("Failed to retrieve transaction", "hash", hash, "err", err) - return nil, nil - } else if tx == nil { - return nil, nil +func (s *PublicTransactionPoolAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) *RPCTransaction { + // Try to return an already finalized transaction + if tx, blockHash, blockNumber, index := core.GetTransaction(s.b.ChainDb(), hash); tx != nil { + return newRPCTransaction(tx, blockHash, blockNumber, index) } - if isPending { - return newRPCPendingTransaction(tx), nil - } - - blockHash, _, _, err := getTransactionBlockData(s.b.ChainDb(), hash) - if err != nil { - log.Debug("Failed to retrieve transaction block", "hash", hash, "err", err) - return nil, nil + // No finalized transaction, try to retrieve it from the pool + if tx := s.b.GetPoolTransaction(hash); tx != nil { + return newRPCPendingTransaction(tx) } - - if block, _ := s.b.GetBlock(ctx, blockHash); block != nil { - return newRPCTransaction(block, hash) - } - return nil, nil + // Transaction unknown, return as such + return nil } // GetRawTransactionByHash returns the bytes of the transaction for the given hash. func (s *PublicTransactionPoolAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) { var tx *types.Transaction - var err error - if tx, _, err = getTransaction(s.b.ChainDb(), s.b, hash); err != nil { - log.Debug("Failed to retrieve transaction", "hash", hash, "err", err) - return nil, nil - } else if tx == nil { - return nil, nil + // Retrieve a finalized transaction, or a pooled otherwise + if tx, _, _, _ = core.GetTransaction(s.b.ChainDb(), hash); tx == nil { + if tx = s.b.GetPoolTransaction(hash); tx == nil { + // Transaction not found anywhere, abort + return nil, nil + } } - + // Serialize to RLP and return return rlp.EncodeToBytes(tx) } // GetTransactionReceipt returns the transaction receipt for the given transaction hash. func (s *PublicTransactionPoolAPI) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) { - receipt := core.GetReceipt(s.b.ChainDb(), hash) - if receipt == nil { - log.Debug("Receipt not found for transaction", "hash", hash) - return nil, nil - } - - tx, _, err := getTransaction(s.b.ChainDb(), s.b, hash) - if err != nil { - log.Debug("Failed to retrieve transaction", "hash", hash, "err", err) - return nil, nil - } - - txBlock, blockIndex, index, err := getTransactionBlockData(s.b.ChainDb(), hash) - if err != nil { - log.Debug("Failed to retrieve transaction block", "hash", hash, "err", err) + tx, blockHash, blockNumber, index := core.GetTransaction(s.b.ChainDb(), hash) + if tx == nil { return nil, nil } + receipt, _, _, _ := core.GetReceipt(s.b.ChainDb(), hash) // Old receipts don't have the lookup data available var signer types.Signer = types.FrontierSigner{} if tx.Protected() { @@ -1069,8 +991,8 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(hash common.Hash) (map[ fields := map[string]interface{}{ "root": hexutil.Bytes(receipt.PostState), - "blockHash": txBlock, - "blockNumber": hexutil.Uint64(blockIndex), + "blockHash": blockHash, + "blockNumber": hexutil.Uint64(blockNumber), "transactionHash": hash, "transactionIndex": hexutil.Uint64(index), "from": from, diff --git a/light/txpool.go b/light/txpool.go index 0430b280f..416148b7e 100644 --- a/light/txpool.go +++ b/light/txpool.go @@ -130,19 +130,6 @@ type txBlockData struct { Index uint64 } -// storeTxBlockData stores the block position of a mined tx in the local db -func (pool *TxPool) storeTxBlockData(txh common.Hash, tbd txBlockData) { - //fmt.Println("storeTxBlockData", txh, tbd) - data, _ := rlp.EncodeToBytes(tbd) - pool.chainDb.Put(append(txh[:], byte(1)), data) -} - -// removeTxBlockData removes the stored block position of a rolled back tx -func (pool *TxPool) removeTxBlockData(txh common.Hash) { - //fmt.Println("removeTxBlockData", txh) - pool.chainDb.Delete(append(txh[:], byte(1))) -} - // txStateChanges stores the recent changes between pending/mined states of // transactions. True means mined, false means rolled back, no entry means no change type txStateChanges map[common.Hash]bool @@ -172,59 +159,48 @@ func (txc txStateChanges) getLists() (mined []common.Hash, rollback []common.Has // checkMinedTxs checks newly added blocks for the currently pending transactions // and marks them as mined if necessary. It also stores block position in the db // and adds them to the received txStateChanges map. -func (pool *TxPool) checkMinedTxs(ctx context.Context, hash common.Hash, idx uint64, txc txStateChanges) error { - //fmt.Println("checkMinedTxs") +func (pool *TxPool) checkMinedTxs(ctx context.Context, hash common.Hash, number uint64, txc txStateChanges) error { + // If no transactions are pending, we don't care about anything if len(pool.pending) == 0 { return nil } - //fmt.Println("len(pool) =", len(pool.pending)) - - block, err := GetBlock(ctx, pool.odr, hash, idx) - var receipts types.Receipts + block, err := GetBlock(ctx, pool.odr, hash, number) if err != nil { - //fmt.Println(err) return err } - //fmt.Println("len(block.Transactions()) =", len(block.Transactions())) - + // Gather all the local transaction mined in this block list := pool.mined[hash] - for i, tx := range block.Transactions() { - txHash := tx.Hash() - //fmt.Println(" txHash:", txHash) - if tx, ok := pool.pending[txHash]; ok { - //fmt.Println("TX FOUND") - if receipts == nil { - receipts, err = GetBlockReceipts(ctx, pool.odr, hash, idx) - if err != nil { - return err - } - if len(receipts) != len(block.Transactions()) { - panic(nil) // should never happen if hashes did match - } - core.SetReceiptsData(pool.config, block, receipts) - } - //fmt.Println("WriteReceipt", receipts[i].TxHash) - core.WriteReceipt(pool.chainDb, receipts[i]) - pool.storeTxBlockData(txHash, txBlockData{hash, idx, uint64(i)}) - delete(pool.pending, txHash) + for _, tx := range block.Transactions() { + if _, ok := pool.pending[tx.Hash()]; ok { list = append(list, tx) - txc.setState(txHash, true) } } + // If some transactions have been mined, write the needed data to disk and update if list != nil { + // Retrieve all the receipts belonging to this block and write the loopup table + if _, err := GetBlockReceipts(ctx, pool.odr, hash, number); err != nil { // ODR caches, ignore results + return err + } + if err := core.WriteTxLookupEntries(pool.chainDb, block); err != nil { + return err + } + // Update the transaction pool's state + for _, tx := range list { + delete(pool.pending, tx.Hash()) + txc.setState(tx.Hash(), true) + } pool.mined[hash] = list } return nil } // rollbackTxs marks the transactions contained in recently rolled back blocks -// as rolled back. It also removes block position info from the db and adds them -// to the received txStateChanges map. +// as rolled back. It also removes any positional lookup entries. func (pool *TxPool) rollbackTxs(hash common.Hash, txc txStateChanges) { if list, ok := pool.mined[hash]; ok { for _, tx := range list { txHash := tx.Hash() - pool.removeTxBlockData(txHash) + core.DeleteTxLookupEntry(pool.chainDb, txHash) pool.pending[txHash] = tx txc.setState(txHash, false) } diff --git a/miner/worker.go b/miner/worker.go index e44514755..411bc4e1b 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -293,9 +293,7 @@ func (self *worker) wait() { // check if canon block and write transactions if stat == core.CanonStatTy { // This puts transactions in a extra db for rpc - core.WriteTransactions(self.chainDb, block) - // store the receipts - core.WriteReceipts(self.chainDb, work.receipts) + core.WriteTxLookupEntries(self.chainDb, block) // Write map map bloom filters core.WriteMipmapBloom(self.chainDb, block.NumberU64(), work.receipts) // implicit by posting ChainHeadEvent diff --git a/params/version.go b/params/version.go index 3dac93d89..089666260 100644 --- a/params/version.go +++ b/params/version.go @@ -22,8 +22,8 @@ import ( const ( VersionMajor = 1 // Major version component of the current release - VersionMinor = 6 // Minor version component of the current release - VersionPatch = 8 // Patch version component of the current release + VersionMinor = 7 // Minor version component of the current release + VersionPatch = 0 // Patch version component of the current release VersionMeta = "unstable" // Version metadata to append to the version string ) |