Merge pull request #8 from DeBankDeFi/pre-exec

State node: implement pre-execution API
This commit is contained in:
sunny 2021-06-01 16:38:54 +08:00 committed by GitHub
commit 8e1cc27b62
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 315 additions and 16 deletions

View file

@ -18,7 +18,6 @@ package core
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc"
@ -148,3 +147,9 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg)
return applyTransaction(msg, config, bc, author, gp, statedb, header, tx, usedGas, vmenv)
}
func ApplyTransactionForPreExec(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, msg types.Message, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) {
blockContext := NewEVMBlockContext(header, bc, author)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg)
return applyTransaction(msg, config, bc, author, gp, statedb, header, tx, usedGas, vmenv)
}

View file

@ -482,6 +482,25 @@ func (pool *TxPool) Content() (map[common.Address]types.Transactions, map[common
return pending, queued
}
// ContentByAccount retrieves the data content of the transaction pool with a specific account,
// returning the pending as well as queued transaction.
func (pool *TxPool) ContentByAccount(addr common.Address) (pending, queued types.Transactions) {
pool.mu.Lock()
defer pool.mu.Unlock()
list, ok := pool.pending[addr]
if ok {
pending = list.Flatten()
}
list, ok = pool.queue[addr]
if ok {
queued = list.Flatten()
}
return
}
// Pending retrieves all currently processable transactions, grouped by origin
// account and sorted by nonce. The returned transaction set is a copy and can be
// freely modified by calling code.

View file

@ -263,6 +263,10 @@ func (b *EthAPIBackend) TxPoolContent() (map[common.Address]types.Transactions,
return b.eth.TxPool().Content()
}
func (b *EthAPIBackend) TxPoolContentByAccount(addr common.Address) (pending, queued types.Transactions) {
return b.eth.TxPool().ContentByAccount(addr)
}
func (b *EthAPIBackend) TxPool() *core.TxPool {
return b.eth.TxPool()
}

230
eth/api_pre_exec.go Normal file
View file

@ -0,0 +1,230 @@
package eth
import (
"context"
"errors"
"fmt"
"hash"
"math/big"
"time"
"github.com/DeBankDeFi/eth/txtrace"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"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/eth/tracers"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/rpc"
"golang.org/x/crypto/sha3"
)
type helpHash struct {
hashed hash.Hash
}
func newHash() *helpHash {
return &helpHash{hashed: sha3.NewLegacyKeccak256()}
}
func (h *helpHash) Reset() {
h.hashed.Reset()
}
func (h *helpHash) Update(key, val []byte) {
h.hashed.Write(key)
h.hashed.Write(val)
}
func (h *helpHash) Hash() common.Hash {
return common.BytesToHash(h.hashed.Sum(nil))
}
type PreExecTx struct {
ChainId *big.Int
From, To, Data, Value, Gas, GasPrice, Nonce string
}
type preData struct {
block *types.Block
tx *types.Transaction
msg types.Message
stateDb *state.StateDB
header *types.Header
}
// PreExecAPI provides pre exec info for rpc
type PreExecAPI struct {
e *Ethereum
}
func NewPreExecAPI(e *Ethereum) *PreExecAPI {
return &PreExecAPI{e: e}
}
func (api *PreExecAPI) getBlockAndMsg(origin *PreExecTx, number *big.Int) (*types.Block, types.Message) {
fromAddr := common.HexToAddress(origin.From)
toAddr := common.HexToAddress(origin.To)
tx := types.NewTx(&types.LegacyTx{
Nonce: hexutil.MustDecodeUint64(origin.Nonce),
To: &toAddr,
Value: hexutil.MustDecodeBig(origin.Value),
Gas: hexutil.MustDecodeUint64(origin.Gas),
GasPrice: hexutil.MustDecodeBig(origin.GasPrice),
Data: hexutil.MustDecode(origin.Data),
})
number.Add(number, big.NewInt(1))
block := types.NewBlock(
&types.Header{Number: number},
[]*types.Transaction{tx}, nil, nil, newHash())
msg := types.NewMessage(
fromAddr,
&toAddr,
hexutil.MustDecodeUint64(origin.Nonce),
hexutil.MustDecodeBig(origin.Value),
hexutil.MustDecodeUint64(origin.Gas),
hexutil.MustDecodeBig(origin.GasPrice),
hexutil.MustDecode(origin.Data),
nil, false,
)
return block, msg
}
func (api *PreExecAPI) prepareData(ctx context.Context, origin *PreExecTx) (*preData, error) {
var (
d preData
err error
)
bc := api.e.blockchain
d.header, err = api.e.APIBackend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
if err != nil {
return nil, err
}
latestNumber := d.header.Number
parent := api.e.blockchain.GetBlockByNumber(latestNumber.Uint64())
d.stateDb, err = state.New(parent.Header().Root, bc.StateCache(), bc.Snapshots())
if err != nil {
return nil, err
}
d.block, d.msg = api.getBlockAndMsg(origin, latestNumber)
d.tx = d.block.Transactions()[0]
return &d, nil
}
func (api *PreExecAPI) GetLogs(ctx context.Context, origin *PreExecTx) (*types.Receipt, error) {
var (
bc = api.e.blockchain
)
d, err := api.prepareData(ctx, origin)
if err != nil {
return nil, err
}
gas := d.tx.Gas()
gp := new(core.GasPool).AddGas(gas)
d.stateDb.Prepare(d.tx.Hash(), d.block.Hash(), 0)
receipt, err := core.ApplyTransactionForPreExec(
bc.Config(), bc, nil, gp, d.stateDb, d.header, d.tx, d.msg, &gas, *bc.GetVMConfig())
if err != nil {
return nil, err
}
return receipt, nil
}
// TraceTransaction tracing pre-exec transaction object.
func (api *PreExecAPI) TraceTransaction(ctx context.Context, origin *PreExecTx, config *tracers.TraceConfig) (interface{}, error) {
var (
bc = api.e.blockchain
tracer vm.Tracer
err error
)
d, err := api.prepareData(ctx, origin)
if err != nil {
return nil, err
}
txContext := core.NewEVMTxContext(d.msg)
switch {
case config != nil && config.Tracer != nil:
// Define a meaningful timeout of a single transaction trace
timeout := 5 * time.Second
if config.Timeout != nil {
if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
return nil, err
}
}
// Constuct the JavaScript tracer to execute with
if tracer, err = tracers.New(*config.Tracer, txContext); err != nil {
return nil, err
}
// Handle timeouts and RPC cancellations
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
go func() {
<-deadlineCtx.Done()
if deadlineCtx.Err() == context.DeadlineExceeded {
tracer.(*tracers.Tracer).Stop(errors.New("execution timeout"))
}
}()
case config == nil:
fallthrough
default:
// Constuct the txtrace.StructLogger tracer to execute with
tracer = txtrace.NewTraceStructLogger(nil)
}
// Run the transaction with tracing enabled.
vmenv := vm.NewEVM(core.NewEVMBlockContext(d.header, bc, nil), txContext, d.stateDb, bc.Config(), vm.Config{Debug: true, Tracer: tracer})
txIndex := 0
// Call Prepare to clear out the statedb access list
d.stateDb.Prepare(d.tx.Hash(), d.block.Hash(), txIndex)
// check if type of tracer is txtrace.StructLogger, in that case, fill info.
var traceLogger *txtrace.StructLogger
switch tracer.(type) {
case *txtrace.StructLogger:
traceLogger = tracer.(*txtrace.StructLogger)
traceLogger.SetFrom(d.msg.From())
traceLogger.SetTo(d.msg.To())
traceLogger.SetValue(*d.msg.Value())
traceLogger.SetGasUsed(d.tx.Gas())
traceLogger.SetBlockHash(d.block.Hash())
traceLogger.SetBlockNumber(d.block.Number())
traceLogger.SetTx(d.tx.Hash())
traceLogger.SetTxIndex(uint(txIndex))
}
result, err := core.ApplyMessage(vmenv, d.msg, new(core.GasPool).AddGas(d.msg.Gas()))
if err != nil {
return nil, fmt.Errorf("tracing failed: %v", err)
}
// Depending on the tracer type, format and return the output.
switch tracer := tracer.(type) {
case *vm.StructLogger:
// If the result contains a revert reason, return it.
returnVal := fmt.Sprintf("%x", result.Return())
if len(result.Revert()) > 0 {
returnVal = fmt.Sprintf("%x", result.Revert())
}
return &ethapi.ExecutionResult{
Gas: result.UsedGas,
Failed: result.Failed(),
ReturnValue: returnVal,
StructLogs: ethapi.FormatLogs(tracer.StructLogs()),
}, nil
case *tracers.Tracer:
return tracer.GetResult()
case *txtrace.StructLogger:
tracer.Finalize()
return tracer.GetResult(), nil
default:
panic(fmt.Sprintf("bad tracer type %T", tracer))
}
}

