debug_traceCall
and debug_traceBlock
#261
2
.github/workflows/tests.yaml
vendored
2
.github/workflows/tests.yaml
vendored
@ -14,7 +14,7 @@ on:
|
|||||||
env:
|
env:
|
||||||
# Needed until we can incorporate docker startup into the executor container
|
# Needed until we can incorporate docker startup into the executor container
|
||||||
DOCKER_HOST: unix:///var/run/dind.sock
|
DOCKER_HOST: unix:///var/run/dind.sock
|
||||||
SO_VERSION: v1.1.0-e0b5318-202309201927 # contains fixes for plugeth stack
|
SO_VERSION: v1.1.0-b7f215d-202401282321 # contains fixes for plugeth stack
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
4
go.mod
4
go.mod
@ -295,9 +295,9 @@ require (
|
|||||||
|
|
||||||
replace (
|
replace (
|
||||||
github.com/cerc-io/eth-ipfs-state-validator/v5 => git.vdb.to/cerc-io/eth-ipfs-state-validator/v5 v5.1.1-alpha
|
github.com/cerc-io/eth-ipfs-state-validator/v5 => git.vdb.to/cerc-io/eth-ipfs-state-validator/v5 v5.1.1-alpha
|
||||||
github.com/cerc-io/eth-iterator-utils => git.vdb.to/cerc-io/eth-iterator-utils v0.1.2
|
github.com/cerc-io/eth-iterator-utils => git.vdb.to/cerc-io/eth-iterator-utils v0.1.2-beta
|
||||||
github.com/cerc-io/eth-testing => git.vdb.to/cerc-io/eth-testing v0.3.1
|
github.com/cerc-io/eth-testing => git.vdb.to/cerc-io/eth-testing v0.3.1
|
||||||
github.com/cerc-io/ipld-eth-statedb => git.vdb.to/cerc-io/ipld-eth-statedb v0.0.6-alpha
|
github.com/cerc-io/ipld-eth-statedb => git.vdb.to/cerc-io/ipld-eth-statedb v0.0.7-alpha-0.0.1 // git.vdb.to/cerc-io/ipld-eth-statedb v0.0.6-alpha
|
||||||
github.com/cerc-io/plugeth-statediff => git.vdb.to/cerc-io/plugeth-statediff v0.1.4
|
github.com/cerc-io/plugeth-statediff => git.vdb.to/cerc-io/plugeth-statediff v0.1.4
|
||||||
github.com/ethereum/go-ethereum => git.vdb.to/cerc-io/plugeth v0.0.0-20230808125822-691dc334fab1
|
github.com/ethereum/go-ethereum => git.vdb.to/cerc-io/plugeth v0.0.0-20230808125822-691dc334fab1
|
||||||
github.com/openrelayxyz/plugeth-utils => git.vdb.to/cerc-io/plugeth-utils v0.0.0-20230706160122-cd41de354c46
|
github.com/openrelayxyz/plugeth-utils => git.vdb.to/cerc-io/plugeth-utils v0.0.0-20230706160122-cd41de354c46
|
||||||
|
8
go.sum
8
go.sum
@ -47,11 +47,11 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D
|
|||||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||||
git.vdb.to/cerc-io/eth-ipfs-state-validator/v5 v5.1.1-alpha h1:tophBXLyhMKmQILsxjfUTwQW+TGHtEJNajVULJJLhe0=
|
git.vdb.to/cerc-io/eth-ipfs-state-validator/v5 v5.1.1-alpha h1:tophBXLyhMKmQILsxjfUTwQW+TGHtEJNajVULJJLhe0=
|
||||||
git.vdb.to/cerc-io/eth-ipfs-state-validator/v5 v5.1.1-alpha/go.mod h1:e/9QV7BeaAvR+E6G3fab5+OxKGOEwCmJzs8WReuqKZM=
|
git.vdb.to/cerc-io/eth-ipfs-state-validator/v5 v5.1.1-alpha/go.mod h1:e/9QV7BeaAvR+E6G3fab5+OxKGOEwCmJzs8WReuqKZM=
|
||||||
git.vdb.to/cerc-io/eth-iterator-utils v0.1.2 h1:+3+T+J21J/VkhlCFujl8HT4XuwebavIuKj0+qfE+0QM=
|
git.vdb.to/cerc-io/eth-iterator-utils v0.1.2-beta h1:pv1HCRlD7/1X7i35MWylwGhji0aWI4QujsrJoYOW55U=
|
||||||
git.vdb.to/cerc-io/eth-iterator-utils v0.1.2/go.mod h1:OvXbdWbZ5viBXC/Ui1EkhsSmGB+AUX+TjGa3UDAfjfg=
|
git.vdb.to/cerc-io/eth-iterator-utils v0.1.2-beta/go.mod h1:OvXbdWbZ5viBXC/Ui1EkhsSmGB+AUX+TjGa3UDAfjfg=
|
||||||
git.vdb.to/cerc-io/eth-testing v0.3.1 h1:sPnlMev6oEgTjsW7GtUkSsjKNG/+X6P9q0izSejLGpM=
|
git.vdb.to/cerc-io/eth-testing v0.3.1 h1:sPnlMev6oEgTjsW7GtUkSsjKNG/+X6P9q0izSejLGpM=
|
||||||
git.vdb.to/cerc-io/ipld-eth-statedb v0.0.6-alpha h1:0YnoohjuK7w2JhIqLDDyVUNnu1RjyeDqqyhm6MojD74=
|
git.vdb.to/cerc-io/ipld-eth-statedb v0.0.7-alpha-0.0.1 h1:wIT5/LEYlBSDp2lzY8R9+RIkM4DALSP4VJg7FpKxR+c=
|
||||||
git.vdb.to/cerc-io/ipld-eth-statedb v0.0.6-alpha/go.mod h1:cCQCfIUX5vTZBHeAfLa8wLUeLKO8kygDPm7Afc+MMI8=
|
git.vdb.to/cerc-io/ipld-eth-statedb v0.0.7-alpha-0.0.1/go.mod h1:isx+cwWmkOL6hzfbcjRt9lDR4vxT0s0kCR8wD01H1/8=
|
||||||
git.vdb.to/cerc-io/plugeth v0.0.0-20230808125822-691dc334fab1 h1:KLjxHwp9Zp7xhECccmJS00RiL+VwTuUGLU7qeIctg8g=
|
git.vdb.to/cerc-io/plugeth v0.0.0-20230808125822-691dc334fab1 h1:KLjxHwp9Zp7xhECccmJS00RiL+VwTuUGLU7qeIctg8g=
|
||||||
git.vdb.to/cerc-io/plugeth v0.0.0-20230808125822-691dc334fab1/go.mod h1:cYXZu70+6xmDgIgrTD81GPasv16piiAFJnKyAbwVPMU=
|
git.vdb.to/cerc-io/plugeth v0.0.0-20230808125822-691dc334fab1/go.mod h1:cYXZu70+6xmDgIgrTD81GPasv16piiAFJnKyAbwVPMU=
|
||||||
git.vdb.to/cerc-io/plugeth-statediff v0.1.4 h1:swDJDAk1/yu6MOHAvxeyZz+MS1H9FCmSWGQRswFxFEw=
|
git.vdb.to/cerc-io/plugeth-statediff v0.1.4 h1:swDJDAk1/yu6MOHAvxeyZz+MS1H9FCmSWGQRswFxFEw=
|
||||||
|
@ -27,7 +27,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cerc-io/plugeth-statediff"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
|
||||||
|
statediff "github.com/cerc-io/plugeth-statediff"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
@ -1005,9 +1007,52 @@ func (diff *StateOverride) Apply(state *ipld_direct_state.StateDB) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Now finalize the changes. Finalize is normally performed between transactions.
|
||||||
|
// By using finalize, the overrides are semantically behaving as
|
||||||
|
// if they were created in a transaction just before the tracing occur.
|
||||||
|
state.Finalise(false)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BlockOverrides is a set of header fields to override.
|
||||||
|
type BlockOverrides struct {
|
||||||
|
Number *hexutil.Big
|
||||||
|
Difficulty *hexutil.Big
|
||||||
|
Time *hexutil.Uint64
|
||||||
|
GasLimit *hexutil.Uint64
|
||||||
|
Coinbase *common.Address
|
||||||
|
Random *common.Hash
|
||||||
|
BaseFee *hexutil.Big
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply overrides the given header fields into the given block context.
|
||||||
|
func (diff *BlockOverrides) Apply(blockCtx *vm.BlockContext) {
|
||||||
|
if diff == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if diff.Number != nil {
|
||||||
|
blockCtx.BlockNumber = diff.Number.ToInt()
|
||||||
|
}
|
||||||
|
if diff.Difficulty != nil {
|
||||||
|
blockCtx.Difficulty = diff.Difficulty.ToInt()
|
||||||
|
}
|
||||||
|
if diff.Time != nil {
|
||||||
|
blockCtx.Time = uint64(*diff.Time)
|
||||||
|
}
|
||||||
|
if diff.GasLimit != nil {
|
||||||
|
blockCtx.GasLimit = uint64(*diff.GasLimit)
|
||||||
|
}
|
||||||
|
if diff.Coinbase != nil {
|
||||||
|
blockCtx.Coinbase = *diff.Coinbase
|
||||||
|
}
|
||||||
|
if diff.Random != nil {
|
||||||
|
blockCtx.Random = diff.Random
|
||||||
|
}
|
||||||
|
if diff.BaseFee != nil {
|
||||||
|
blockCtx.BaseFee = diff.BaseFee.ToInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Call executes the given transaction on the state for the given block number.
|
// Call executes the given transaction on the state for the given block number.
|
||||||
//
|
//
|
||||||
// Additionally, the caller can specify a batch of contract for fields overriding.
|
// Additionally, the caller can specify a batch of contract for fields overriding.
|
||||||
|
466
pkg/eth/debug_test/debug_test.go
Normal file
466
pkg/eth/debug_test/debug_test.go
Normal file
@ -0,0 +1,466 @@
|
|||||||
|
// VulcanizeDB
|
||||||
|
// Copyright © 2019 Vulcanize
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package eth_debug_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
statediff "github.com/cerc-io/plugeth-statediff"
|
||||||
|
"github.com/cerc-io/plugeth-statediff/adapt"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"github.com/cerc-io/ipld-eth-server/v5/pkg/eth"
|
||||||
|
"github.com/cerc-io/ipld-eth-server/v5/pkg/shared"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
db *sqlx.DB
|
||||||
|
chainConfig = &*params.TestChainConfig
|
||||||
|
mockTD = big.NewInt(1337)
|
||||||
|
ctx = context.Background()
|
||||||
|
tb *testBackend
|
||||||
|
accounts Accounts
|
||||||
|
genBlocks int
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = BeforeSuite(func() {
|
||||||
|
// db and type initializations
|
||||||
|
var err error
|
||||||
|
db = shared.SetupDB()
|
||||||
|
|
||||||
|
// Initialize test accounts
|
||||||
|
accounts = newAccounts(3)
|
||||||
|
genesis := &core.Genesis{
|
||||||
|
Config: chainConfig,
|
||||||
|
Alloc: core.GenesisAlloc{
|
||||||
|
accounts[0].addr: {Balance: big.NewInt(params.Ether)},
|
||||||
|
accounts[1].addr: {Balance: big.NewInt(params.Ether)},
|
||||||
|
accounts[2].addr: {Balance: big.NewInt(params.Ether)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
genBlocks = 10
|
||||||
|
signer := types.HomesteadSigner{}
|
||||||
|
tb, blocks, receipts := newTestBackend(genBlocks, genesis, func(i int, b *core.BlockGen) {
|
||||||
|
// Transfer from account[0] to account[1]
|
||||||
|
// value: 1000 wei
|
||||||
|
// fee: 0 wei
|
||||||
|
tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key)
|
||||||
|
b.AddTx(tx)
|
||||||
|
})
|
||||||
|
transformer := shared.SetupTestStateDiffIndexer(ctx, chainConfig, blocks[0].Hash())
|
||||||
|
params := statediff.Params{}
|
||||||
|
|
||||||
|
// iterate over the blocks, generating statediff payloads, and transforming the data into Postgres
|
||||||
|
builder := statediff.NewBuilder(adapt.GethStateView(tb.chain.StateCache()))
|
||||||
|
for i, block := range blocks {
|
||||||
|
var args statediff.Args
|
||||||
|
if i == 0 {
|
||||||
|
args = statediff.Args{
|
||||||
|
OldStateRoot: common.Hash{},
|
||||||
|
NewStateRoot: block.Root(),
|
||||||
|
BlockNumber: block.Number(),
|
||||||
|
BlockHash: block.Hash(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
args = statediff.Args{
|
||||||
|
OldStateRoot: blocks[i-1].Root(),
|
||||||
|
NewStateRoot: block.Root(),
|
||||||
|
BlockNumber: block.Number(),
|
||||||
|
BlockHash: block.Hash(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diff, err := builder.BuildStateDiffObject(args, params)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
tx, err := transformer.PushBlock(block, receipts[i], mockTD)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
defer tx.RollbackOnFailure(err)
|
||||||
|
|
||||||
|
for _, node := range diff.Nodes {
|
||||||
|
err = transformer.PushStateNode(tx, node, block.Hash().String())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ipld := range diff.IPLDs {
|
||||||
|
err = transformer.PushIPLD(tx, ipld)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Submit()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
}
|
||||||
|
|
||||||
|
backend, err := eth.NewEthBackend(db, ð.Config{
|
||||||
|
ChainConfig: chainConfig,
|
||||||
|
VMConfig: vm.Config{},
|
||||||
|
RPCGasCap: big.NewInt(10000000000), // Max gas capacity for a rpc call.
|
||||||
|
GroupCacheConfig: &shared.GroupCacheConfig{
|
||||||
|
StateDB: shared.GroupConfig{
|
||||||
|
Name: "eth_debug_test",
|
||||||
|
CacheSizeInMB: 8,
|
||||||
|
CacheExpiryInMins: 60,
|
||||||
|
LogStatsIntervalInSecs: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
tracingAPI, _ = eth.NewTracingAPI(backend, nil, eth.APIConfig{StateDiffTimeout: shared.DefaultStateDiffTimeout})
|
||||||
|
tb.teardown()
|
||||||
|
})
|
||||||
|
|
||||||
|
var _ = AfterSuite(func() {
|
||||||
|
shared.TearDownDB(db)
|
||||||
|
})
|
||||||
|
|
||||||
|
var (
|
||||||
|
tracingAPI *eth.TracingAPI
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("eth state reading tests", func() {
|
||||||
|
Describe("debug_traceCall", func() {
|
||||||
|
It("Works", func() {
|
||||||
|
var testSuite = []struct {
|
||||||
|
blockNumber rpc.BlockNumber
|
||||||
|
call eth.TransactionArgs
|
||||||
|
config *eth.TraceCallConfig
|
||||||
|
expectErr error
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
// Standard JSON trace upon the genesis, plain transfer.
|
||||||
|
{
|
||||||
|
blockNumber: rpc.BlockNumber(0),
|
||||||
|
call: eth.TransactionArgs{
|
||||||
|
From: &accounts[0].addr,
|
||||||
|
To: &accounts[1].addr,
|
||||||
|
Value: (*hexutil.Big)(big.NewInt(1000)),
|
||||||
|
},
|
||||||
|
config: nil,
|
||||||
|
expectErr: nil,
|
||||||
|
expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`,
|
||||||
|
},
|
||||||
|
// Standard JSON trace upon the head, plain transfer.
|
||||||
|
{
|
||||||
|
blockNumber: rpc.BlockNumber(genBlocks),
|
||||||
|
call: eth.TransactionArgs{
|
||||||
|
From: &accounts[0].addr,
|
||||||
|
To: &accounts[1].addr,
|
||||||
|
Value: (*hexutil.Big)(big.NewInt(1000)),
|
||||||
|
},
|
||||||
|
config: nil,
|
||||||
|
expectErr: nil,
|
||||||
|
expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`,
|
||||||
|
},
|
||||||
|
// Standard JSON trace upon the non-existent block, error expects
|
||||||
|
{
|
||||||
|
blockNumber: rpc.BlockNumber(genBlocks + 1),
|
||||||
|
call: eth.TransactionArgs{
|
||||||
|
From: &accounts[0].addr,
|
||||||
|
To: &accounts[1].addr,
|
||||||
|
Value: (*hexutil.Big)(big.NewInt(1000)),
|
||||||
|
},
|
||||||
|
config: nil,
|
||||||
|
expectErr: fmt.Errorf("block #%d not found", genBlocks+1),
|
||||||
|
//expect: nil,
|
||||||
|
},
|
||||||
|
// Standard JSON trace upon the latest block
|
||||||
|
{
|
||||||
|
blockNumber: rpc.LatestBlockNumber,
|
||||||
|
call: eth.TransactionArgs{
|
||||||
|
From: &accounts[0].addr,
|
||||||
|
To: &accounts[1].addr,
|
||||||
|
Value: (*hexutil.Big)(big.NewInt(1000)),
|
||||||
|
},
|
||||||
|
config: nil,
|
||||||
|
expectErr: nil,
|
||||||
|
expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`,
|
||||||
|
},
|
||||||
|
// Tracing on 'pending' should fail:
|
||||||
|
{
|
||||||
|
blockNumber: rpc.PendingBlockNumber,
|
||||||
|
call: eth.TransactionArgs{
|
||||||
|
From: &accounts[0].addr,
|
||||||
|
To: &accounts[1].addr,
|
||||||
|
Value: (*hexutil.Big)(big.NewInt(1000)),
|
||||||
|
},
|
||||||
|
config: nil,
|
||||||
|
expectErr: fmt.Errorf("tracing on top of pending is not supported"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
blockNumber: rpc.LatestBlockNumber,
|
||||||
|
call: eth.TransactionArgs{
|
||||||
|
From: &accounts[0].addr,
|
||||||
|
Input: &hexutil.Bytes{0x43}, // blocknumber
|
||||||
|
},
|
||||||
|
config: ð.TraceCallConfig{
|
||||||
|
BlockOverrides: ð.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))},
|
||||||
|
},
|
||||||
|
expectErr: nil,
|
||||||
|
expect: ` {"gas":53018,"failed":false,"returnValue":"","structLogs":[
|
||||||
|
{"pc":0,"op":"NUMBER","gas":9999946984,"gasCost":2,"depth":1,"stack":[]},
|
||||||
|
{"pc":1,"op":"STOP","gas":9999946982,"gasCost":0,"depth":1,"stack":["0x1337"]}]}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, testspec := range testSuite {
|
||||||
|
result, err := tracingAPI.TraceCall(context.Background(), testspec.call, rpc.BlockNumberOrHash{BlockNumber: &testspec.blockNumber}, testspec.config)
|
||||||
|
if testspec.expectErr != nil {
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err).To(Equal(testspec.expectErr))
|
||||||
|
} else {
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
var have *logger.ExecutionResult
|
||||||
|
err := json.Unmarshal(result.(json.RawMessage), &have)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
var want *logger.ExecutionResult
|
||||||
|
err = json.Unmarshal([]byte(testspec.expect), &want)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(have).To(Equal(want))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("debug_traceBlock", func() {
|
||||||
|
It("Works", func() {
|
||||||
|
var testSuite = []struct {
|
||||||
|
blockNumber rpc.BlockNumber
|
||||||
|
config *eth.TraceConfig
|
||||||
|
want string
|
||||||
|
expectErr error
|
||||||
|
}{
|
||||||
|
// Trace genesis block, expect error
|
||||||
|
{
|
||||||
|
blockNumber: rpc.BlockNumber(0),
|
||||||
|
expectErr: errors.New("genesis is not traceable"),
|
||||||
|
},
|
||||||
|
// Trace head block
|
||||||
|
{
|
||||||
|
blockNumber: rpc.BlockNumber(genBlocks),
|
||||||
|
want: `[{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`,
|
||||||
|
},
|
||||||
|
// Trace non-existent block
|
||||||
|
{
|
||||||
|
blockNumber: rpc.BlockNumber(genBlocks + 1),
|
||||||
|
expectErr: fmt.Errorf("block #%d not found", genBlocks+1),
|
||||||
|
},
|
||||||
|
// Trace latest block
|
||||||
|
{
|
||||||
|
blockNumber: rpc.LatestBlockNumber,
|
||||||
|
want: `[{"result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`,
|
||||||
|
},
|
||||||
|
// Trace pending block
|
||||||
|
{
|
||||||
|
blockNumber: rpc.PendingBlockNumber,
|
||||||
|
expectErr: errors.New("pending block number not supported"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testSuite {
|
||||||
|
result, err := tracingAPI.TraceBlockByNumber(context.Background(), tc.blockNumber, tc.config)
|
||||||
|
if tc.expectErr != nil {
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err).To(Equal(tc.expectErr))
|
||||||
|
} else {
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
have, _ := json.Marshal(result)
|
||||||
|
want := tc.want
|
||||||
|
Expect(string(have)).To(Equal(want))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
type testBackend struct {
|
||||||
|
chainConfig *params.ChainConfig
|
||||||
|
engine consensus.Engine
|
||||||
|
chaindb ethdb.Database
|
||||||
|
chain *core.BlockChain
|
||||||
|
|
||||||
|
refHook func() // Hook is invoked when the requested state is referenced
|
||||||
|
relHook func() // Hook is invoked when the requested state is released
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
||||||
|
return b.chain.GetHeaderByHash(hash), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
|
||||||
|
if number == rpc.PendingBlockNumber || number == rpc.LatestBlockNumber {
|
||||||
|
return b.chain.CurrentHeader(), nil
|
||||||
|
}
|
||||||
|
return b.chain.GetHeaderByNumber(uint64(number)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
||||||
|
return b.chain.GetBlockByHash(hash), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
|
||||||
|
if number == rpc.PendingBlockNumber || number == rpc.LatestBlockNumber {
|
||||||
|
return b.chain.GetBlockByNumber(b.chain.CurrentBlock().Number.Uint64()), nil
|
||||||
|
}
|
||||||
|
return b.chain.GetBlockByNumber(uint64(number)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
|
||||||
|
tx, hash, blockNumber, index := rawdb.ReadTransaction(b.chaindb, txHash)
|
||||||
|
return tx, hash, blockNumber, index, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *testBackend) RPCGasCap() uint64 {
|
||||||
|
return 25000000
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *testBackend) ChainConfig() *params.ChainConfig {
|
||||||
|
return b.chainConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *testBackend) Engine() consensus.Engine {
|
||||||
|
return b.engine
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *testBackend) ChainDb() ethdb.Database {
|
||||||
|
return b.chaindb
|
||||||
|
}
|
||||||
|
|
||||||
|
// teardown releases the associated resources.
|
||||||
|
func (b *testBackend) teardown() {
|
||||||
|
b.chain.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errStateNotFound = errors.New("state not found")
|
||||||
|
errBlockNotFound = errors.New("block not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *testBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error) {
|
||||||
|
statedb, err := b.chain.StateAt(block.Root())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errStateNotFound
|
||||||
|
}
|
||||||
|
if b.refHook != nil {
|
||||||
|
b.refHook()
|
||||||
|
}
|
||||||
|
release := func() {
|
||||||
|
if b.relHook != nil {
|
||||||
|
b.relHook()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return statedb, release, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
|
||||||
|
parent := b.chain.GetBlock(block.ParentHash(), block.NumberU64()-1)
|
||||||
|
if parent == nil {
|
||||||
|
return nil, vm.BlockContext{}, nil, nil, errBlockNotFound
|
||||||
|
}
|
||||||
|
statedb, release, err := b.StateAtBlock(ctx, parent, reexec, nil, true, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, vm.BlockContext{}, nil, nil, errStateNotFound
|
||||||
|
}
|
||||||
|
if txIndex == 0 && len(block.Transactions()) == 0 {
|
||||||
|
return nil, vm.BlockContext{}, statedb, release, nil
|
||||||
|
}
|
||||||
|
// Recompute transactions up to the target index.
|
||||||
|
signer := types.MakeSigner(b.chainConfig, block.Number())
|
||||||
|
for idx, tx := range block.Transactions() {
|
||||||
|
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||||
|
txContext := core.NewEVMTxContext(msg)
|
||||||
|
context := core.NewEVMBlockContext(block.Header(), b.chain, nil)
|
||||||
|
if idx == txIndex {
|
||||||
|
return msg, context, statedb, release, nil
|
||||||
|
}
|
||||||
|
vmenv := vm.NewEVM(context, txContext, statedb, b.chainConfig, vm.Config{})
|
||||||
|
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
|
||||||
|
return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
|
||||||
|
}
|
||||||
|
statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
|
||||||
|
}
|
||||||
|
return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash())
|
||||||
|
}
|
||||||
|
|
||||||
|
// testBackend creates a new test backend. OBS: After test is done, teardown must be
|
||||||
|
// invoked in order to release associated resources.
|
||||||
|
func newTestBackend(n int, gspec *core.Genesis, generator func(i int, b *core.BlockGen)) (*testBackend, types.Blocks, []types.Receipts) {
|
||||||
|
backend := &testBackend{
|
||||||
|
chainConfig: gspec.Config,
|
||||||
|
engine: ethash.NewFaker(),
|
||||||
|
chaindb: rawdb.NewMemoryDatabase(),
|
||||||
|
}
|
||||||
|
// Generate blocks for testing
|
||||||
|
_, blocks, receipts := core.GenerateChainWithGenesis(gspec, backend.engine, n, generator)
|
||||||
|
|
||||||
|
// Import the canonical chain
|
||||||
|
cacheConfig := &core.CacheConfig{
|
||||||
|
TrieCleanLimit: 256,
|
||||||
|
TrieDirtyLimit: 256,
|
||||||
|
TrieTimeLimit: 5 * time.Minute,
|
||||||
|
SnapshotLimit: 0,
|
||||||
|
TrieDirtyDisabled: true, // Archive mode
|
||||||
|
}
|
||||||
|
chain, err := core.NewBlockChain(backend.chaindb, cacheConfig, gspec, nil, backend.engine, vm.Config{}, nil, nil)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
n, err = chain.InsertChain(blocks)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
backend.chain = chain
|
||||||
|
return backend, blocks, receipts
|
||||||
|
}
|
||||||
|
|
||||||
|
type Account struct {
|
||||||
|
key *ecdsa.PrivateKey
|
||||||
|
addr common.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
type Accounts []Account
|
||||||
|
|
||||||
|
func (a Accounts) Len() int { return len(a) }
|
||||||
|
func (a Accounts) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a Accounts) Less(i, j int) bool { return bytes.Compare(a[i].addr.Bytes(), a[j].addr.Bytes()) < 0 }
|
||||||
|
|
||||||
|
func newAccounts(n int) (accounts Accounts) {
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
key, _ := crypto.GenerateKey()
|
||||||
|
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
accounts = append(accounts, Account{key: key, addr: addr})
|
||||||
|
}
|
||||||
|
sort.Sort(accounts)
|
||||||
|
return accounts
|
||||||
|
}
|
29
pkg/eth/debug_test/eth_suite_test.go
Normal file
29
pkg/eth/debug_test/eth_suite_test.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// VulcanizeDB
|
||||||
|
// Copyright © 2019 Vulcanize
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package eth_debug_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestETHSuite(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "ipld-eth-server/pkg/eth/state_test")
|
||||||
|
}
|
@ -23,7 +23,7 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/cerc-io/plugeth-statediff"
|
statediff "github.com/cerc-io/plugeth-statediff"
|
||||||
"github.com/cerc-io/plugeth-statediff/adapt"
|
"github.com/cerc-io/plugeth-statediff/adapt"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/cerc-io/plugeth-statediff"
|
statediff "github.com/cerc-io/plugeth-statediff"
|
||||||
"github.com/cerc-io/plugeth-statediff/adapt"
|
"github.com/cerc-io/plugeth-statediff/adapt"
|
||||||
"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/state"
|
||||||
|
476
pkg/eth/tracing.go
Normal file
476
pkg/eth/tracing.go
Normal file
@ -0,0 +1,476 @@
|
|||||||
|
package eth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
ipld_direct_state "github.com/cerc-io/ipld-eth-statedb/direct_by_leaf"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// defaultTraceTimeout is the amount of time a single transaction can execute
|
||||||
|
// by default before being forcefully aborted.
|
||||||
|
defaultTraceTimeout = 5 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// TracingAPI is the collection of tracing APIs exposed over the private debugging endpoint.
|
||||||
|
type TracingAPI struct {
|
||||||
|
backend *Backend
|
||||||
|
rpc *rpc.Client
|
||||||
|
config APIConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTracingAPI creates a new TracingAPI with the provided underlying Backend
|
||||||
|
func NewTracingAPI(b *Backend, client *rpc.Client, config APIConfig) (*TracingAPI, error) {
|
||||||
|
if b == nil {
|
||||||
|
return nil, errors.New("ipld-eth-server must be configured with an ethereum backend")
|
||||||
|
}
|
||||||
|
if config.ForwardEthCalls && client == nil {
|
||||||
|
return nil, errors.New("ipld-eth-server is configured to forward eth_calls to proxy node but no proxy node is configured")
|
||||||
|
}
|
||||||
|
if config.ForwardGetStorageAt && client == nil {
|
||||||
|
return nil, errors.New("ipld-eth-server is configured to forward eth_getStorageAt to proxy node but no proxy node is configured")
|
||||||
|
}
|
||||||
|
if config.ProxyOnError && client == nil {
|
||||||
|
return nil, errors.New("ipld-eth-server is configured to forward all calls to proxy node on errors but no proxy node is configured")
|
||||||
|
}
|
||||||
|
return &TracingAPI{
|
||||||
|
backend: b,
|
||||||
|
rpc: client,
|
||||||
|
config: config,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceConfig holds extra parameters to trace functions.
|
||||||
|
type TraceConfig struct {
|
||||||
|
*logger.Config
|
||||||
|
Tracer *string
|
||||||
|
Timeout *string
|
||||||
|
Reexec *uint64
|
||||||
|
// Config specific to given tracer. Note struct logger
|
||||||
|
// config are historically embedded in main object.
|
||||||
|
TracerConfig json.RawMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceCallConfig is the config for traceCall API. It holds one more
|
||||||
|
// field to override the state for tracing.
|
||||||
|
type TraceCallConfig struct {
|
||||||
|
TraceConfig
|
||||||
|
StateOverrides *StateOverride
|
||||||
|
BlockOverrides *BlockOverrides
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceCall lets you trace a given eth_call. It collects the structured logs
|
||||||
|
// created during the execution of EVM if the given transaction was added on
|
||||||
|
// top of the provided block and returns them as a JSON object.
|
||||||
|
func (api *TracingAPI) TraceCall(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) {
|
||||||
|
trace, err := api.localTraceCall(ctx, args, blockNrOrHash, config)
|
||||||
|
if trace != nil && err == nil {
|
||||||
|
return trace, nil
|
||||||
|
}
|
||||||
|
if api.config.ProxyOnError {
|
||||||
|
var res interface{}
|
||||||
|
if err := api.rpc.CallContext(ctx, &res, "debug_traceCall", args, blockNrOrHash, config); res != nil && err == nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *TracingAPI) localTraceCall(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) {
|
||||||
|
// Try to retrieve the specified block
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
block *types.Block
|
||||||
|
)
|
||||||
|
if hash, ok := blockNrOrHash.Hash(); ok {
|
||||||
|
block, err = api.blockByHash(ctx, hash)
|
||||||
|
} else if number, ok := blockNrOrHash.Number(); ok {
|
||||||
|
if number == rpc.PendingBlockNumber {
|
||||||
|
// We don't have access to the miner here. For tracing 'future' transactions,
|
||||||
|
// it can be done with block- and state-overrides instead, which offers
|
||||||
|
// more flexibility and stability than trying to trace on 'pending', since
|
||||||
|
// the contents of 'pending' is unstable and probably not a true representation
|
||||||
|
// of what the next actual block is likely to contain.
|
||||||
|
return nil, errors.New("tracing on top of pending is not supported")
|
||||||
|
}
|
||||||
|
block, err = api.blockByNumber(ctx, number)
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("invalid arguments; neither block nor hash specified")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stateDB, _, err := api.backend.IPLDDirectStateDBAndHeaderByNumberOrHash(ctx, rpc.BlockNumberOrHashWithHash(block.Hash(), true))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||||
|
// Apply the customization rules if required.
|
||||||
|
if config != nil {
|
||||||
|
if err := config.StateOverrides.Apply(stateDB); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.BlockOverrides.Apply(&vmctx)
|
||||||
|
}
|
||||||
|
// Execute the trace
|
||||||
|
msg, err := args.ToMessage(api.backend.RPCGasCap(), block.BaseFee())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var traceConfig *TraceConfig
|
||||||
|
if config != nil {
|
||||||
|
traceConfig = &config.TraceConfig
|
||||||
|
}
|
||||||
|
return api.traceTx(ctx, msg, new(tracers.Context), vmctx, stateDB, traceConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// traceTx configures a new tracer according to the provided configuration, and
|
||||||
|
// executes the given message in the provided environment. The return value will
|
||||||
|
// be tracer dependent.
|
||||||
|
func (api *TracingAPI) traceTx(ctx context.Context, message *core.Message, txctx *tracers.Context, vmctx vm.BlockContext, statedb *ipld_direct_state.StateDB, config *TraceConfig) (interface{}, error) {
|
||||||
|
var (
|
||||||
|
tracer tracers.Tracer
|
||||||
|
err error
|
||||||
|
timeout = defaultTraceTimeout
|
||||||
|
txContext = core.NewEVMTxContext(message)
|
||||||
|
)
|
||||||
|
if config == nil {
|
||||||
|
config = &TraceConfig{}
|
||||||
|
}
|
||||||
|
// Default tracer is the struct logger
|
||||||
|
tracer = logger.NewStructLogger(config.Config)
|
||||||
|
if config.Tracer != nil {
|
||||||
|
tracer, err = tracers.DefaultDirectory.New(*config.Tracer, txctx, config.TracerConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Tracer: tracer, NoBaseFee: true})
|
||||||
|
|
||||||
|
// Define a meaningful timeout of a single transaction trace
|
||||||
|
if config.Timeout != nil {
|
||||||
|
if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
|
go func() {
|
||||||
|
<-deadlineCtx.Done()
|
||||||
|
if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) {
|
||||||
|
tracer.Stop(errors.New("execution timeout"))
|
||||||
|
// Stop evm execution. Note cancellation is not necessarily immediate.
|
||||||
|
vmenv.Cancel()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Call Prepare to clear out the statedb access list
|
||||||
|
statedb.SetTxContext(txctx.TxHash, txctx.TxIndex)
|
||||||
|
if _, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.GasLimit)); err != nil {
|
||||||
|
return nil, fmt.Errorf("tracing failed: %w", err)
|
||||||
|
}
|
||||||
|
return tracer.GetResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
// chainContext constructs the context reader which is used by the evm for reading
|
||||||
|
// the necessary chain context.
|
||||||
|
func (api *TracingAPI) chainContext(ctx context.Context) core.ChainContext {
|
||||||
|
return &chainContext{api: api, ctx: ctx}
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockByNumber is the wrapper of the chain access function offered by the backend.
|
||||||
|
// It will return an error if the block is not found.
|
||||||
|
func (api *TracingAPI) blockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
|
||||||
|
block, err := api.backend.BlockByNumber(ctx, number)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if block == nil {
|
||||||
|
return nil, fmt.Errorf("block #%d not found", number)
|
||||||
|
}
|
||||||
|
return block, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockByHash is the wrapper of the chain access function offered by the backend.
|
||||||
|
// It will return an error if the block is not found.
|
||||||
|
func (api *TracingAPI) blockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
||||||
|
block, err := api.backend.BlockByHash(ctx, hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if block == nil {
|
||||||
|
return nil, fmt.Errorf("block %s not found", hash.Hex())
|
||||||
|
}
|
||||||
|
return block, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// txTraceTask represents a single transaction trace task when an entire block
|
||||||
|
// is being traced.
|
||||||
|
type txTraceTask struct {
|
||||||
|
statedb *ipld_direct_state.StateDB // Intermediate state prepped for tracing
|
||||||
|
index int // Transaction offset in the block
|
||||||
|
}
|
||||||
|
|
||||||
|
// txTraceResult is the result of a single transaction trace.
|
||||||
|
type txTraceResult struct {
|
||||||
|
Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer
|
||||||
|
Error string `json:"error,omitempty"` // Trace failure produced by the tracer
|
||||||
|
}
|
||||||
|
|
||||||
|
var noGenesisErr = errors.New("genesis is not traceable")
|
||||||
|
|
||||||
|
// TraceBlockByNumber returns the structured logs created during the execution of
|
||||||
|
// EVM and returns them as a JSON object.
|
||||||
|
func (api *TracingAPI) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) {
|
||||||
|
if number == 0 {
|
||||||
|
return nil, noGenesisErr
|
||||||
|
}
|
||||||
|
block, err := api.blockByNumber(ctx, number)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
trace, err := api.traceBlock(ctx, block, config)
|
||||||
|
if trace != nil && err == nil {
|
||||||
|
return trace, nil
|
||||||
|
}
|
||||||
|
if api.config.ProxyOnError {
|
||||||
|
var res []*txTraceResult
|
||||||
|
if err := api.rpc.CallContext(ctx, &res, "debug_traceBlockByNumber", number, config); res != nil && err == nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceBlockByHash returns the structured logs created during the execution of
|
||||||
|
// EVM and returns them as a JSON object.
|
||||||
|
func (api *TracingAPI) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) {
|
||||||
|
block, err := api.blockByHash(ctx, hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if block.NumberU64() == 0 {
|
||||||
|
return nil, noGenesisErr
|
||||||
|
}
|
||||||
|
trace, err := api.traceBlock(ctx, block, config)
|
||||||
|
if trace != nil && err == nil {
|
||||||
|
return trace, nil
|
||||||
|
}
|
||||||
|
if api.config.ProxyOnError {
|
||||||
|
var res []*txTraceResult
|
||||||
|
if err := api.rpc.CallContext(ctx, &res, "debug_traceBlockByHash", hash, config); res != nil && err == nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceBlock returns the structured logs created during the execution of EVM
|
||||||
|
// and returns them as a JSON object.
|
||||||
|
func (api *TracingAPI) TraceBlock(ctx context.Context, blob hexutil.Bytes, config *TraceConfig) ([]*txTraceResult, error) {
|
||||||
|
trace, err := api.localTraceBlock(ctx, blob, config)
|
||||||
|
if trace != nil && err == nil {
|
||||||
|
return trace, nil
|
||||||
|
}
|
||||||
|
if api.config.ProxyOnError {
|
||||||
|
var res []*txTraceResult
|
||||||
|
if err := api.rpc.CallContext(ctx, &res, "debug_traceBlock", blob, config); res != nil && err == nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *TracingAPI) localTraceBlock(ctx context.Context, blob hexutil.Bytes, config *TraceConfig) ([]*txTraceResult, error) {
|
||||||
|
block := new(types.Block)
|
||||||
|
if err := rlp.Decode(bytes.NewReader(blob), block); err != nil {
|
||||||
|
return nil, fmt.Errorf("could not decode block: %v", err)
|
||||||
|
}
|
||||||
|
return api.traceBlock(ctx, block, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// traceBlock configures a new tracer according to the provided configuration, and
|
||||||
|
// executes all the transactions contained within. The return value will be one item
|
||||||
|
// per transaction, dependent on the requested tracer.
|
||||||
|
func (api *TracingAPI) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) {
|
||||||
|
if block.NumberU64() == 0 {
|
||||||
|
return nil, errors.New("genesis is not traceable")
|
||||||
|
}
|
||||||
|
stateDB, _, err := api.backend.IPLDDirectStateDBAndHeaderByNumberOrHash(ctx, rpc.BlockNumberOrHashWithHash(block.ParentHash(), true))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// JS tracers have high overhead. In this case run a parallel
|
||||||
|
// process that generates states in one thread and traces txes
|
||||||
|
// in separate worker threads.
|
||||||
|
if config != nil && config.Tracer != nil && *config.Tracer != "" {
|
||||||
|
if isJS := tracers.DefaultDirectory.IsJS(*config.Tracer); isJS {
|
||||||
|
return api.traceBlockParallel(ctx, block, stateDB, config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Native tracers have low overhead
|
||||||
|
var (
|
||||||
|
txs = block.Transactions()
|
||||||
|
blockHash = block.Hash()
|
||||||
|
is158 = api.backend.ChainConfig().IsEIP158(block.Number())
|
||||||
|
blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||||
|
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
|
||||||
|
results = make([]*txTraceResult, len(txs))
|
||||||
|
)
|
||||||
|
for i, tx := range txs {
|
||||||
|
// Generate the next state snapshot fast without tracing
|
||||||
|
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||||
|
txctx := &tracers.Context{
|
||||||
|
BlockHash: blockHash,
|
||||||
|
BlockNumber: block.Number(),
|
||||||
|
TxIndex: i,
|
||||||
|
TxHash: tx.Hash(),
|
||||||
|
}
|
||||||
|
res, err := api.traceTx(ctx, msg, txctx, blockCtx, stateDB, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
results[i] = &txTraceResult{Result: res}
|
||||||
|
// Finalize the state so any modifications are written to the trie
|
||||||
|
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
|
||||||
|
stateDB.Finalise(is158)
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// traceBlockParallel is for tracers that have a high overhead (read JS tracers). One thread
|
||||||
|
// runs along and executes txes without tracing enabled to generate their prestate.
|
||||||
|
// Worker threads take the tasks and the prestate and trace them.
|
||||||
|
func (api *TracingAPI) traceBlockParallel(ctx context.Context, block *types.Block, statedb *ipld_direct_state.StateDB, config *TraceConfig) ([]*txTraceResult, error) {
|
||||||
|
// Execute all the transaction contained within the block concurrently
|
||||||
|
var (
|
||||||
|
txs = block.Transactions()
|
||||||
|
blockHash = block.Hash()
|
||||||
|
blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||||
|
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
|
||||||
|
results = make([]*txTraceResult, len(txs))
|
||||||
|
pend sync.WaitGroup
|
||||||
|
)
|
||||||
|
threads := runtime.NumCPU()
|
||||||
|
if threads > len(txs) {
|
||||||
|
threads = len(txs)
|
||||||
|
}
|
||||||
|
jobs := make(chan *txTraceTask, threads)
|
||||||
|
for th := 0; th < threads; th++ {
|
||||||
|
pend.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer pend.Done()
|
||||||
|
// Fetch and execute the next transaction trace tasks
|
||||||
|
for task := range jobs {
|
||||||
|
msg, _ := core.TransactionToMessage(txs[task.index], signer, block.BaseFee())
|
||||||
|
txctx := &tracers.Context{
|
||||||
|
BlockHash: blockHash,
|
||||||
|
BlockNumber: block.Number(),
|
||||||
|
TxIndex: task.index,
|
||||||
|
TxHash: txs[task.index].Hash(),
|
||||||
|
}
|
||||||
|
res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config)
|
||||||
|
if err != nil {
|
||||||
|
results[task.index] = &txTraceResult{Error: err.Error()}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
results[task.index] = &txTraceResult{Result: res}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feed the transactions into the tracers and return
|
||||||
|
var failed error
|
||||||
|
txloop:
|
||||||
|
for i, tx := range txs {
|
||||||
|
// Send the trace task over for execution
|
||||||
|
task := &txTraceTask{statedb: statedb.Copy(), index: i}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
failed = ctx.Err()
|
||||||
|
break txloop
|
||||||
|
case jobs <- task:
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the next state snapshot fast without tracing
|
||||||
|
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||||
|
statedb.SetTxContext(tx.Hash(), i)
|
||||||
|
vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{})
|
||||||
|
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)); err != nil {
|
||||||
|
failed = err
|
||||||
|
break txloop
|
||||||
|
}
|
||||||
|
// Finalize the state so any modifications are written to the trie
|
||||||
|
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
|
||||||
|
statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
|
||||||
|
}
|
||||||
|
|
||||||
|
close(jobs)
|
||||||
|
pend.Wait()
|
||||||
|
|
||||||
|
// If execution failed in between, abort
|
||||||
|
if failed != nil {
|
||||||
|
return nil, failed
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockByNumberAndHash is the wrapper of the chain access function offered by
|
||||||
|
// the backend. It will return an error if the block is not found.
|
||||||
|
//
|
||||||
|
// Note this function is friendly for the light client which can only retrieve the
|
||||||
|
// historical(before the CHT) header/block by number.
|
||||||
|
func (api *TracingAPI) blockByNumberAndHash(ctx context.Context, number rpc.BlockNumber, hash common.Hash) (*types.Block, error) {
|
||||||
|
block, err := api.blockByNumber(ctx, number)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if block.Hash() == hash {
|
||||||
|
return block, nil
|
||||||
|
}
|
||||||
|
return api.blockByHash(ctx, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
type chainContext struct {
|
||||||
|
api *TracingAPI
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *chainContext) Engine() consensus.Engine {
|
||||||
|
return context.api.backend.Engine()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *chainContext) GetHeader(hash common.Hash, number uint64) *types.Header {
|
||||||
|
header, err := context.api.backend.HeaderByNumber(context.ctx, rpc.BlockNumber(number))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if header.Hash() == hash {
|
||||||
|
return header
|
||||||
|
}
|
||||||
|
header, err = context.api.backend.HeaderByHash(context.ctx, hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return header
|
||||||
|
}
|
@ -1,9 +1,15 @@
|
|||||||
package eth
|
package eth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TransactionArgs represents the arguments to construct a new transaction
|
// TransactionArgs represents the arguments to construct a new transaction
|
||||||
@ -47,3 +53,85 @@ func (args *TransactionArgs) data() []byte {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToMessage converts the transaction arguments to the Message type used by the
|
||||||
|
// core evm. This method is used in calls and traces that do not require a real
|
||||||
|
// live transaction.
|
||||||
|
func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (*core.Message, error) {
|
||||||
|
// Reject invalid combinations of pre- and post-1559 fee styles
|
||||||
|
if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) {
|
||||||
|
return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
|
||||||
|
}
|
||||||
|
// Set sender address or use zero address if none specified.
|
||||||
|
addr := args.from()
|
||||||
|
|
||||||
|
// Set default gas & gas price if none were set
|
||||||
|
gas := globalGasCap
|
||||||
|
if gas == 0 {
|
||||||
|
gas = uint64(math.MaxUint64 / 2)
|
||||||
|
}
|
||||||
|
if args.Gas != nil {
|
||||||
|
gas = uint64(*args.Gas)
|
||||||
|
}
|
||||||
|
if globalGasCap != 0 && globalGasCap < gas {
|
||||||
|
logrus.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap)
|
||||||
|
gas = globalGasCap
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
gasPrice *big.Int
|
||||||
|
gasFeeCap *big.Int
|
||||||
|
gasTipCap *big.Int
|
||||||
|
)
|
||||||
|
if baseFee == nil {
|
||||||
|
// If there's no basefee, then it must be a non-1559 execution
|
||||||
|
gasPrice = new(big.Int)
|
||||||
|
if args.GasPrice != nil {
|
||||||
|
gasPrice = args.GasPrice.ToInt()
|
||||||
|
}
|
||||||
|
gasFeeCap, gasTipCap = gasPrice, gasPrice
|
||||||
|
} else {
|
||||||
|
// A basefee is provided, necessitating 1559-type execution
|
||||||
|
if args.GasPrice != nil {
|
||||||
|
// User specified the legacy gas field, convert to 1559 gas typing
|
||||||
|
gasPrice = args.GasPrice.ToInt()
|
||||||
|
gasFeeCap, gasTipCap = gasPrice, gasPrice
|
||||||
|
} else {
|
||||||
|
// User specified 1559 gas fields (or none), use those
|
||||||
|
gasFeeCap = new(big.Int)
|
||||||
|
if args.MaxFeePerGas != nil {
|
||||||
|
gasFeeCap = args.MaxFeePerGas.ToInt()
|
||||||
|
}
|
||||||
|
gasTipCap = new(big.Int)
|
||||||
|
if args.MaxPriorityFeePerGas != nil {
|
||||||
|
gasTipCap = args.MaxPriorityFeePerGas.ToInt()
|
||||||
|
}
|
||||||
|
// Backfill the legacy gasPrice for EVM execution, unless we're all zeroes
|
||||||
|
gasPrice = new(big.Int)
|
||||||
|
if gasFeeCap.BitLen() > 0 || gasTipCap.BitLen() > 0 {
|
||||||
|
gasPrice = math.BigMin(new(big.Int).Add(gasTipCap, baseFee), gasFeeCap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value := new(big.Int)
|
||||||
|
if args.Value != nil {
|
||||||
|
value = args.Value.ToInt()
|
||||||
|
}
|
||||||
|
data := args.data()
|
||||||
|
var accessList types.AccessList
|
||||||
|
if args.AccessList != nil {
|
||||||
|
accessList = *args.AccessList
|
||||||
|
}
|
||||||
|
msg := &core.Message{
|
||||||
|
From: addr,
|
||||||
|
To: args.To,
|
||||||
|
Value: value,
|
||||||
|
GasLimit: gas,
|
||||||
|
GasPrice: gasPrice,
|
||||||
|
GasFeeCap: gasFeeCap,
|
||||||
|
GasTipCap: gasTipCap,
|
||||||
|
Data: data,
|
||||||
|
AccessList: accessList,
|
||||||
|
SkipAccountChecks: true,
|
||||||
|
}
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
@ -110,6 +110,8 @@ type Config struct {
|
|||||||
ProxyOnError bool
|
ProxyOnError bool
|
||||||
GetLogsBlockLimit int64
|
GetLogsBlockLimit int64
|
||||||
NodeNetworkID string
|
NodeNetworkID string
|
||||||
|
TracingEnabled bool
|
||||||
|
TracingPublic bool
|
||||||
|
|
||||||
// Cache configuration.
|
// Cache configuration.
|
||||||
GroupCache *ethServerShared.GroupCacheConfig
|
GroupCache *ethServerShared.GroupCacheConfig
|
||||||
@ -154,6 +156,8 @@ func NewConfig() (*Config, error) {
|
|||||||
c.ForwardGetStorageAt = viper.GetBool("ethereum.forwardGetStorageAt")
|
c.ForwardGetStorageAt = viper.GetBool("ethereum.forwardGetStorageAt")
|
||||||
c.ProxyOnError = viper.GetBool("ethereum.proxyOnError")
|
c.ProxyOnError = viper.GetBool("ethereum.proxyOnError")
|
||||||
c.EthHttpEndpoint = ethHTTPEndpoint
|
c.EthHttpEndpoint = ethHTTPEndpoint
|
||||||
|
c.TracingEnabled = viper.GetBool("ethereum.tracingEnabled")
|
||||||
|
c.TracingPublic = viper.GetBool("ethereum.tracingPublic")
|
||||||
|
|
||||||
if viper.IsSet("ethereum.getLogsBlockLimit") {
|
if viper.IsSet("ethereum.getLogsBlockLimit") {
|
||||||
c.GetLogsBlockLimit = viper.GetInt64("ethereum.getLogsBlockLimit")
|
c.GetLogsBlockLimit = viper.GetInt64("ethereum.getLogsBlockLimit")
|
||||||
|
@ -78,6 +78,10 @@ type Service struct {
|
|||||||
proxyOnError bool
|
proxyOnError bool
|
||||||
// eth node network id
|
// eth node network id
|
||||||
nodeNetworkId string
|
nodeNetworkId string
|
||||||
|
// tracing enabled
|
||||||
|
tracingEnabled bool
|
||||||
|
// tracing public
|
||||||
|
tracingPublic bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer creates a new Server using an underlying Service struct
|
// NewServer creates a new Server using an underlying Service struct
|
||||||
@ -93,6 +97,8 @@ func NewServer(settings *Config) (Server, error) {
|
|||||||
sap.getLogsBlockLimit = settings.GetLogsBlockLimit
|
sap.getLogsBlockLimit = settings.GetLogsBlockLimit
|
||||||
sap.proxyOnError = settings.ProxyOnError
|
sap.proxyOnError = settings.ProxyOnError
|
||||||
sap.nodeNetworkId = settings.NodeNetworkID
|
sap.nodeNetworkId = settings.NodeNetworkID
|
||||||
|
sap.tracingEnabled = settings.TracingEnabled
|
||||||
|
sap.tracingPublic = settings.TracingPublic
|
||||||
var err error
|
var err error
|
||||||
sap.backend, err = eth.NewEthBackend(sap.db, ð.Config{
|
sap.backend, err = eth.NewEthBackend(sap.db, ð.Config{
|
||||||
ChainConfig: settings.ChainConfig,
|
ChainConfig: settings.ChainConfig,
|
||||||
@ -138,9 +144,10 @@ func (sap *Service) APIs() []rpc.API {
|
|||||||
log.Fatalf("unable to create public eth api: %v", err)
|
log.Fatalf("unable to create public eth api: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this uses stubbed StateAtBlock and StateAtTransaction methods and so does not support debug_traceBlock and debug_traceCall
|
||||||
debugTracerAPI := tracers.APIs(&debug.Backend{Backend: *sap.backend})[0]
|
debugTracerAPI := tracers.APIs(&debug.Backend{Backend: *sap.backend})[0]
|
||||||
|
|
||||||
return append(apis,
|
apis = append(apis,
|
||||||
rpc.API{
|
rpc.API{
|
||||||
Namespace: eth.APIName,
|
Namespace: eth.APIName,
|
||||||
Version: eth.APIVersion,
|
Version: eth.APIVersion,
|
||||||
@ -149,6 +156,22 @@ func (sap *Service) APIs() []rpc.API {
|
|||||||
},
|
},
|
||||||
debugTracerAPI,
|
debugTracerAPI,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if sap.tracingEnabled {
|
||||||
|
tracingAPI, err := eth.NewTracingAPI(sap.backend, sap.client, conf)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to create tracing api: %v", err)
|
||||||
|
}
|
||||||
|
apis = append(apis, rpc.API{
|
||||||
|
// Uses same namespace as stubbed debug API above
|
||||||
|
// Need to test if this will properly overlay and form a union with the existing methods supported by that
|
||||||
|
// Or if only one is exposed
|
||||||
|
Namespace: "debug",
|
||||||
|
Service: tracingAPI,
|
||||||
|
Public: sap.tracingPublic,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return apis
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve listens for incoming converter data off the screenAndServePayload from the Sync process
|
// Serve listens for incoming converter data off the screenAndServePayload from the Sync process
|
||||||
|
@ -5,7 +5,7 @@ services:
|
|||||||
restart: on-failure
|
restart: on-failure
|
||||||
depends_on:
|
depends_on:
|
||||||
- ipld-eth-db
|
- ipld-eth-db
|
||||||
image: git.vdb.to/cerc-io/ipld-eth-db/ipld-eth-db:v5.0.5-alpha
|
image: git.vdb.to/cerc-io/ipld-eth-db/ipld-eth-db:v5.2.1-alpha
|
||||||
environment:
|
environment:
|
||||||
DATABASE_USER: "vdbm"
|
DATABASE_USER: "vdbm"
|
||||||
DATABASE_NAME: "cerc_testing"
|
DATABASE_NAME: "cerc_testing"
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
git.vdb.to/cerc-io/ipld-eth-db v5.2.0-alpha
|
git.vdb.to/cerc-io/ipld-eth-db v5.2.1-alpha
|
||||||
git.vdb.to/cerc-io/plugeth-statediff v0.1.1
|
git.vdb.to/cerc-io/plugeth-statediff v0.1.1
|
||||||
|
Loading…
Reference in New Issue
Block a user