From 28aea46ac08579f3ecd1c35620915b8e1bfcc8b0 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Mon, 21 Aug 2017 08:47:15 +0800 Subject: [PATCH] core: implement Metropolis EIP 658, receipt status byte --- accounts/abi/bind/backends/simulated.go | 3 +- common/bytes.go | 3 ++ core/database_util_test.go | 4 +-- core/state_processor.go | 4 +-- core/state_transition.go | 19 +++++------ core/types/gen_receipt_json.go | 6 ++++ core/types/receipt.go | 44 ++++++++++++++++--------- core/vm/evm.go | 6 +++- core/vm/instructions.go | 2 +- eth/api.go | 5 +-- eth/backend_test.go | 4 +-- eth/filters/filter_test.go | 10 +++--- internal/ethapi/api.go | 3 +- les/odr_test.go | 4 +-- light/odr_test.go | 2 +- tests/state_test_util.go | 2 +- 16 files changed, 75 insertions(+), 46 deletions(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index e0ee06a0a..88fb3331e 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -253,7 +253,8 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM // about the transaction and calling mechanisms. vmenv := vm.NewEVM(evmContext, statedb, b.config, vm.Config{}) gaspool := new(core.GasPool).AddGas(math.MaxBig256) - ret, gasUsed, _, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb() + // TODO utilize returned failed flag to help gas estimation. + ret, gasUsed, _, _, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb() return ret, gasUsed, err } diff --git a/common/bytes.go b/common/bytes.go index c445968f2..66577bbfd 100644 --- a/common/bytes.go +++ b/common/bytes.go @@ -47,6 +47,9 @@ func FromHex(s string) []byte { // // Returns an exact copy of the provided bytes func CopyBytes(b []byte) (copiedBytes []byte) { + if b == nil { + return nil + } copiedBytes = make([]byte, len(b)) copy(copiedBytes, b) diff --git a/core/database_util_test.go b/core/database_util_test.go index e9a6df97b..5c75d53d0 100644 --- a/core/database_util_test.go +++ b/core/database_util_test.go @@ -461,12 +461,12 @@ func TestMipmapChain(t *testing.T) { var receipts types.Receipts switch i { case 1: - receipt := types.NewReceipt(nil, new(big.Int)) + receipt := types.NewReceipt(nil, false, new(big.Int)) receipt.Logs = []*types.Log{{Address: addr, Topics: []common.Hash{hash1}}} gen.AddUncheckedReceipt(receipt) receipts = types.Receipts{receipt} case 1000: - receipt := types.NewReceipt(nil, new(big.Int)) + receipt := types.NewReceipt(nil, false, new(big.Int)) receipt.Logs = []*types.Log{{Address: addr2}} gen.AddUncheckedReceipt(receipt) receipts = types.Receipts{receipt} diff --git a/core/state_processor.go b/core/state_processor.go index 4489cfce2..a4b554b10 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -98,7 +98,7 @@ func ApplyTransaction(config *params.ChainConfig, bc *BlockChain, author *common // about the transaction and calling mechanisms. vmenv := vm.NewEVM(context, statedb, config, cfg) // Apply the transaction to the current state (included in the env) - _, gas, err := ApplyMessage(vmenv, msg, gp) + _, gas, failed, err := ApplyMessage(vmenv, msg, gp) if err != nil { return nil, nil, err } @@ -114,7 +114,7 @@ func ApplyTransaction(config *params.ChainConfig, bc *BlockChain, author *common // 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. - receipt := types.NewReceipt(root, usedGas) + receipt := types.NewReceipt(root, failed, 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/state_transition.go b/core/state_transition.go index 0ae9d7fcb..bab4540be 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -59,8 +59,7 @@ type StateTransition struct { value *big.Int data []byte state vm.StateDB - - evm *vm.EVM + evm *vm.EVM } // Message represents a message sent to a contract. @@ -127,11 +126,11 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition // the gas used (which includes gas refunds) and an error if it failed. An error always // indicates a core error meaning that the message would always fail for that particular // state and would never be accepted within a block. -func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, *big.Int, error) { +func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, *big.Int, bool, error) { st := NewStateTransition(evm, msg, gp) - ret, _, gasUsed, err := st.TransitionDb() - return ret, gasUsed, err + ret, _, gasUsed, failed, err := st.TransitionDb() + return ret, gasUsed, failed, err } func (st *StateTransition) from() vm.AccountRef { @@ -208,7 +207,7 @@ func (st *StateTransition) preCheck() error { // TransitionDb will transition the state by applying the current message and returning the result // including the required gas for the operation as well as the used gas. It returns an error if it // failed. An error indicates a consensus issue. -func (st *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *big.Int, err error) { +func (st *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *big.Int, failed bool, err error) { if err = st.preCheck(); err != nil { return } @@ -222,10 +221,10 @@ func (st *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *big // TODO convert to uint64 intrinsicGas := IntrinsicGas(st.data, contractCreation, homestead) if intrinsicGas.BitLen() > 64 { - return nil, nil, nil, vm.ErrOutOfGas + return nil, nil, nil, false, vm.ErrOutOfGas } if err = st.useGas(intrinsicGas.Uint64()); err != nil { - return nil, nil, nil, err + return nil, nil, nil, false, err } var ( @@ -248,7 +247,7 @@ func (st *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *big // sufficient balance to make the transfer happen. The first // balance transfer may never fail. if vmerr == vm.ErrInsufficientBalance { - return nil, nil, nil, vmerr + return nil, nil, nil, false, vmerr } } requiredGas = new(big.Int).Set(st.gasUsed()) @@ -256,7 +255,7 @@ func (st *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *big st.refundGas() st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(st.gasUsed(), st.gasPrice)) - return ret, requiredGas, st.gasUsed(), err + return ret, requiredGas, st.gasUsed(), vmerr != nil, err } func (st *StateTransition) refundGas() { diff --git a/core/types/gen_receipt_json.go b/core/types/gen_receipt_json.go index eb2e5d42b..1e6880c22 100644 --- a/core/types/gen_receipt_json.go +++ b/core/types/gen_receipt_json.go @@ -14,6 +14,7 @@ import ( func (r Receipt) MarshalJSON() ([]byte, error) { type Receipt struct { PostState hexutil.Bytes `json:"root"` + Failed bool `json:"failed"` CumulativeGasUsed *hexutil.Big `json:"cumulativeGasUsed" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"` Logs []*Log `json:"logs" gencodec:"required"` @@ -23,6 +24,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) { } var enc Receipt enc.PostState = r.PostState + enc.Failed = r.Failed enc.CumulativeGasUsed = (*hexutil.Big)(r.CumulativeGasUsed) enc.Bloom = r.Bloom enc.Logs = r.Logs @@ -35,6 +37,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) { func (r *Receipt) UnmarshalJSON(input []byte) error { type Receipt struct { PostState hexutil.Bytes `json:"root"` + Failed *bool `json:"failed"` CumulativeGasUsed *hexutil.Big `json:"cumulativeGasUsed" gencodec:"required"` Bloom *Bloom `json:"logsBloom" gencodec:"required"` Logs []*Log `json:"logs" gencodec:"required"` @@ -49,6 +52,9 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { if dec.PostState != nil { r.PostState = dec.PostState } + if dec.Failed != nil { + r.Failed = *dec.Failed + } 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 c9906b015..9c49648b4 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -28,10 +28,16 @@ import ( //go:generate gencodec -type Receipt -field-override receiptMarshaling -out gen_receipt_json.go +const ( + receiptStatusSuccessful = byte(0x01) + receiptStatusFailed = byte(0x00) +) + // Receipt represents the results of a transaction. type Receipt struct { // Consensus fields PostState []byte `json:"root"` + Failed bool `json:"failed"` CumulativeGasUsed *big.Int `json:"cumulativeGasUsed" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"` Logs []*Log `json:"logs" gencodec:"required"` @@ -60,21 +66,26 @@ type homesteadReceiptRLP struct { // metropolisReceiptRLP contains the receipt's Metropolis consensus fields, used // during RLP serialization. type metropolisReceiptRLP struct { + Status byte 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)} +func NewReceipt(root []byte, failed bool, cumulativeGasUsed *big.Int) *Receipt { + return &Receipt{PostState: common.CopyBytes(root), Failed: failed, CumulativeGasUsed: new(big.Int).Set(cumulativeGasUsed)} } // EncodeRLP implements rlp.Encoder, and flattens the consensus fields of a receipt // into an RLP stream. If no post state is present, metropolis fork is assumed. func (r *Receipt) EncodeRLP(w io.Writer) error { if r.PostState == nil { - return rlp.Encode(w, &metropolisReceiptRLP{r.CumulativeGasUsed, r.Bloom, r.Logs}) + status := receiptStatusSuccessful + if r.Failed { + status = receiptStatusFailed + } + return rlp.Encode(w, &metropolisReceiptRLP{status, r.CumulativeGasUsed, r.Bloom, r.Logs}) } return rlp.Encode(w, &homesteadReceiptRLP{r.PostState, r.CumulativeGasUsed, r.Bloom, r.Logs}) } @@ -87,29 +98,31 @@ func (r *Receipt) DecodeRLP(s *rlp.Stream) error { if err != nil { return err } - list, _, err := rlp.SplitList(raw) + content, _, err := rlp.SplitList(raw) if err != nil { return err } - items, err := rlp.CountValues(list) + kind, cnt, _, err := rlp.Split(content) if err != nil { return err } - // Deserialize based on the number of content items - switch items { - case 3: - // Metropolis receipts have 3 components + // Deserialize based on the first component type. + switch { + case kind == rlp.Byte || kind == rlp.String && len(cnt) == 0: + // The first component of metropolis receipts is Byte + // or empty String(byte with 0x00 value). var metro metropolisReceiptRLP if err := rlp.DecodeBytes(raw, &metro); err != nil { return err } + r.Failed = metro.Status == receiptStatusFailed r.CumulativeGasUsed = metro.CumulativeGasUsed r.Bloom = metro.Bloom r.Logs = metro.Logs return nil - case 4: - // Homestead receipts have 4 components + case kind == rlp.String: + // The first component of homestead receipts is non-empty String. var home homesteadReceiptRLP if err := rlp.DecodeBytes(raw, &home); err != nil { return err @@ -121,14 +134,14 @@ func (r *Receipt) DecodeRLP(s *rlp.Stream) error { return nil default: - return fmt.Errorf("invalid receipt components: %v", items) + return fmt.Errorf("invalid first receipt component: %v", kind) } } // 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{failed=%t cgas=%v bloom=%x logs=%v}", r.Failed, 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) } @@ -144,7 +157,7 @@ func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error { for i, log := range r.Logs { logs[i] = (*LogForStorage)(log) } - return rlp.Encode(w, []interface{}{r.PostState, r.CumulativeGasUsed, r.Bloom, r.TxHash, r.ContractAddress, logs, r.GasUsed}) + return rlp.Encode(w, []interface{}{r.PostState, r.Failed, r.CumulativeGasUsed, r.Bloom, r.TxHash, r.ContractAddress, logs, r.GasUsed}) } // DecodeRLP implements rlp.Decoder, and loads both consensus and implementation @@ -152,6 +165,7 @@ func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error { func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error { var receipt struct { PostState []byte + Failed bool CumulativeGasUsed *big.Int Bloom Bloom TxHash common.Hash @@ -163,7 +177,7 @@ func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error { return err } // Assign the consensus fields - r.PostState, r.CumulativeGasUsed, r.Bloom = receipt.PostState, receipt.CumulativeGasUsed, receipt.Bloom + r.PostState, r.Failed, r.CumulativeGasUsed, r.Bloom = receipt.PostState, receipt.Failed, receipt.CumulativeGasUsed, receipt.Bloom r.Logs = make([]*Log, len(receipt.Logs)) for i, log := range receipt.Logs { r.Logs[i] = (*Log)(log) diff --git a/core/vm/evm.go b/core/vm/evm.go index 8d654c666..34933a1dc 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -158,7 +158,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value) // initialise a new contract and set the code that is to be used by the - // E The contract is a scoped evmironment for this execution context + // E The contract is a scoped environment for this execution context // only. contract := NewContract(caller, to, value, gas) contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) @@ -351,6 +351,10 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I contract.UseGas(contract.Gas) } } + // Assign err if contract code size exceeds the max while the err is still empty. + if maxCodeSizeExceeded && err == nil { + err = errMaxCodeSizeExceeded + } return ret, contractAddr, contract.Gas, err } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index ece4d2229..b6d6e22c4 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -33,6 +33,7 @@ var ( errWriteProtection = errors.New("evm: write protection") errReturnDataOutOfBounds = errors.New("evm: return data out of bounds") errExecutionReverted = errors.New("evm: execution reverted") + errMaxCodeSizeExceeded = errors.New("evm: max code size exceeded") ) func opAdd(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { @@ -619,7 +620,6 @@ func opCall(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Sta if value.Sign() != 0 { gas += params.CallStipend } - ret, returnGas, err := evm.Call(contract, address, args, gas, value) if err != nil { stack.push(new(big.Int)) diff --git a/eth/api.go b/eth/api.go index f5214fc37..9904c6f53 100644 --- a/eth/api.go +++ b/eth/api.go @@ -523,7 +523,8 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, txHash common. // Run the transaction with tracing enabled. vmenv := vm.NewEVM(context, statedb, api.config, vm.Config{Debug: true, Tracer: tracer}) - ret, gas, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())) + // TODO utilize failed flag + ret, gas, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())) if err != nil { return nil, fmt.Errorf("tracing failed: %v", err) } @@ -570,7 +571,7 @@ func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int) (co vmenv := vm.NewEVM(context, statedb, api.config, vm.Config{}) gp := new(core.GasPool).AddGas(tx.Gas()) - _, _, err := core.ApplyMessage(vmenv, msg, gp) + _, _, _, err := core.ApplyMessage(vmenv, msg, gp) if err != nil { return nil, vm.Context{}, nil, fmt.Errorf("tx %x failed: %v", tx.Hash(), err) } diff --git a/eth/backend_test.go b/eth/backend_test.go index 4351b24cf..1fd25e95a 100644 --- a/eth/backend_test.go +++ b/eth/backend_test.go @@ -35,11 +35,11 @@ func TestMipmapUpgrade(t *testing.T) { chain, receipts := core.GenerateChain(params.TestChainConfig, genesis, db, 10, func(i int, gen *core.BlockGen) { switch i { case 1: - receipt := types.NewReceipt(nil, new(big.Int)) + receipt := types.NewReceipt(nil, false, new(big.Int)) receipt.Logs = []*types.Log{{Address: addr}} gen.AddUncheckedReceipt(receipt) case 2: - receipt := types.NewReceipt(nil, new(big.Int)) + receipt := types.NewReceipt(nil, false, new(big.Int)) receipt.Logs = []*types.Log{{Address: addr}} gen.AddUncheckedReceipt(receipt) } diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go index 3244c04d7..cf508a218 100644 --- a/eth/filters/filter_test.go +++ b/eth/filters/filter_test.go @@ -33,7 +33,7 @@ import ( ) func makeReceipt(addr common.Address) *types.Receipt { - receipt := types.NewReceipt(nil, new(big.Int)) + receipt := types.NewReceipt(nil, false, new(big.Int)) receipt.Logs = []*types.Log{ {Address: addr}, } @@ -145,7 +145,7 @@ func TestFilters(t *testing.T) { var receipts types.Receipts switch i { case 1: - receipt := types.NewReceipt(nil, new(big.Int)) + receipt := types.NewReceipt(nil, false, new(big.Int)) receipt.Logs = []*types.Log{ { Address: addr, @@ -155,7 +155,7 @@ func TestFilters(t *testing.T) { gen.AddUncheckedReceipt(receipt) receipts = types.Receipts{receipt} case 2: - receipt := types.NewReceipt(nil, new(big.Int)) + receipt := types.NewReceipt(nil, false, new(big.Int)) receipt.Logs = []*types.Log{ { Address: addr, @@ -165,7 +165,7 @@ func TestFilters(t *testing.T) { gen.AddUncheckedReceipt(receipt) receipts = types.Receipts{receipt} case 998: - receipt := types.NewReceipt(nil, new(big.Int)) + receipt := types.NewReceipt(nil, false, new(big.Int)) receipt.Logs = []*types.Log{ { Address: addr, @@ -175,7 +175,7 @@ func TestFilters(t *testing.T) { gen.AddUncheckedReceipt(receipt) receipts = types.Receipts{receipt} case 999: - receipt := types.NewReceipt(nil, new(big.Int)) + receipt := types.NewReceipt(nil, false, new(big.Int)) receipt.Logs = []*types.Log{ { Address: addr, diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index c3924b93a..30710aaab 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -635,7 +635,8 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr // Setup the gas pool (also for unmetered requests) // and apply the message. gp := new(core.GasPool).AddGas(math.MaxBig256) - res, gas, err := core.ApplyMessage(evm, msg, gp) + // TODO utilize failed flag to help gas estimation + res, gas, _, err := core.ApplyMessage(evm, msg, gp) if err := vmError(); err != nil { return nil, common.Big0, err } diff --git a/les/odr_test.go b/les/odr_test.go index 3a0fd6738..f56c4036d 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -127,7 +127,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai //vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxBig256) - ret, _, _ := core.ApplyMessage(vmenv, msg, gp) + ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp) res = append(res, ret...) } } else { @@ -138,7 +138,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai context := core.NewEVMContext(msg, header, lc, nil) vmenv := vm.NewEVM(context, state, config, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxBig256) - ret, _, _ := core.ApplyMessage(vmenv, msg, gp) + ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp) if state.Error() == nil { res = append(res, ret...) } diff --git a/light/odr_test.go b/light/odr_test.go index bd1e976e8..c0c5438fd 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -180,7 +180,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain context := core.NewEVMContext(msg, header, chain, nil) vmenv := vm.NewEVM(context, st, config, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxBig256) - ret, _, _ := core.ApplyMessage(vmenv, msg, gp) + ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp) res = append(res, ret...) if st.Error() != nil { return res, st.Error() diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 2bf940bab..0eb85ab28 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -156,7 +156,7 @@ func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config) error { gaspool := new(core.GasPool) gaspool.AddGas(block.GasLimit()) snapshot := statedb.Snapshot() - if _, _, err := core.ApplyMessage(evm, msg, gaspool); err != nil { + if _, _, _, err := core.ApplyMessage(evm, msg, gaspool); err != nil { statedb.RevertToSnapshot(snapshot) } if post.Logs != nil {