mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2025-01-11 21:40:05 +00:00
Support for trace_callMany (#1486)
Co-authored-by: Alexey Sharp <alexeysharp@Alexeys-iMac.local>
This commit is contained in:
parent
5c34713e71
commit
2daa71e6cb
cmd
rpcdaemon/commands
rpctest
turbo/shards
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
126
cmd/rpctest/rpctest/bench13.go
Normal file
126
cmd/rpctest/rpctest/bench13.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user