core/vm: adds refund as part of the json standard trace (#17910)

This adds the global accumulated refund counter to the standard
json output as a numeric json value. Previously this was not very
interesting since it was not used much, but with the new sstore
gas changes the value is a lot more interesting from a consensus
investigation perspective.
This commit is contained in:
Martin Holst Swende 2018-10-23 16:28:18 +02:00 committed by Felix Lange
parent 3088c122d8
commit 4c0883e20d
6 changed files with 79 additions and 51 deletions

View File

@ -52,6 +52,7 @@ func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cos
MemorySize: memory.Len(), MemorySize: memory.Len(),
Storage: nil, Storage: nil,
Depth: depth, Depth: depth,
RefundCounter: env.StateDB.GetRefund(),
Err: err, Err: err,
} }
if !l.cfg.DisableMemory { if !l.cfg.DisableMemory {

View File

@ -13,6 +13,7 @@ import (
var _ = (*structLogMarshaling)(nil) var _ = (*structLogMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (s StructLog) MarshalJSON() ([]byte, error) { func (s StructLog) MarshalJSON() ([]byte, error) {
type StructLog struct { type StructLog struct {
Pc uint64 `json:"pc"` Pc uint64 `json:"pc"`
@ -24,6 +25,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
Stack []*math.HexOrDecimal256 `json:"stack"` Stack []*math.HexOrDecimal256 `json:"stack"`
Storage map[common.Hash]common.Hash `json:"-"` Storage map[common.Hash]common.Hash `json:"-"`
Depth int `json:"depth"` Depth int `json:"depth"`
RefundCounter uint64 `json:"refund"`
Err error `json:"-"` Err error `json:"-"`
OpName string `json:"opName"` OpName string `json:"opName"`
ErrorString string `json:"error"` ErrorString string `json:"error"`
@ -43,12 +45,14 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
} }
enc.Storage = s.Storage enc.Storage = s.Storage
enc.Depth = s.Depth enc.Depth = s.Depth
enc.RefundCounter = s.RefundCounter
enc.Err = s.Err enc.Err = s.Err
enc.OpName = s.OpName() enc.OpName = s.OpName()
enc.ErrorString = s.ErrorString() enc.ErrorString = s.ErrorString()
return json.Marshal(&enc) return json.Marshal(&enc)
} }
// UnmarshalJSON unmarshals from JSON.
func (s *StructLog) UnmarshalJSON(input []byte) error { func (s *StructLog) UnmarshalJSON(input []byte) error {
type StructLog struct { type StructLog struct {
Pc *uint64 `json:"pc"` Pc *uint64 `json:"pc"`
@ -60,6 +64,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
Stack []*math.HexOrDecimal256 `json:"stack"` Stack []*math.HexOrDecimal256 `json:"stack"`
Storage map[common.Hash]common.Hash `json:"-"` Storage map[common.Hash]common.Hash `json:"-"`
Depth *int `json:"depth"` Depth *int `json:"depth"`
RefundCounter *uint64 `json:"refund"`
Err error `json:"-"` Err error `json:"-"`
} }
var dec StructLog var dec StructLog
@ -96,6 +101,9 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
if dec.Depth != nil { if dec.Depth != nil {
s.Depth = *dec.Depth s.Depth = *dec.Depth
} }
if dec.RefundCounter != nil {
s.RefundCounter = *dec.RefundCounter
}
if dec.Err != nil { if dec.Err != nil {
s.Err = dec.Err s.Err = dec.Err
} }

View File

@ -65,6 +65,7 @@ type StructLog struct {
Stack []*big.Int `json:"stack"` Stack []*big.Int `json:"stack"`
Storage map[common.Hash]common.Hash `json:"-"` Storage map[common.Hash]common.Hash `json:"-"`
Depth int `json:"depth"` Depth int `json:"depth"`
RefundCounter uint64 `json:"refund"`
Err error `json:"-"` Err error `json:"-"`
} }
@ -177,7 +178,7 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
storage = l.changedValues[contract.Address()].Copy() storage = l.changedValues[contract.Address()].Copy()
} }
// create a new snaptshot of the EVM. // create a new snaptshot of the EVM.
log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, storage, depth, err} log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, storage, depth, env.StateDB.GetRefund(), err}
l.logs = append(l.logs, log) l.logs = append(l.logs, log)
return nil return nil

View File

@ -21,6 +21,7 @@ import (
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
@ -41,9 +42,15 @@ func (d *dummyContractRef) SetBalance(*big.Int) {}
func (d *dummyContractRef) SetNonce(uint64) {} func (d *dummyContractRef) SetNonce(uint64) {}
func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) } func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) }
type dummyStatedb struct {
state.StateDB
}
func (dummyStatedb) GetRefund() uint64 { return 1337 }
func TestStoreCapture(t *testing.T) { func TestStoreCapture(t *testing.T) {
var ( var (
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) env = NewEVM(Context{}, &dummyStatedb{}, params.TestChainConfig, Config{})
logger = NewStructLogger(nil) logger = NewStructLogger(nil)
mem = NewMemory() mem = NewMemory()
stack = newstack() stack = newstack()
@ -51,9 +58,7 @@ func TestStoreCapture(t *testing.T) {
) )
stack.push(big.NewInt(1)) stack.push(big.NewInt(1))
stack.push(big.NewInt(0)) stack.push(big.NewInt(0))
var index common.Hash var index common.Hash
logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, contract, 0, nil) logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, contract, 0, nil)
if len(logger.changedValues[contract.Address()]) == 0 { if len(logger.changedValues[contract.Address()]) == 0 {
t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.changedValues[contract.Address()])) t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.changedValues[contract.Address()]))

