Support for trace_callMany ()

Co-authored-by: Alexey Sharp <alexeysharp@Alexeys-iMac.local>
This commit is contained in:
ledgerwatch 2021-02-10 17:04:47 +00:00 committed by GitHub
parent 5c34713e71
commit 2daa71e6cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 341 additions and 21 deletions

View File

@ -3,6 +3,7 @@ package commands
import (
"bytes"
"context"
"encoding/json"
"fmt"
"math"
"math/big"
@ -22,6 +23,7 @@ import (
"github.com/ledgerwatch/turbo-geth/log"
"github.com/ledgerwatch/turbo-geth/rpc"
"github.com/ledgerwatch/turbo-geth/turbo/rpchelper"
"github.com/ledgerwatch/turbo-geth/turbo/shards"
"github.com/ledgerwatch/turbo-geth/turbo/transactions"
)
@ -47,9 +49,6 @@ type TraceCallParam struct {
Data hexutil.Bytes `json:"data"`
}
// TraceCallParams array of callMany structs
type TraceCallParams []TraceCallParam
// TraceCallResult is the response to `trace_call` method
type TraceCallResult struct {
Output hexutil.Bytes `json:"output"`
@ -546,22 +545,156 @@ func (api *TraceAPIImpl) Call(ctx context.Context, args TraceCallParam, traceTyp
return traceResult, nil
}
// TODO(tjayrush) - try to use a concrete type here
// TraceCallManyParam array of callMany structs
// type TraceCallManyParam struct {
// obj TraceCallParam
// traceTypes []string
// }
// TraceCallManyParams array of callMany structs
// type TraceCallManyParams struct {
// things []TraceCallManyParam
// }
// CallMany implements trace_callMany.
func (api *TraceAPIImpl) CallMany(ctx context.Context, calls []interface{}, blockNr *rpc.BlockNumberOrHash) ([]interface{}, error) {
var stub []interface{}
return stub, fmt.Errorf(NotImplemented, "trace_callMany")
func (api *TraceAPIImpl) CallMany(ctx context.Context, calls json.RawMessage, blockNrOrHash *rpc.BlockNumberOrHash) ([]*TraceCallResult, error) {
dbtx, err := api.dbReader.Begin(ctx, ethdb.RO)
if err != nil {
return nil, err
}
defer dbtx.Rollback()
chainConfig, err := api.chainConfig(dbtx)
if err != nil {
return nil, err
}
if blockNrOrHash == nil {
var num = rpc.LatestBlockNumber
blockNrOrHash = &rpc.BlockNumberOrHash{BlockNumber: &num}
}
blockNumber, hash, err := rpchelper.GetBlockNumber(*blockNrOrHash, dbtx)
if err != nil {
return nil, err
}
var stateReader state.StateReader
if num, ok := blockNrOrHash.Number(); ok && num == rpc.LatestBlockNumber {
stateReader = state.NewPlainStateReader(dbtx)
} else {
stateReader = state.NewPlainDBState(dbtx, blockNumber)
}
stateCache := shards.NewStateCache(32, 0 /* no limit */)
cachedReader := state.NewCachedReader(stateReader, stateCache)
noop := state.NewNoopWriter()
cachedWriter := state.NewCachedWriter(noop, stateCache)
header := rawdb.ReadHeader(dbtx, hash, blockNumber)
if header == nil {
return nil, fmt.Errorf("block %d(%x) not found", blockNumber, hash)
}
// Setup context so it may be cancelled the call has completed
// or, in case of unmetered gas, setup a context with a timeout.
var cancel context.CancelFunc
if callTimeout > 0 {
ctx, cancel = context.WithTimeout(ctx, callTimeout)
} else {
ctx, cancel = context.WithCancel(ctx)
}
// Make sure the context is cancelled when the call has completed
// this makes sure resources are cleaned up.
defer cancel()
var results []*TraceCallResult
dec := json.NewDecoder(bytes.NewReader(calls))
tok, err := dec.Token()
if err != nil {
return nil, err
}
if tok != json.Delim('[') {
return nil, fmt.Errorf("expected array of [callparam, tracetypes]")
}
txIndex := 0
for dec.More() {
tok, err = dec.Token()
if err != nil {
return nil, err
}
if tok != json.Delim('[') {
return nil, fmt.Errorf("expected [callparam, tracetypes]")
}
var args TraceCallParam
if err = dec.Decode(&args); err != nil {
return nil, err
}
var traceTypes []string
if err = dec.Decode(&traceTypes); err != nil {
return nil, err
}
tok, err = dec.Token()
if err != nil {
return nil, err
}
if tok != json.Delim(']') {
return nil, fmt.Errorf("expected end of [callparam, tracetypes]")
}
traceResult := &TraceCallResult{}
var traceTypeTrace, traceTypeStateDiff, traceTypeVmTrace bool
for _, traceType := range traceTypes {
switch traceType {
case TraceTypeTrace:
traceTypeTrace = true
case TraceTypeStateDiff:
traceTypeStateDiff = true
case TraceTypeVmTrace:
traceTypeVmTrace = true
default:
return nil, fmt.Errorf("unrecognized trace type: %s", traceType)
}
}
var ot OeTracer
if traceTypeTrace {
ot.r = traceResult
ot.traceAddr = []int{}
}
// Get a new instance of the EVM.
msg := args.ToMessage(api.gasCap)
evmCtx := transactions.GetEvmContext(msg, header, blockNrOrHash.RequireCanonical, dbtx)
ibs := state.New(cachedReader)
// Create initial IntraBlockState, we will compare it with ibs (IntraBlockState after the transaction)
evm := vm.NewEVM(evmCtx, ibs, chainConfig, vm.Config{Debug: traceTypeTrace, Tracer: &ot})
gp := new(core.GasPool).AddGas(msg.Gas())
var execResult *core.ExecutionResult
execResult, err = core.ApplyMessage(evm, msg, gp, true /* refunds */)
if err != nil {
return nil, fmt.Errorf("first run for txIndex %d error: %w", txIndex, err)
}
traceResult.Output = execResult.ReturnData
if traceTypeStateDiff {
// Clone the state cache before applying the changes, clone is discarded
cloneCache := stateCache.Clone()
cloneReader := state.NewCachedReader(stateReader, cloneCache)
initialIbs := state.New(cloneReader)
sdMap := make(map[common.Address]*StateDiffAccount)
traceResult.StateDiff = sdMap
sd := &StateDiff{sdMap: sdMap}
if err = ibs.FinalizeTx(ctx, sd); err != nil {
return nil, err
}
if err = ibs.CommitBlock(ctx, cachedWriter); err != nil {
return nil, err
}
sd.CompareStates(initialIbs, ibs)
}
if traceTypeVmTrace {
return nil, fmt.Errorf("vmTrace not implemented yet")
}
results = append(results, traceResult)
txIndex++
}
tok, err = dec.Token()
if err != nil {
return nil, err
}
if tok != json.Delim(']') {
return nil, fmt.Errorf("expected end of array of [callparam, tracetypes]")
}
return results, nil
}
// RawTransaction implements trace_rawTransaction.

View File

@ -2,6 +2,7 @@ package commands
import (
"context"
"encoding/json"
"github.com/ledgerwatch/turbo-geth/cmd/rpcdaemon/cli"
"github.com/ledgerwatch/turbo-geth/common"
@ -16,7 +17,7 @@ type TraceAPI interface {
ReplayBlockTransactions(ctx context.Context, blockNr rpc.BlockNumber, traceTypes []string) ([]interface{}, error)
ReplayTransaction(ctx context.Context, txHash common.Hash, traceTypes []string) ([]interface{}, error)
Call(ctx context.Context, call TraceCallParam, types []string, blockNr *rpc.BlockNumberOrHash) (*TraceCallResult, error)
CallMany(ctx context.Context, calls []interface{}, blockNr *rpc.BlockNumberOrHash) ([]interface{}, error)
CallMany(ctx context.Context, calls json.RawMessage, blockNr *rpc.BlockNumberOrHash) ([]*TraceCallResult, error)
RawTransaction(ctx context.Context, txHash common.Hash, traceTypes []string) ([]interface{}, error)
// Filtering (see ./trace_filtering.go)

View File

@ -162,6 +162,16 @@ func main() {
}
with(bench12Cmd, withGethUrl, withTGUrl, withNeedCompare, withBlockNum)
var bench13Cmd = &cobra.Command{
Use: "bench13",
Short: "",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
rpctest.Bench13(tgURL, gethURL, needCompare, blockNum)
},
}
with(bench13Cmd, withGethUrl, withTGUrl, withNeedCompare, withBlockNum)
var proofsCmd = &cobra.Command{
Use: "proofs",
Short: "",
@ -215,6 +225,7 @@ func main() {
bench10Cmd,
bench11Cmd,
bench12Cmd,
bench13Cmd,
proofsCmd,
fixStateCmd,
compareAccountRange,

View File

@ -10,6 +10,8 @@ import (
// Transactions on which OpenEthereum reports incorrect traces
var wrongTxs = []string{
"0xe47180a05a7cc25c187b426fed5390365874add72a5681242ac4b288d4a6833a", // Block 7000000
"0x76ea0eae8561c10321b050bc28d00ae4e22f99dc303153cc7fb1334ec88ad41e", // Block 7000053
"0xd5a9b32b262202cda422dd5a2ccf8d7d56e9b3425ba7d350548e62a5bd26b481", // Block 8000011
"0x1953ad3591fa0f6f3f00dfa0f93a57e1dc7fa003e2192a18c64c71847cf64e0c", // Block 8000035
"0xfbd66bcbc4cb374946f350ca6835571b09f68c5f635ff9fc533c3fa2ac0d19cb", // Block 9000004

View File

@ -0,0 +1,126 @@
package rpctest
import (
"fmt"
"net/http"
"time"
"github.com/ledgerwatch/turbo-geth/common"
"github.com/ledgerwatch/turbo-geth/common/hexutil"
)
// bench13 compares response of TurboGeth with Geth
// but also can be used for comparing RPCDaemon with Geth
// parameters:
// needCompare - if false - doesn't call TurboGeth and doesn't compare responses
// use false value - to generate vegeta files, it's faster but we can generate vegeta files for Geth and Turbogeth
func Bench13(tgURL, oeURL string, needCompare bool, blockNum uint64) {
setRoutes(tgURL, oeURL)
var client = &http.Client{
Timeout: time.Second * 600,
}
var res CallResult
reqGen := &RequestGenerator{
client: client,
}
skipTxs := make(map[common.Hash]struct{})
for _, txHash := range wrongTxs {
skipTxs[common.HexToHash(txHash)] = struct{}{}
}
reqGen.reqID++
var blockNumber EthBlockNumber
res = reqGen.TurboGeth("eth_blockNumber", reqGen.blockNumber(), &blockNumber)
if res.Err != nil {
fmt.Printf("Could not get block number: %v\n", res.Err)
return
}
if blockNumber.Error != nil {
fmt.Printf("Error getting block number: %d %s\n", blockNumber.Error.Code, blockNumber.Error.Message)
return
}
lastBlock := blockNumber.Number
fmt.Printf("Last block: %d\n", lastBlock)
firstBn := int(blockNum)
for bn := firstBn; bn <= int(lastBlock); bn++ {
reqGen.reqID++
var b EthBlockByNumber
res = reqGen.TurboGeth("eth_getBlockByNumber", reqGen.getBlockByNumber(bn), &b)
if res.Err != nil {
fmt.Printf("Could not retrieve block (turbo-geth) %d: %v\n", bn, res.Err)
return
}
if b.Error != nil {
fmt.Printf("Error retrieving block (turbo-geth): %d %s\n", b.Error.Code, b.Error.Message)
return
}
if needCompare {
var bg EthBlockByNumber
res = reqGen.Geth("eth_getBlockByNumber", reqGen.getBlockByNumber(bn), &bg)
if res.Err != nil {
fmt.Printf("Could not retrieve block (geth) %d: %v\n", bn, res.Err)
return
}
if bg.Error != nil {
fmt.Printf("Error retrieving block (geth): %d %s\n", bg.Error.Code, bg.Error.Message)
return
}
if !compareBlocks(&b, &bg) {
fmt.Printf("Block difference for %d\n", bn)
return
}
}
n := len(b.Result.Transactions)
from := make([]common.Address, n)
to := make([]*common.Address, n)
gas := make([]*hexutil.Big, n)
gasPrice := make([]*hexutil.Big, n)
value := make([]*hexutil.Big, n)
data := make([]hexutil.Bytes, n)
for i := 0; i < n; i++ {
tx := b.Result.Transactions[i]
from[i] = tx.From
to[i] = tx.To
gas[i] = &tx.Gas
gasPrice[i] = &tx.GasPrice
value[i] = &tx.Value
data[i] = tx.Input
}
reqGen.reqID++
res = reqGen.TurboGeth2("trace_callMany", reqGen.traceCallMany(from, to, gas, gasPrice, value, data, bn-1))
if res.Err != nil {
fmt.Printf("Could not trace callMany (turbo-geth) %d: %v\n", bn, res.Err)
return
}
if errVal := res.Result.Get("error"); errVal != nil {
fmt.Printf("Error tracing call (turbo-geth): %d %s\n", errVal.GetInt("code"), errVal.GetStringBytes("message"))
return
}
if needCompare {
resg := reqGen.Geth2("trace_callMany", reqGen.traceCallMany(from, to, gas, gasPrice, value, data, bn-1))
if resg.Err != nil {
fmt.Printf("Could not trace call (oe) %d: %v\n", bn, resg.Err)
return
}
if errVal := resg.Result.Get("error"); errVal != nil {
fmt.Printf("Error tracing call (oe): %d %s\n", errVal.GetInt("code"), errVal.GetStringBytes("message"))
return
}
if resg.Err == nil && resg.Result.Get("error") == nil {
if err := compareTraceCallManys(res.Result, resg.Result); err != nil {
fmt.Printf("Different traceManys block %d: %v\n", bn, err)
fmt.Printf("\n\nTG response=================================\n%s\n", res.Response)
fmt.Printf("\n\nG response=================================\n%s\n", resg.Response)
return
}
}
}
}
}

View File

@ -110,6 +110,36 @@ func (g *RequestGenerator) traceCall(from common.Address, to *common.Address, ga
fmt.Fprintf(&sb, `,"data":"%s"`, data)
}
fmt.Fprintf(&sb, `},["trace", "stateDiff"],"0x%x"], "id":%d}`, bn, g.reqID)
//fmt.Fprintf(&sb, `},["trace"],"0x%x"], "id":%d}`, bn, g.reqID)
return sb.String()
}
func (g *RequestGenerator) traceCallMany(from []common.Address, to []*common.Address, gas []*hexutil.Big, gasPrice []*hexutil.Big, value []*hexutil.Big, data []hexutil.Bytes, bn int) string {
var sb strings.Builder
fmt.Fprintf(&sb, `{ "jsonrpc": "2.0", "method": "trace_callMany", "params": [[`)
for i, f := range from {
if i > 0 {
fmt.Fprintf(&sb, `,`)
}
fmt.Fprintf(&sb, `[{"from":"0x%x"`, f)
if to[i] != nil {
fmt.Fprintf(&sb, `,"to":"0x%x"`, *to[i])
}
if gas[i] != nil {
fmt.Fprintf(&sb, `,"gas":"%s"`, gas[i])
}
if gasPrice[i] != nil {
fmt.Fprintf(&sb, `,"gasPrice":"%s"`, gasPrice[i])
}
if value[i] != nil {
fmt.Fprintf(&sb, `,"value":"%s"`, value[i])
}
if len(data[i]) > 0 {
fmt.Fprintf(&sb, `,"data":"%s"`, data[i])
}
fmt.Fprintf(&sb, `},["trace", "stateDiff"]]`)
}
fmt.Fprintf(&sb, `],"0x%x"], "id":%d}`, bn, g.reqID)
return sb.String()
}

View File

@ -183,6 +183,12 @@ func compareTraceCalls(trace, traceg *fastjson.Value) error {
return compareJsonValues("result", r, rg)
}
func compareTraceCallManys(trace, traceg *fastjson.Value) error {
r := trace.Get("result")
rg := traceg.Get("result")
return compareJsonValues("result", r, rg)
}
func compareBalances(balance, balanceg *EthBalance) bool {
if balance.Balance.ToInt().Cmp(balanceg.Balance.ToInt()) != 0 {
fmt.Printf("Different balance: %d %d\n", balance.Balance.ToInt(), balanceg.Balance.ToInt())

View File

@ -534,6 +534,17 @@ func NewStateCache(degree int, limit int) *StateCache {
return &sc
}
// Clone creates a clone cache which can be modified independently, but it shares the parts of the cache that are common
func (sc *StateCache) Clone() *StateCache {
var clone StateCache
clone.readWrites = sc.readWrites.Clone()
clone.writes = sc.writes.Clone()
clone.limit = sc.limit
heap.Init(&clone.readQueue)
heap.Init(&clone.unprocQueue)
return &clone
}
func (sc *StateCache) get(key btree.Item) (CacheItem, bool) {
item := sc.readWrites.Get(key)
if item == nil {
@ -646,7 +657,7 @@ func (sc *StateCache) setRead(item CacheItem, absent bool) {
} else {
item.ClearFlags(AbsentFlag)
}
if sc.readSize+item.GetSize() > sc.limit {
if sc.limit != 0 && sc.readSize+item.GetSize() > sc.limit {
for sc.readQueue.Len() > 0 && sc.readSize+item.GetSize() > sc.limit {
// Read queue cannot grow anymore, need to evict one element
cacheItem := heap.Pop(&sc.readQueue).(CacheItem)
@ -728,7 +739,7 @@ func (sc *StateCache) setWrite(item CacheItem, writeItem CacheWriteItem, delete
sc.writeSize += writeItem.GetSize()
return
}
if sc.readSize+item.GetSize() > sc.limit {
if sc.limit != 0 && sc.readSize+item.GetSize() > sc.limit {
for sc.readQueue.Len() > 0 && sc.readSize+item.GetSize() > sc.limit {
// There is no space available, need to evict one read element
cacheItem := heap.Pop(&sc.readQueue).(CacheItem)