Skip to content
This repository has been archived by the owner on Oct 4, 2019. It is now read-only.

Commit

Permalink
getTransactionReceipt - dynamic status generation
Browse files Browse the repository at this point in the history
  • Loading branch information
tzdybal committed Jul 18, 2018
1 parent 01ce9b3 commit 7a5e3f9
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 39 deletions.
4 changes: 2 additions & 2 deletions accounts/abi/bind/backends/simulated.go
Expand Up @@ -120,7 +120,7 @@ func (b *SimulatedBackend) ContractCall(contract common.Address, data []byte, pe
vmenv := core.NewEnv(statedb, core.DefaultConfigMorden.ChainConfig, b.blockchain, msg, block.Header())
gaspool := new(core.GasPool).AddGas(common.MaxBig)

out, _, err := core.ApplyMessage(vmenv, msg, gaspool)
out, _, _, err := core.ApplyMessage(vmenv, msg, gaspool)
return out, err
}

Expand Down Expand Up @@ -170,7 +170,7 @@ func (b *SimulatedBackend) EstimateGasLimit(sender common.Address, contract *com
vmenv := core.NewEnv(statedb, core.DefaultConfigMorden.ChainConfig, b.blockchain, msg, block.Header())
gaspool := new(core.GasPool).AddGas(common.MaxBig)

_, gas, _, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb()
_, gas, _, _, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb()
return gas, err
}

Expand Down
2 changes: 1 addition & 1 deletion common/registrar/ethreg/api.go
Expand Up @@ -209,7 +209,7 @@ func (be *registryAPIBackend) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr
header := be.bc.CurrentBlock().Header()
vmenv := core.NewEnv(statedb, be.config, be.bc, msg, header)
gp := new(core.GasPool).AddGas(common.MaxBig)
res, gas, err := core.ApplyMessage(vmenv, msg, gp)
res, gas, _, err := core.ApplyMessage(vmenv, msg, gp)

return common.ToHex(res), gas.String(), err
}
Expand Down
78 changes: 75 additions & 3 deletions core/state_processor.go
Expand Up @@ -17,17 +17,20 @@
package core

import (
"math/big"

"bytes"
"errors"
"fmt"
"math/big"

"github.com/ethereumproject/go-ethereum/common"
"github.com/ethereumproject/go-ethereum/core/state"
"github.com/ethereumproject/go-ethereum/core/types"
"github.com/ethereumproject/go-ethereum/core/vm"
"github.com/ethereumproject/go-ethereum/crypto"
"github.com/ethereumproject/go-ethereum/ethdb"
"github.com/ethereumproject/go-ethereum/logger"
"github.com/ethereumproject/go-ethereum/logger/glog"
"github.com/ethereumproject/go-ethereum/rlp"
)

var (
Expand Down Expand Up @@ -106,6 +109,70 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB) (ty
return receipts, allLogs, totalUsedGas, err
}

func (p *StateProcessor) ReplayTransaction(txHash common.Hash, statedb *state.StateDB) (*types.Receipt, error) {
statedb = statedb.Copy()

blockHash, _, index, err := getTransactionBlockData(p.bc.chainDb, txHash)
if err != nil {
return nil, err
}

block := GetBlock(p.bc.chainDb, blockHash)
tx := block.Transactions()[index]

var (
totalUsedGas = big.NewInt(0)
header = block.Header()
gp = new(GasPool).AddGas(block.GasLimit())
)

if tx.Protected() {
chainId := p.config.GetChainID()
if chainId.Cmp(new(big.Int)) == 0 {
return nil, fmt.Errorf("ChainID is not set for EIP-155 in chain configuration at block number: %v. \n Tx ChainID: %v", block.Number(), tx.ChainId())
}
if tx.ChainId() == nil || tx.ChainId().Cmp(chainId) != 0 {
return nil, fmt.Errorf("Invalid transaction chain id. Current chain id: %v tx chain id: %v", p.config.GetChainID(), tx.ChainId())
}
}
//statedb.StartRecord(tx.Hash(), block.Hash(), i)
if !UseSputnikVM {
receipt, _, _, err := ApplyTransaction(p.config, p.bc, gp, statedb, header, tx, totalUsedGas)
if err != nil {
return nil, err
}
return receipt, nil
}
receipt, _, _, err := ApplyMultiVmTransaction(p.config, p.bc, gp, statedb, header, tx, totalUsedGas)
if err != nil {
return nil, err
}
return receipt, nil
}

// TODO(tzdybal) - refactor (duplicate from eth/api.go)
// 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
}