View file

@ -345,6 +345,12 @@ func (s *Ethereum) APIs() []rpc.API {
Service: s.netRPCService,
Public: true,
},
{
Namespace: "pre",
Version: "1.0",
Service: NewPreExecAPI(s),
Public: true,
},
}...)
}

11
go.mod
View file

@ -3,10 +3,8 @@ module github.com/ethereum/go-ethereum
go 1.15
require (
github.com/Azure/azure-pipeline-go v0.2.2 // indirect
github.com/Azure/azure-storage-blob-go v0.7.0
github.com/Azure/go-autorest/autorest/adal v0.8.0 // indirect
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect
github.com/DeBankDeFi/eth v1.10.3
github.com/VictoriaMetrics/fastcache v1.5.7
github.com/aws/aws-sdk-go-v2 v1.2.0
github.com/aws/aws-sdk-go-v2/config v1.1.1
@ -18,15 +16,12 @@ require (
github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f
github.com/davecgh/go-spew v1.1.1
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea
github.com/dlclark/regexp2 v1.2.0 // indirect
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf
github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498
github.com/edsrzf/mmap-go v1.0.0
github.com/fatih/color v1.7.0
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff
github.com/go-ole/go-ole v1.2.1 // indirect
github.com/go-sourcemap/sourcemap v2.1.2+incompatible // indirect
github.com/go-stack/stack v1.8.0
github.com/golang/protobuf v1.4.3
github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3
@ -43,10 +38,8 @@ require (
github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e
github.com/julienschmidt/httprouter v1.2.0
github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.0
github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035
github.com/naoina/go-stringutil v0.1.0 // indirect
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416
github.com/olekukonko/tablewriter v0.0.5
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7
@ -57,7 +50,6 @@ require (
github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4
github.com/stretchr/testify v1.7.0
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954
github.com/tklauser/go-sysconf v0.3.5 // indirect
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
@ -67,5 +59,4 @@ require (
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6
gopkg.in/urfave/cli.v1 v1.20.0
gotest.tools v2.2.0+incompatible // indirect
)

5
go.sum
View file

@ -42,6 +42,10 @@ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbt
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/DeBankDeFi/eth v1.10.3-rc1 h1:c/JTocFaCThfMdq/R3dCENWBYj0VQ8YhpCAqEcLKsyA=
github.com/DeBankDeFi/eth v1.10.3-rc1/go.mod h1:UAiugF3perbugrqRhIGYBzh4Rl930XLXkmHlqh0YcQs=
github.com/DeBankDeFi/eth v1.10.3 h1:Niun1jAilCbYaYNOvbiMg3iyochzyFK8zt1VCsH27kk=
github.com/DeBankDeFi/eth v1.10.3/go.mod h1:UAiugF3perbugrqRhIGYBzh4Rl930XLXkmHlqh0YcQs=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
@ -126,6 +130,7 @@ github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ethereum/go-ethereum v1.10.3/go.mod h1:99onQmSd1GRGOziyGldI41YQb7EESX3Q4H41IfJgIQQ=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=

View file

@ -99,6 +99,31 @@ func NewPublicTxPoolAPI(b Backend) *PublicTxPoolAPI {
return &PublicTxPoolAPI{b}
}
// ContentByAccount returns the transactions contained within the tx pool by given account address.
func (s *PublicTxPoolAPI) ContentByAccount(addr common.Address) map[string]map[string]*RPCTransaction {
// map[(queued|pending)]map[tx.Nonce()]tx
content := map[string]map[string]*RPCTransaction{
"pending": make(map[string]*RPCTransaction),
"queued": make(map[string]*RPCTransaction),
}
pending, queued := s.b.TxPoolContentByAccount(addr)
dump := make(map[string]*RPCTransaction)
for _, tx := range pending {
dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx)
}
content["pending"] = dump
dump = make(map[string]*RPCTransaction)
for _, tx := range queued {
dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx)
}
content["queued"] = dump
return content
}
// Content returns the transactions contained within the transaction pool.
func (s *PublicTxPoolAPI) Content() map[string]map[string]map[string]*RPCTransaction {
content := map[string]map[string]map[string]*RPCTransaction{

View file

@ -76,6 +76,7 @@ type Backend interface {
GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error)
Stats() (pending int, queued int)
TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions)
TxPoolContentByAccount(address common.Address) (pending, queued types.Transactions)
SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
// Filter API

View file

@ -773,7 +773,15 @@ web3._extend({
const TxpoolJs = `
web3._extend({
property: 'txpool',
methods: [],
methods:
[
new web3._extend.Method({
name: 'contentByAccount',
call: 'txpool_contentByAccount',
params: 1,
inputFormatter: [null]
}),
],
properties:
[
new web3._extend.Property({

View file

@ -212,6 +212,11 @@ func (b *LesApiBackend) TxPoolContent() (map[common.Address]types.Transactions,
return b.eth.txPool.Content()
}
func (b *LesApiBackend) TxPoolContentByAccount(addr common.Address) (pending, queued types.Transactions) {
// TODO(barryz) not implement for light node.
return nil, nil
}
func (b *LesApiBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
return b.eth.txPool.SubscribeNewTxsEvent(ch)
}

View file

@ -21,10 +21,10 @@ import (
)
const (
VersionMajor = 1 // Major version component of the current release
VersionMinor = 10 // Minor version component of the current release
VersionPatch = 3 // Patch version component of the current release
VersionMeta = "stable" // Version metadata to append to the version string
VersionMajor = 1 // Major version component of the current release
VersionMinor = 10 // Minor version component of the current release
VersionPatch = 3 // Patch version component of the current release
VersionMeta = "unstable-pre-exec" // Version metadata to append to the version string
)
// Version holds the textual version string.