Skip to content

Commit

Permalink
Add traceBlock endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
vitaliy committed Sep 9, 2024
1 parent 9076460 commit 8351049
Show file tree
Hide file tree
Showing 12 changed files with 320 additions and 46 deletions.
10 changes: 6 additions & 4 deletions packages/chainutil/evmtrace.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import (
"github.com/iotaledger/wasp/packages/isc"
)

func EVMTraceTransaction(
func EVMTrace(
ch chain.ChainCore,
aliasOutput *isc.AliasOutputWithID,
blockTime time.Time,
iscRequestsInBlock []isc.Request,
txIndex uint64,
txIndex *uint64,
blockNumber *uint64,
tracer *tracers.Tracer,
) error {
_, err := runISCTask(
Expand All @@ -24,8 +25,9 @@ func EVMTraceTransaction(
iscRequestsInBlock,
false,
&isc.EVMTracer{
Tracer: tracer,
TxIndex: txIndex,
Tracer: tracer,
TxIndex: txIndex,
BlockNumber: blockNumber,
},
)
return err
Expand Down
2 changes: 1 addition & 1 deletion packages/evm/jsonrpc/chainbackend.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type ChainBackend interface {
EVMSendTransaction(tx *types.Transaction) error
EVMCall(aliasOutput *isc.AliasOutputWithID, callMsg ethereum.CallMsg) ([]byte, error)
EVMEstimateGas(aliasOutput *isc.AliasOutputWithID, callMsg ethereum.CallMsg) (uint64, error)
EVMTraceTransaction(aliasOutput *isc.AliasOutputWithID, blockTime time.Time, iscRequestsInBlock []isc.Request, txIndex uint64, tracer *tracers.Tracer) error
EVMTrace(aliasOutput *isc.AliasOutputWithID, blockTime time.Time, iscRequestsInBlock []isc.Request, txIndex *uint64, blockNumber *uint64, tracer *tracers.Tracer) error
FeePolicy(blockIndex uint32) (*gas.FeePolicy, error)
ISCChainID() *isc.ChainID
ISCCallView(chainState state.State, scName string, funName string, args dict.Dict) (dict.Dict, error)
Expand Down
95 changes: 83 additions & 12 deletions packages/evm/jsonrpc/evmchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,25 @@ func (e *EVMChain) TransactionByBlockNumberAndIndex(blockNumber *big.Int, index
return txs[index], block.Hash(), bn, nil
}

func (e *EVMChain) txsByBlockNumber(blockNumber *big.Int) (txs types.Transactions, err error) {
e.log.Debugf("TxsByBlockNumber(blockNumber=%v, index=%v)", blockNumber)
cachedTxs := e.index.TxsByBlockNumber(blockNumber)
if cachedTxs != nil {
return cachedTxs, nil
}
latestState, err := e.backend.ISCLatestState()
if err != nil {
return nil, err
}
db := blockchainDB(latestState)
block := db.GetBlockByNumber(blockNumber.Uint64())
if block == nil {
return nil, err
}

return block.Transactions(), nil
}

func (e *EVMChain) BlockByHash(hash common.Hash) *types.Block {
e.log.Debugf("BlockByHash(hash=%v)", hash)

Expand Down Expand Up @@ -639,44 +658,48 @@ func (e *EVMChain) iscRequestsInBlock(evmBlockNumber uint64) (*blocklog.BlockInf
}
iscBlockIndex := iscState.BlockIndex()
blocklogStatePartition := subrealm.NewReadOnly(iscState, kv.Key(blocklog.Contract.Hname().Bytes()))

return blocklog.GetRequestsInBlock(blocklogStatePartition, iscBlockIndex)
}

func (e *EVMChain) TraceTransaction(txHash common.Hash, config *tracers.TraceConfig) (any, error) {
e.log.Debugf("TraceTransaction(txHash=%v, config=?)", txHash)
func (e *EVMChain) Trace(config *tracers.TraceConfig, txIndex *uint64, txHash common.Hash, blockNumber uint64, blockHash common.Hash) (any, error) {
tracerType := "callTracer"
if config.Tracer != nil {
tracerType = *config.Tracer
}

_, blockHash, blockNumber, txIndex, err := e.TransactionByHash(txHash)
iscBlock, iscRequestsInBlock, err := e.iscRequestsInBlock(blockNumber)
if err != nil {
return nil, err
}
if blockNumber == 0 {
return nil, errors.New("tx not found")
}

iscBlock, iscRequestsInBlock, err := e.iscRequestsInBlock(blockNumber)
if err != nil {
return nil, err
var blockTxs types.Transactions
var txi int
if txIndex != nil {
txi = int(*txIndex)
} else {
blockTxs, err = e.txsByBlockNumber(new(big.Int).SetUint64(blockNumber))
if err != nil {
return nil, err
}
}

tracer, err := newTracer(tracerType, &tracers.Context{
BlockHash: blockHash,
BlockNumber: new(big.Int).SetUint64(blockNumber),
TxIndex: int(txIndex),
TxIndex: txi,
TxHash: txHash,
}, config.TracerConfig)
}, config.TracerConfig, blockTxs)
if err != nil {
return nil, err
}

err = e.backend.EVMTraceTransaction(
err = e.backend.EVMTrace(
iscBlock.PreviousAliasOutput,
iscBlock.Timestamp,
iscRequestsInBlock,
txIndex,
&blockNumber,
tracer,
)
if err != nil {
Expand All @@ -686,6 +709,54 @@ func (e *EVMChain) TraceTransaction(txHash common.Hash, config *tracers.TraceCon
return tracer.GetResult()
}

func (e *EVMChain) TraceTransaction(txHash common.Hash, config *tracers.TraceConfig) (any, error) {
e.log.Debugf("TraceTransaction(txHash=%v, config=?)", txHash)

_, blockHash, blockNumber, txIndex, err := e.TransactionByHash(txHash)
if err != nil {
return nil, err
}
if blockNumber == 0 {
return nil, errors.New("tx not found")
}

return e.Trace(config, &txIndex, txHash, blockNumber, blockHash)
}

func (e *EVMChain) TraceBlockByHash(blockHash common.Hash, config *tracers.TraceConfig) (any, error) {
e.log.Debugf("TraceBlockByHash(blockHash=%v, config=?)", blockHash)

block := e.BlockByHash(blockHash)
if block == nil {
return nil, errors.New("block not found")
}

return e.Trace(config, nil, common.Hash{}, block.Number().Uint64(), blockHash)
}

func (e *EVMChain) TraceBlockByNumber(blockNumber uint64, config *tracers.TraceConfig) (any, error) {
e.log.Debugf("TraceBlockByNumber(blockNumber=%v, config=?)", blockNumber)

block, err := e.BlockByNumber(big.NewInt(int64(blockNumber)))
if err != nil {
return nil, fmt.Errorf("block not found: %w", err)
}

return e.Trace(config, nil, common.Hash{}, blockNumber, block.Hash())
}

func (e *EVMChain) GetBlockReceipts(blockNumber rpc.BlockNumber) ([]*types.Receipt, error) {
e.log.Debugf("GetBlockReceipts(blockNumber=%v)", blockNumber)
bn := parseBlockNumber(blockNumber)
chainState, err := e.iscStateFromEVMBlockNumber(bn)
if err != nil {
return nil, err
}

db := blockchainDB(chainState)
return db.GetReceiptsByBlockNumber(bn.Uint64()), nil
}

var maxUint32 = big.NewInt(math.MaxUint32)

// the first EVM block (number 0) is "minted" at ISC block index 0 (init chain)
Expand Down
15 changes: 15 additions & 0 deletions packages/evm/jsonrpc/jsonrpcindex/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,21 @@ func (c *Index) TxByBlockNumberAndIndex(blockNumber *big.Int, txIndex uint64) (t
return txs[txIndex], block.Hash()
}

func (c *Index) TxsByBlockNumber(blockNumber *big.Int) types.Transactions {
if blockNumber == nil {
return nil
}
db := c.evmDBFromBlockIndex(uint32(blockNumber.Uint64()))
if db == nil {
return nil
}
block := db.GetBlockByNumber(blockNumber.Uint64())
if block == nil {
return nil
}
return block.Transactions()
}

// internals

const (
Expand Down
128 changes: 123 additions & 5 deletions packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"context"
"encoding/json"
"math/big"
"regexp"
"slices"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -579,10 +579,128 @@ func TestRPCTraceTx(t *testing.T) {
tracers.TraceConfig{TracerConfig: []byte(`{"tracer": "callTracer"}`)},
)
require.NoError(t, err)
lastCallRegExp := regexp.MustCompile(`{.+"to":"0x([a-zA-Z0-9_.-]+)".*}`)
match1 := lastCallRegExp.Find(res1)
match2 := lastCallRegExp.Find(res2)
require.NotEqual(t, match1, match2)

trace1 := jsonrpc.CallFrame{}
err = json.Unmarshal(res1, &trace1)
require.NoError(t, err)

require.Equal(t, creatorAddress, trace1.From)
require.Equal(t, contractAddress, *trace1.To)
require.Equal(t, big.NewInt(123), trace1.Value)
expectedInput, err := contractABI.Pack("sendTo", common.Address{0x1}, big.NewInt(1))
require.NoError(t, err)
require.Equal(t, expectedInput, trace1.Input)
require.Empty(t, trace1.Error)
require.Empty(t, trace1.RevertReason)

require.Len(t, trace1.Calls, 1)
trace2 := trace1.Calls[0]
require.Equal(t, contractAddress, trace2.From)
require.Equal(t, common.Address{0x1}, *trace2.To)
require.Equal(t, big.NewInt(1), trace2.Value)
require.Empty(t, trace2.Input)
require.Empty(t, trace2.Error)
require.Empty(t, trace2.RevertReason)
}

func TestRPCTraceBlock(t *testing.T) {
env := newSoloTestEnv(t)
creator, creatorAddress := env.soloChain.NewEthereumAccountWithL2Funds()
creator2, creatorAddress2 := env.soloChain.NewEthereumAccountWithL2Funds()
contractABI, err := abi.JSON(strings.NewReader(evmtest.ISCTestContractABI))
require.NoError(t, err)
_, _, contractAddress := env.DeployEVMContract(creator, contractABI, evmtest.ISCTestContractBytecode)

// make it so that 2 requests are included in the same block
tx1 := types.MustSignNewTx(creator, types.NewEIP155Signer(big.NewInt(int64(env.ChainID))),
&types.LegacyTx{
Nonce: env.NonceAt(creatorAddress),
To: &contractAddress,
Value: big.NewInt(123),
Gas: 100000,
GasPrice: big.NewInt(10000000000),
Data: lo.Must(contractABI.Pack("sendTo", common.Address{0x1}, big.NewInt(2))),
})

tx2 := types.MustSignNewTx(creator2, types.NewEIP155Signer(big.NewInt(int64(env.ChainID))),
&types.LegacyTx{
Nonce: env.NonceAt(creatorAddress2),
To: &contractAddress,
Value: big.NewInt(321),
Gas: 100000,
GasPrice: big.NewInt(10000000000),
Data: lo.Must(contractABI.Pack("sendTo", common.Address{0x2}, big.NewInt(3))),
})

req1 := lo.Must(isc.NewEVMOffLedgerTxRequest(env.soloChain.ChainID, tx1))
req2 := lo.Must(isc.NewEVMOffLedgerTxRequest(env.soloChain.ChainID, tx2))
env.soloChain.WaitForRequestsMark()
env.soloChain.Env.AddRequestsToMempool(env.soloChain, []isc.Request{req1, req2})
require.True(t, env.soloChain.WaitForRequestsThrough(2, 180*time.Second))

bi := env.soloChain.GetLatestBlockInfo()
require.EqualValues(t, 2, bi.NumSuccessfulRequests)

var res1 json.RawMessage
// we have to use the raw client, because the normal client does not support debug methods
err = env.RawClient.CallContext(
context.Background(),
&res1,
"debug_traceBlockByNumber",
env.BlockNumber(),
tracers.TraceConfig{TracerConfig: []byte(`{"tracer": "callTracer"}`)},
)
require.NoError(t, err)

traceBlock := make([]jsonrpc.TxTraceResult, 0)
err = json.Unmarshal(res1, &traceBlock)
require.NoError(t, err)

require.Len(t, traceBlock, 2)

trace1 := traceBlock[slices.IndexFunc(traceBlock, func(v jsonrpc.TxTraceResult) bool {
return v.TxHash == tx1.Hash()
})].Result

trace2 := traceBlock[slices.IndexFunc(traceBlock, func(v jsonrpc.TxTraceResult) bool {
return v.TxHash == tx2.Hash()
})].Result

require.Equal(t, creatorAddress, trace1.From)
require.Equal(t, contractAddress, *trace1.To)
require.Equal(t, big.NewInt(123), trace1.Value)
expectedInput, err := contractABI.Pack("sendTo", common.Address{0x1}, big.NewInt(2))
require.NoError(t, err)
require.Equal(t, expectedInput, trace1.Input)
require.Empty(t, trace1.Error)
require.Empty(t, trace1.RevertReason)

require.Len(t, trace1.Calls, 1)
innerCall1 := trace1.Calls[0]
require.Equal(t, contractAddress, innerCall1.From)
require.Equal(t, common.Address{0x1}, *innerCall1.To)
require.Equal(t, big.NewInt(2), innerCall1.Value)
require.Empty(t, innerCall1.Input)
require.Empty(t, innerCall1.Error)
require.Empty(t, innerCall1.RevertReason)

require.Equal(t, creatorAddress2, trace2.From)
require.Equal(t, contractAddress, *trace2.To)
require.Equal(t, big.NewInt(321), trace2.Value)
expectedInput, err = contractABI.Pack("sendTo", common.Address{0x2}, big.NewInt(3))
require.NoError(t, err)
require.Equal(t, expectedInput, trace2.Input)
require.Empty(t, trace2.Error)
require.Empty(t, trace2.RevertReason)

require.Len(t, trace2.Calls, 1)
innerCall2 := trace2.Calls[0]
require.Equal(t, contractAddress, innerCall2.From)
require.Equal(t, common.Address{0x2}, *innerCall2.To)
require.Equal(t, big.NewInt(3), innerCall2.Value)
require.Empty(t, innerCall2.Input)
require.Empty(t, innerCall2.Error)
require.Empty(t, innerCall2.RevertReason)
}

func BenchmarkRPCEstimateGas(b *testing.B) {
Expand Down
23 changes: 23 additions & 0 deletions packages/evm/jsonrpc/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,17 @@ func (e *EthService) Logs(ctx context.Context, q *RPCFilterQuery) (*rpc.Subscrip
return rpcSub, nil
}

func (e *EthService) GetBlockReceipts(blockNumber rpc.BlockNumber) ([]*types.Receipt, error) {
return withMetrics(e.metrics, "eth_getBlockReceipts", func() ([]*types.Receipt, error) {
receipts, err := e.evmChain.GetBlockReceipts(blockNumber)
if err != nil {
return nil, e.resolveError(err)
}

return receipts, nil
})
}

/*
Not implemented:
func (e *EthService) NewFilter()
Expand Down Expand Up @@ -543,6 +554,18 @@ func (d *DebugService) TraceTransaction(txHash common.Hash, config *tracers.Trac
})
}

func (d *DebugService) TraceBlockByNumber(blockNumber uint64, config *tracers.TraceConfig) (interface{}, error) {
return withMetrics(d.metrics, "debug_traceBlockByNumber", func() (interface{}, error) {
return d.evmChain.TraceBlockByNumber(blockNumber, config)
})
}

func (d *DebugService) TraceBlockByHash(blockHash common.Hash, config *tracers.TraceConfig) (interface{}, error) {
return withMetrics(d.metrics, "debug_traceBlockByHash", func() (interface{}, error) {
return d.evmChain.TraceBlockByHash(blockHash, config)
})
}

type EVMService struct {
evmChain *EVMChain
}
Expand Down
Loading

0 comments on commit 8351049

Please sign in to comment.