// ApplyTransaction attempts to apply a transaction to the given state database
// and uses the input parameters for its environment.
//
Expand All @@ -114,7 +181,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB) (ty
func ApplyTransaction(config *ChainConfig, bc *BlockChain, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int) (*types.Receipt, vm.Logs, *big.Int, error) {
tx.SetSigner(config.GetSigner(header.Number))

_, gas, err := ApplyMessage(NewEnv(statedb, config, bc, tx, header), tx, gp)
_, gas, failed, err := ApplyMessage(NewEnv(statedb, config, bc, tx, header), tx, gp)
if err != nil {
return nil, nil, nil, err
}
Expand All @@ -132,6 +199,11 @@ func ApplyTransaction(config *ChainConfig, bc *BlockChain, gp *GasPool, statedb
logs := statedb.GetLogs(tx.Hash())
receipt.Logs = logs
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
if failed {
receipt.Status = types.TxFailure
} else {
receipt.Status = types.TxSuccess
}

glog.V(logger.Debug).Infoln(receipt)

Expand Down
37 changes: 16 additions & 21 deletions core/state_transition.go
Expand Up @@ -127,11 +127,11 @@ func NewStateTransition(env vm.Environment, msg Message, gp *GasPool) *StateTran
// 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(env vm.Environment, msg Message, gp *GasPool) ([]byte, *big.Int, error) {
func ApplyMessage(env vm.Environment, msg Message, gp *GasPool) ([]byte, *big.Int, bool, error) {
st := NewStateTransition(env, msg, gp)

ret, _, gasUsed, err := st.TransitionDb()
return ret, gasUsed, err
ret, _, gasUsed, failed, err := st.TransitionDb()
return ret, gasUsed, failed, err
}

func (self *StateTransition) from() (vm.Account, error) {
Expand Down Expand Up @@ -221,7 +221,7 @@ func (self *StateTransition) preCheck() (err error) {
}

// TransitionDb will move the state by applying the message against the given environment.
func (self *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *big.Int, err error) {
func (self *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *big.Int, failed bool, err error) {
if err = self.preCheck(); err != nil {
return
}
Expand All @@ -232,45 +232,40 @@ func (self *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *b
contractCreation := MessageCreatesContract(msg)
// Pay intrinsic gas
if err = self.useGas(IntrinsicGas(self.data, contractCreation, homestead)); err != nil {
return nil, nil, nil, InvalidTxError(err)
return nil, nil, nil, false, InvalidTxError(err)
}

vmenv := self.env
//var addr common.Address
var vmerr error
if contractCreation {
ret, _, err = vmenv.Create(sender, self.data, self.gas, self.gasPrice, self.value)
if homestead && err == vm.CodeStoreOutOfGasError {
ret, _, vmerr = vmenv.Create(sender, self.data, self.gas, self.gasPrice, self.value)
if homestead && vmerr == vm.CodeStoreOutOfGasError {
self.gas = big.NewInt(0)
}

if err != nil {
ret = nil
glog.V(logger.Core).Infoln("VM create err:", err)
if vmerr != nil {
glog.V(logger.Core).Infoln("VM create err:", vmerr)
}
} else {
// Increment the nonce for the next transaction
self.state.SetNonce(sender.Address(), self.state.GetNonce(sender.Address())+1)
ret, err = vmenv.Call(sender, self.to().Address(), self.data, self.gas, self.gasPrice, self.value)
if err != nil {
glog.V(logger.Core).Infoln("VM call err:", err)
ret, vmerr = vmenv.Call(sender, self.to().Address(), self.data, self.gas, self.gasPrice, self.value)
if vmerr != nil {
glog.V(logger.Core).Infoln("VM call err:", vmerr)
}
}

if err != nil && IsValueTransferErr(err) {
return nil, nil, nil, InvalidTxError(err)
}

// We aren't interested in errors here. Errors returned by the VM are non-consensus errors and therefor shouldn't bubble up
if err != nil {
err = nil
if vmerr != nil && IsValueTransferErr(vmerr) {
return nil, nil, nil, false, InvalidTxError(vmerr)
}

requiredGas = new(big.Int).Set(self.gasUsed())

self.refundGas()
self.state.AddBalance(self.env.Coinbase(), new(big.Int).Mul(self.gasUsed(), self.gasPrice))

return ret, requiredGas, self.gasUsed(), err
return ret, requiredGas, self.gasUsed(), vmerr != nil, err
}

func (self *StateTransition) refundGas() {
Expand Down
48 changes: 45 additions & 3 deletions core/types/receipt.go
Expand Up @@ -17,6 +17,7 @@
package types

import (
"bytes"
"fmt"
"io"
"math/big"
Expand All @@ -26,6 +27,14 @@ import (
"github.com/ethereumproject/go-ethereum/rlp"
)

type ReceiptStatus byte

const (
TxFailure ReceiptStatus = 0
TxSuccess ReceiptStatus = 1
TxStatusUnknown ReceiptStatus = 0xFF
)

// Receipt represents the results of a transaction.
type Receipt struct {
// Consensus fields
Expand All @@ -38,6 +47,7 @@ type Receipt struct {
TxHash common.Hash
ContractAddress common.Address
GasUsed *big.Int
Status ReceiptStatus
}

// NewReceipt creates a barebone transaction receipt, copying the init fields.
Expand Down Expand Up @@ -92,12 +102,21 @@ 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})
return rlp.Encode(w, []interface{}{r.PostState, r.CumulativeGasUsed, r.Bloom, r.TxHash, r.ContractAddress, logs, r.GasUsed, r.Status})
}

// 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
}
var receipt struct {
PostState []byte
CumulativeGasUsed *big.Int
Expand All @@ -106,18 +125,41 @@ func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error {
ContractAddress common.Address
Logs []*vm.LogForStorage
GasUsed *big.Int
Status ReceiptStatus
}
if err := s.Decode(&receipt); err != nil {
receipt.Status = TxStatusUnknown

raw, err := s.Raw()
if err != nil {
return err
}

if err := s.Decode(&receipt); err != nil {
//if !strings.HasPrefix(err.Error(), "rlp: too few elements for struct") {
// return err
//}
s.Reset(bytes.NewReader(raw), uint64(len(raw)))
if err := s.Decode(&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
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 = receipt.TxHash, receipt.ContractAddress, receipt.GasUsed
r.TxHash, r.ContractAddress, r.GasUsed, r.Status = receipt.TxHash, receipt.ContractAddress, receipt.GasUsed, receipt.Status

return nil
}
Expand Down

0 comments on commit 7a5e3f9

Please sign in to comment.