View File

@ -295,6 +295,7 @@ type Tracer struct {
costValue *uint // Swappable cost value wrapped by a log accessor costValue *uint // Swappable cost value wrapped by a log accessor
depthValue *uint // Swappable depth value wrapped by a log accessor depthValue *uint // Swappable depth value wrapped by a log accessor
errorValue *string // Swappable error value wrapped by a log accessor errorValue *string // Swappable error value wrapped by a log accessor
refundValue *uint // Swappable refund value wrapped by a log accessor
ctx map[string]interface{} // Transaction context gathered throughout execution ctx map[string]interface{} // Transaction context gathered throughout execution
err error // Error, if one has occurred err error // Error, if one has occurred
@ -323,6 +324,7 @@ func New(code string) (*Tracer, error) {
gasValue: new(uint), gasValue: new(uint),
costValue: new(uint), costValue: new(uint),
depthValue: new(uint), depthValue: new(uint),
refundValue: new(uint),
} }
// Set up builtins for this environment // Set up builtins for this environment
tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int { tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int {
@ -442,6 +444,9 @@ func New(code string) (*Tracer, error) {
tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.depthValue); return 1 }) tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.depthValue); return 1 })
tracer.vm.PutPropString(logObject, "getDepth") tracer.vm.PutPropString(logObject, "getDepth")
tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.refundValue); return 1 })
tracer.vm.PutPropString(logObject, "getRefund")
tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { tracer.vm.PushGoFunction(func(ctx *duktape.Context) int {
if tracer.errorValue != nil { if tracer.errorValue != nil {
ctx.PushString(*tracer.errorValue) ctx.PushString(*tracer.errorValue)
@ -527,6 +532,7 @@ func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost
*jst.gasValue = uint(gas) *jst.gasValue = uint(gas)
*jst.costValue = uint(cost) *jst.costValue = uint(cost)
*jst.depthValue = uint(depth) *jst.depthValue = uint(depth)
*jst.refundValue = uint(env.StateDB.GetRefund())
jst.errorValue = nil jst.errorValue = nil
if err != nil { if err != nil {

View File

@ -25,6 +25,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
@ -43,8 +44,14 @@ func (account) ReturnGas(*big.Int) {}
func (account) SetCode(common.Hash, []byte) {} func (account) SetCode(common.Hash, []byte) {}
func (account) ForEachStorage(cb func(key, value common.Hash) bool) {} func (account) ForEachStorage(cb func(key, value common.Hash) bool) {}
type dummyStatedb struct {
state.StateDB
}
func (dummyStatedb) GetRefund() uint64 { return 1337 }
func runTrace(tracer *Tracer) (json.RawMessage, error) { func runTrace(tracer *Tracer) (json.RawMessage, error) {
env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, nil, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
contract := vm.NewContract(account{}, account{}, big.NewInt(0), 10000) contract := vm.NewContract(account{}, account{}, big.NewInt(0), 10000)
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0}
@ -126,7 +133,7 @@ func TestHaltBetweenSteps(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, nil, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), 0) contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), 0)
tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, contract, 0, nil) tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, contract, 0, nil)