From 8c43b48dc9eb639bc75cadebc6892a418bd26570 Mon Sep 17 00:00:00 2001 From: Austin Abell Date: Mon, 17 Jun 2019 16:44:37 -0400 Subject: [PATCH] Receipt fixes and state trie clearing detail (#62) * receipt and state trie clearing changes * Fixed receipt storage and added backward compatible decoding --- core/block_validator.go | 2 +- core/chain_manager.go | 2 +- core/database_util_test.go | 11 +-- core/multivm_processor.go | 2 +- core/state_processor.go | 9 ++- core/types/receipt.go | 158 +++++++++++++++++++++++++++---------- miner/worker.go | 2 +- 7 files changed, 135 insertions(+), 51 deletions(-) diff --git a/core/block_validator.go b/core/block_validator.go index 080a24978..75f881db1 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -138,7 +138,7 @@ func (v *BlockValidator) ValidateState(block, parent *types.Block, statedb *stat } // Validate the state root against the received state root and throw // an error if they don't match. - if root := statedb.IntermediateRoot(false); header.Root != root { + if root := statedb.IntermediateRoot(v.config.IsAtlantis(header.Number)); header.Root != root { return fmt.Errorf("invalid merkle root: header=%x computed=%x", header.Root, root) } return nil diff --git a/core/chain_manager.go b/core/chain_manager.go index 35580b243..50629fa7d 100644 --- a/core/chain_manager.go +++ b/core/chain_manager.go @@ -263,7 +263,7 @@ func makeHeader(config *ChainConfig, parent *types.Block, state *state.StateDB) time = new(big.Int).Add(parent.Time(), big.NewInt(10)) // block time is fixed at 10 seconds } return &types.Header{ - Root: state.IntermediateRoot(false), + Root: state.IntermediateRoot(config.IsAtlantis(parent.Number())), ParentHash: parent.Hash(), Coinbase: parent.Coinbase(), Difficulty: CalcDifficulty(config, time.Uint64(), parent.Header()), diff --git a/core/database_util_test.go b/core/database_util_test.go index c24dee447..a148f3173 100644 --- a/core/database_util_test.go +++ b/core/database_util_test.go @@ -28,6 +28,8 @@ import ( "crypto/ecdsa" "encoding/binary" + "strings" + "github.com/eth-classic/go-ethereum/common" "github.com/eth-classic/go-ethereum/core/types" "github.com/eth-classic/go-ethereum/core/vm" @@ -35,7 +37,6 @@ import ( "github.com/eth-classic/go-ethereum/crypto/sha3" "github.com/eth-classic/go-ethereum/ethdb" "github.com/eth-classic/go-ethereum/rlp" - "strings" ) type diffTest struct { @@ -647,7 +648,7 @@ func TestReceiptStorage(t *testing.T) { db, _ := ethdb.NewMemDatabase() receipt1 := &types.Receipt{ - PostState: []byte{0x01}, + Status: types.TxFailure, CumulativeGasUsed: big.NewInt(1), Logs: vm.Logs{ &vm.Log{Address: common.BytesToAddress([]byte{0x11})}, @@ -658,7 +659,7 @@ func TestReceiptStorage(t *testing.T) { GasUsed: big.NewInt(111111), } receipt2 := &types.Receipt{ - PostState: []byte{0x02}, + PostState: []byte{}, CumulativeGasUsed: big.NewInt(2), Logs: vm.Logs{ &vm.Log{Address: common.BytesToAddress([]byte{0x22})}, @@ -706,7 +707,7 @@ func TestBlockReceiptStorage(t *testing.T) { db, _ := ethdb.NewMemDatabase() receipt1 := &types.Receipt{ - PostState: []byte{0x01}, + Status: types.TxFailure, CumulativeGasUsed: big.NewInt(1), Logs: vm.Logs{ &vm.Log{Address: common.BytesToAddress([]byte{0x11})}, @@ -717,7 +718,7 @@ func TestBlockReceiptStorage(t *testing.T) { GasUsed: big.NewInt(111111), } receipt2 := &types.Receipt{ - PostState: []byte{0x02}, + PostState: []byte{}, CumulativeGasUsed: big.NewInt(2), Logs: vm.Logs{ &vm.Log{Address: common.BytesToAddress([]byte{0x22})}, diff --git a/core/multivm_processor.go b/core/multivm_processor.go index 74ae0d485..ae9269fec 100644 --- a/core/multivm_processor.go +++ b/core/multivm_processor.go @@ -179,7 +179,7 @@ Loop: usedGas := vm.UsedGas() totalUsedGas.Add(totalUsedGas, usedGas) - receipt := types.NewReceipt(statedb.IntermediateRoot(false).Bytes(), totalUsedGas) + receipt := types.NewReceipt(statedb.IntermediateRoot(config.IsAtlantis(header.Number)).Bytes(), totalUsedGas) receipt.TxHash = tx.Hash() receipt.GasUsed = new(big.Int).Set(totalUsedGas) if vm.Failed() { diff --git a/core/state_processor.go b/core/state_processor.go index e3f837244..0fcfc6f67 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -119,8 +119,15 @@ func ApplyTransaction(config *ChainConfig, bc *BlockChain, gp *GasPool, statedb } // Update the state with pending changes + var root []byte + if config.IsAtlantis(header.Number) { + statedb.Finalise(true) + } else { + root = statedb.IntermediateRoot(config.IsAtlantis(header.Number)).Bytes() + } + usedGas.Add(usedGas, gas) - receipt := types.NewReceipt(statedb.IntermediateRoot(false).Bytes(), usedGas) + receipt := types.NewReceipt(root, usedGas) receipt.TxHash = tx.Hash() receipt.GasUsed = new(big.Int).Set(gas) if MessageCreatesContract(tx) { diff --git a/core/types/receipt.go b/core/types/receipt.go index 280acf220..f1bd1b189 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -17,6 +17,7 @@ package types import ( + "bytes" "fmt" "io" "math/big" @@ -26,6 +27,11 @@ import ( "github.com/eth-classic/go-ethereum/rlp" ) +var ( + receiptStatusFailedRLP = []byte{} + receiptStatusSuccessfulRLP = []byte{0x01} +) + type ReceiptStatus byte const ( @@ -49,6 +55,29 @@ type Receipt struct { Status ReceiptStatus } +// storedReceiptRLP is the storage encoding of a receipt. +type storedReceiptRLP struct { + PostStateOrStatus []byte + CumulativeGasUsed *big.Int + Bloom Bloom + TxHash common.Hash + ContractAddress common.Address + Logs []*vm.LogForStorage + GasUsed *big.Int +} + +// storedReceiptRLP is the storage encoding of a receipt. +type oldStoredReceiptRLP struct { + PostStateOrStatus []byte + CumulativeGasUsed *big.Int + Bloom Bloom + TxHash common.Hash + ContractAddress common.Address + Logs []*vm.LogForStorage + GasUsed *big.Int + Status ReceiptStatus +} + // 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), Status: TxStatusUnknown} @@ -57,14 +86,14 @@ func NewReceipt(root []byte, cumulativeGasUsed *big.Int) *Receipt { // EncodeRLP implements rlp.Encoder, and flattens the consensus fields of a receipt // into an RLP stream. func (r *Receipt) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, []interface{}{r.PostState, r.CumulativeGasUsed, r.Bloom, r.Logs}) + return rlp.Encode(w, []interface{}{r.statusEncoding(), 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 + PostStateOrStatus []byte CumulativeGasUsed *big.Int Bloom Bloom Logs vm.Logs @@ -72,7 +101,36 @@ func (r *Receipt) DecodeRLP(s *rlp.Stream) error { if err := s.Decode(&receipt); err != nil { return err } - r.PostState, r.CumulativeGasUsed, r.Bloom, r.Logs = receipt.PostState, receipt.CumulativeGasUsed, receipt.Bloom, receipt.Logs + + if err := r.setStatus(receipt.PostStateOrStatus); err != nil { + return err + } + + r.CumulativeGasUsed, r.Bloom, r.Logs = receipt.CumulativeGasUsed, receipt.Bloom, receipt.Logs + return nil +} + +func (r *Receipt) statusEncoding() []byte { + if len(r.PostState) == 0 { + if r.Status == TxFailure { + return receiptStatusFailedRLP + } + return receiptStatusSuccessfulRLP + } + return r.PostState +} + +func (r *Receipt) setStatus(postStateOrStatus []byte) error { + switch { + case bytes.Equal(postStateOrStatus, receiptStatusSuccessfulRLP): + r.Status = TxSuccess + case bytes.Equal(postStateOrStatus, receiptStatusFailedRLP): + r.Status = TxFailure + case len(postStateOrStatus) == len(common.Hash{}): + r.PostState = postStateOrStatus + default: + return fmt.Errorf("invalid receipt status %x", postStateOrStatus) + } return nil } @@ -101,60 +159,78 @@ func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error { for i, log := range r.Logs { logs[i] = (*vm.LogForStorage)(log) } - return rlp.Encode(w, []interface{}{r.PostState, r.CumulativeGasUsed, r.Bloom, r.TxHash, r.ContractAddress, logs, r.GasUsed, r.Status}) + receiptToStore := &storedReceiptRLP{ + PostStateOrStatus: (*Receipt)(r).statusEncoding(), + CumulativeGasUsed: r.CumulativeGasUsed, + Logs: logs, + Bloom: r.Bloom, + TxHash: r.TxHash, + ContractAddress: r.ContractAddress, + GasUsed: r.GasUsed, + } + return rlp.Encode(w, receiptToStore) } // DecodeRLP implements rlp.Decoder, and loads both consensus and implementation // fields of a receipt from an RLP stream. func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error { - var oldReceipt struct { - PostState []byte - CumulativeGasUsed *big.Int - Bloom Bloom - TxHash common.Hash - ContractAddress common.Address - Logs []*vm.LogForStorage - GasUsed *big.Int + raw, err := s.Raw() + if err != nil { + return err } - var receipt struct { - PostState []byte - CumulativeGasUsed *big.Int - Bloom Bloom - TxHash common.Hash - ContractAddress common.Address - Logs []*vm.LogForStorage - GasUsed *big.Int - Status ReceiptStatus + + // Try decoding the receipt without Status first + if err := decodeStoredReceiptRLP(r, raw); err == nil { + return nil } - receipt.Status = TxStatusUnknown - raw, err := s.Raw() - if err != nil { + return decodeOldStoredReceiptRLP(r, raw) +} + +// Decode stored receipt +func decodeStoredReceiptRLP(r *ReceiptForStorage, raw []byte) error { + var receipt storedReceiptRLP + if err := rlp.DecodeBytes(raw, &receipt); err != nil { return err } + r.CumulativeGasUsed = receipt.CumulativeGasUsed + r.Bloom = receipt.Bloom + r.TxHash = receipt.TxHash + r.ContractAddress = receipt.ContractAddress + r.GasUsed = receipt.GasUsed + + r.Logs = make(vm.Logs, len(receipt.Logs)) + for i, log := range receipt.Logs { + r.Logs[i] = (*vm.Log)(log) + } + + if err := (*Receipt)(r).setStatus(receipt.PostStateOrStatus); err != nil { + return err + } + + return nil +} + +// Decode with status field included in storage +func decodeOldStoredReceiptRLP(r *ReceiptForStorage, raw []byte) error { + var receipt oldStoredReceiptRLP if err := rlp.DecodeBytes(raw, &receipt); err != nil { - if err := rlp.DecodeBytes(raw, &oldReceipt); err != nil { - return err - } - receipt.PostState = oldReceipt.PostState - receipt.CumulativeGasUsed = oldReceipt.CumulativeGasUsed - receipt.Bloom = oldReceipt.Bloom - receipt.TxHash = oldReceipt.TxHash - receipt.ContractAddress = oldReceipt.ContractAddress - receipt.Logs = oldReceipt.Logs - receipt.GasUsed = oldReceipt.GasUsed - receipt.Status = TxStatusUnknown - - } - // Assign the consensus fields - r.PostState, r.CumulativeGasUsed, r.Bloom = receipt.PostState, receipt.CumulativeGasUsed, receipt.Bloom + return err + } + + r.PostState = receipt.PostStateOrStatus + r.Status = receipt.Status + r.CumulativeGasUsed = receipt.CumulativeGasUsed + r.Bloom = receipt.Bloom + r.TxHash = receipt.TxHash + r.ContractAddress = receipt.ContractAddress + r.GasUsed = receipt.GasUsed + r.Logs = make(vm.Logs, len(receipt.Logs)) for i, log := range receipt.Logs { r.Logs[i] = (*vm.Log)(log) } - // Assign the implementation fields - r.TxHash, r.ContractAddress, r.GasUsed, r.Status = receipt.TxHash, receipt.ContractAddress, receipt.GasUsed, receipt.Status return nil } diff --git a/miner/worker.go b/miner/worker.go index 3946b9862..d4d360855 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -556,7 +556,7 @@ func (self *worker) commitNewWork() { if atomic.LoadInt32(&self.mining) == 1 { // commit state root after all state transitions. core.AccumulateRewards(work.config, work.state, header, uncles) - header.Root = work.state.IntermediateRoot(false) + header.Root = work.state.IntermediateRoot(self.config.IsAtlantis(header.Number)) } // create the new block whose nonce will be mined.