mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 07:37:20 +00:00
internal/ethapi: add eth_SendRawTransactionSync (#32830)
New RPC method eth_sendRawTransactionSync(rawTx, timeoutMs?) that submits a signed tx and blocks until a receipt is available or a timeout elapses. Two CLI flags to tune server-side limits: --rpc.txsync.defaulttimeout (default wait window) --rpc.txsync.maxtimeout (upper bound; requests are clamped) closes https://github.com/ethereum/go-ethereum/issues/32094 --------- Co-authored-by: aodhgan <gawnieg@gmail.com> Co-authored-by: Sina Mahmoodi <itz.s1na@gmail.com>
This commit is contained in:
parent
c37bd67019
commit
ff54ca02de
9 changed files with 359 additions and 26 deletions
|
|
@ -188,6 +188,8 @@ var (
|
|||
utils.AllowUnprotectedTxs,
|
||||
utils.BatchRequestLimit,
|
||||
utils.BatchResponseMaxSize,
|
||||
utils.RPCTxSyncDefaultTimeoutFlag,
|
||||
utils.RPCTxSyncMaxTimeoutFlag,
|
||||
}
|
||||
|
||||
metricsFlags = []cli.Flag{
|
||||
|
|
|
|||
|
|
@ -615,6 +615,18 @@ var (
|
|||
Value: ethconfig.Defaults.LogQueryLimit,
|
||||
Category: flags.APICategory,
|
||||
}
|
||||
RPCTxSyncDefaultTimeoutFlag = &cli.DurationFlag{
|
||||
Name: "rpc.txsync.defaulttimeout",
|
||||
Usage: "Default timeout for eth_sendRawTransactionSync (e.g. 2s, 500ms)",
|
||||
Value: ethconfig.Defaults.TxSyncDefaultTimeout,
|
||||
Category: flags.APICategory,
|
||||
}
|
||||
RPCTxSyncMaxTimeoutFlag = &cli.DurationFlag{
|
||||
Name: "rpc.txsync.maxtimeout",
|
||||
Usage: "Maximum allowed timeout for eth_sendRawTransactionSync (e.g. 5m)",
|
||||
Value: ethconfig.Defaults.TxSyncMaxTimeout,
|
||||
Category: flags.APICategory,
|
||||
}
|
||||
// Authenticated RPC HTTP settings
|
||||
AuthListenFlag = &cli.StringFlag{
|
||||
Name: "authrpc.addr",
|
||||
|
|
@ -1717,6 +1729,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
if ctx.IsSet(RPCGlobalLogQueryLimit.Name) {
|
||||
cfg.LogQueryLimit = ctx.Int(RPCGlobalLogQueryLimit.Name)
|
||||
}
|
||||
if ctx.IsSet(RPCTxSyncDefaultTimeoutFlag.Name) {
|
||||
cfg.TxSyncDefaultTimeout = ctx.Duration(RPCTxSyncDefaultTimeoutFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(RPCTxSyncMaxTimeoutFlag.Name) {
|
||||
cfg.TxSyncMaxTimeout = ctx.Duration(RPCTxSyncMaxTimeoutFlag.Name)
|
||||
}
|
||||
if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 {
|
||||
// If snap-sync is requested, this flag is also required
|
||||
if cfg.SyncMode == ethconfig.SnapSync {
|
||||
|
|
|
|||
|
|
@ -486,3 +486,11 @@ func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, re
|
|||
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
|
||||
return b.eth.stateAtTransaction(ctx, block, txIndex, reexec)
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) RPCTxSyncDefaultTimeout() time.Duration {
|
||||
return b.eth.config.TxSyncDefaultTimeout
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) RPCTxSyncMaxTimeout() time.Duration {
|
||||
return b.eth.config.TxSyncMaxTimeout
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,27 +49,29 @@ var FullNodeGPO = gasprice.Config{
|
|||
|
||||
// Defaults contains default settings for use on the Ethereum main net.
|
||||
var Defaults = Config{
|
||||
HistoryMode: history.KeepAll,
|
||||
SyncMode: SnapSync,
|
||||
NetworkId: 0, // enable auto configuration of networkID == chainID
|
||||
TxLookupLimit: 2350000,
|
||||
TransactionHistory: 2350000,
|
||||
LogHistory: 2350000,
|
||||
StateHistory: params.FullImmutabilityThreshold,
|
||||
DatabaseCache: 512,
|
||||
TrieCleanCache: 154,
|
||||
TrieDirtyCache: 256,
|
||||
TrieTimeout: 60 * time.Minute,
|
||||
SnapshotCache: 102,
|
||||
FilterLogCacheSize: 32,
|
||||
LogQueryLimit: 1000,
|
||||
Miner: miner.DefaultConfig,
|
||||
TxPool: legacypool.DefaultConfig,
|
||||
BlobPool: blobpool.DefaultConfig,
|
||||
RPCGasCap: 50000000,
|
||||
RPCEVMTimeout: 5 * time.Second,
|
||||
GPO: FullNodeGPO,
|
||||
RPCTxFeeCap: 1, // 1 ether
|
||||
HistoryMode: history.KeepAll,
|
||||
SyncMode: SnapSync,
|
||||
NetworkId: 0, // enable auto configuration of networkID == chainID
|
||||
TxLookupLimit: 2350000,
|
||||
TransactionHistory: 2350000,
|
||||
LogHistory: 2350000,
|
||||
StateHistory: params.FullImmutabilityThreshold,
|
||||
DatabaseCache: 512,
|
||||
TrieCleanCache: 154,
|
||||
TrieDirtyCache: 256,
|
||||
TrieTimeout: 60 * time.Minute,
|
||||
SnapshotCache: 102,
|
||||
FilterLogCacheSize: 32,
|
||||
LogQueryLimit: 1000,
|
||||
Miner: miner.DefaultConfig,
|
||||
TxPool: legacypool.DefaultConfig,
|
||||
BlobPool: blobpool.DefaultConfig,
|
||||
RPCGasCap: 50000000,
|
||||
RPCEVMTimeout: 5 * time.Second,
|
||||
GPO: FullNodeGPO,
|
||||
RPCTxFeeCap: 1, // 1 ether
|
||||
TxSyncDefaultTimeout: 20 * time.Second,
|
||||
TxSyncMaxTimeout: 1 * time.Minute,
|
||||
}
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type Config -formats toml -out gen_config.go
|
||||
|
|
@ -183,6 +185,10 @@ type Config struct {
|
|||
|
||||
// OverrideVerkle (TODO: remove after the fork)
|
||||
OverrideVerkle *uint64 `toml:",omitempty"`
|
||||
|
||||
// EIP-7966: eth_sendRawTransactionSync timeouts
|
||||
TxSyncDefaultTimeout time.Duration `toml:",omitempty"`
|
||||
TxSyncMaxTimeout time.Duration `toml:",omitempty"`
|
||||
}
|
||||
|
||||
// CreateConsensusEngine creates a consensus engine for the given chain config.
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -705,6 +706,39 @@ func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) er
|
|||
return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data))
|
||||
}
|
||||
|
||||
// SendTransactionSync submits a signed tx and waits for a receipt (or until
|
||||
// the optional timeout elapses on the server side). If timeout == 0, the server
|
||||
// uses its default.
|
||||
func (ec *Client) SendTransactionSync(
|
||||
ctx context.Context,
|
||||
tx *types.Transaction,
|
||||
timeout *time.Duration,
|
||||
) (*types.Receipt, error) {
|
||||
raw, err := tx.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ec.SendRawTransactionSync(ctx, raw, timeout)
|
||||
}
|
||||
|
||||
func (ec *Client) SendRawTransactionSync(
|
||||
ctx context.Context,
|
||||
rawTx []byte,
|
||||
timeout *time.Duration,
|
||||
) (*types.Receipt, error) {
|
||||
var ms *hexutil.Uint64
|
||||
if timeout != nil {
|
||||
if d := hexutil.Uint64(timeout.Milliseconds()); d > 0 {
|
||||
ms = &d
|
||||
}
|
||||
}
|
||||
var receipt types.Receipt
|
||||
if err := ec.c.CallContext(ctx, &receipt, "eth_sendRawTransactionSync", hexutil.Bytes(rawTx), ms); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &receipt, nil
|
||||
}
|
||||
|
||||
// RevertErrorData returns the 'revert reason' data of a contract call.
|
||||
//
|
||||
// This can be used with CallContract and EstimateGas, and only when the server is Geth.
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ import (
|
|||
const estimateGasErrorRatio = 0.015
|
||||
|
||||
var errBlobTxNotSupported = errors.New("signing blob transactions not supported")
|
||||
var errSubClosed = errors.New("chain subscription closed")
|
||||
|
||||
// EthereumAPI provides an API to access Ethereum related information.
|
||||
type EthereumAPI struct {
|
||||
|
|
@ -1666,6 +1667,92 @@ func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil
|
|||
return SubmitTransaction(ctx, api.b, tx)
|
||||
}
|
||||
|
||||
// SendRawTransactionSync will add the signed transaction to the transaction pool
|
||||
// and wait until the transaction has been included in a block and return the receipt, or the timeout.
|
||||
func (api *TransactionAPI) SendRawTransactionSync(ctx context.Context, input hexutil.Bytes, timeoutMs *hexutil.Uint64) (map[string]interface{}, error) {
|
||||
tx := new(types.Transaction)
|
||||
if err := tx.UnmarshalBinary(input); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ch := make(chan core.ChainEvent, 128)
|
||||
sub := api.b.SubscribeChainEvent(ch)
|
||||
subErrCh := sub.Err()
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
hash, err := SubmitTransaction(ctx, api.b, tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
maxTimeout := api.b.RPCTxSyncMaxTimeout()
|
||||
defaultTimeout := api.b.RPCTxSyncDefaultTimeout()
|
||||
|
||||
timeout := defaultTimeout
|
||||
if timeoutMs != nil && *timeoutMs > 0 {
|
||||
req := time.Duration(*timeoutMs) * time.Millisecond
|
||||
if req > maxTimeout {
|
||||
timeout = maxTimeout
|
||||
} else {
|
||||
timeout = req
|
||||
}
|
||||
}
|
||||
|
||||
receiptCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
|
||||
// Fast path.
|
||||
if r, err := api.GetTransactionReceipt(receiptCtx, hash); err == nil && r != nil {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-receiptCtx.Done():
|
||||
// If server-side wait window elapsed, return the structured timeout.
|
||||
if errors.Is(receiptCtx.Err(), context.DeadlineExceeded) {
|
||||
return nil, &txSyncTimeoutError{
|
||||
msg: fmt.Sprintf("The transaction was added to the transaction pool but wasn't processed in %v.", timeout),
|
||||
hash: hash,
|
||||
}
|
||||
}
|
||||
return nil, receiptCtx.Err()
|
||||
|
||||
case err, ok := <-subErrCh:
|
||||
if !ok {
|
||||
return nil, errSubClosed
|
||||
}
|
||||
return nil, err
|
||||
|
||||
case ev, ok := <-ch:
|
||||
if !ok {
|
||||
return nil, errSubClosed
|
||||
}
|
||||
rs := ev.Receipts
|
||||
txs := ev.Transactions
|
||||
if len(rs) == 0 || len(rs) != len(txs) {
|
||||
continue
|
||||
}
|
||||
for i := range rs {
|
||||
if rs[i].TxHash == hash {
|
||||
if rs[i].BlockNumber != nil && rs[i].BlockHash != (common.Hash{}) {
|
||||
signer := types.LatestSigner(api.b.ChainConfig())
|
||||
return MarshalReceipt(
|
||||
rs[i],
|
||||
rs[i].BlockHash,
|
||||
rs[i].BlockNumber.Uint64(),
|
||||
signer,
|
||||
txs[i],
|
||||
int(rs[i].TransactionIndex),
|
||||
), nil
|
||||
}
|
||||
return api.GetTransactionReceipt(receiptCtx, hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sign calculates an ECDSA signature for:
|
||||
// keccak256("\x19Ethereum Signed Message:\n" + len(message) + message).
|
||||
//
|
||||
|
|
|
|||
|
|
@ -440,6 +440,19 @@ type testBackend struct {
|
|||
|
||||
pending *types.Block
|
||||
pendingReceipts types.Receipts
|
||||
|
||||
chainFeed *event.Feed
|
||||
autoMine bool
|
||||
|
||||
sentTx *types.Transaction
|
||||
sentTxHash common.Hash
|
||||
|
||||
syncDefaultTimeout time.Duration
|
||||
syncMaxTimeout time.Duration
|
||||
}
|
||||
|
||||
func fakeBlockHash(txh common.Hash) common.Hash {
|
||||
return crypto.Keccak256Hash([]byte("testblock"), txh.Bytes())
|
||||
}
|
||||
|
||||
func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.Engine, generator func(i int, b *core.BlockGen)) *testBackend {
|
||||
|
|
@ -466,6 +479,7 @@ func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.E
|
|||
acc: acc,
|
||||
pending: blocks[n],
|
||||
pendingReceipts: receipts[n],
|
||||
chainFeed: new(event.Feed),
|
||||
}
|
||||
return backend
|
||||
}
|
||||
|
|
@ -587,19 +601,64 @@ func (b testBackend) GetEVM(ctx context.Context, state *state.StateDB, header *t
|
|||
return vm.NewEVM(context, state, b.chain.Config(), *vmConfig)
|
||||
}
|
||||
func (b testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
|
||||
panic("implement me")
|
||||
return b.chainFeed.Subscribe(ch)
|
||||
}
|
||||
func (b testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
|
||||
panic("implement me")
|
||||
}
|
||||
func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
|
||||
panic("implement me")
|
||||
func (b *testBackend) SendTx(ctx context.Context, tx *types.Transaction) error {
|
||||
b.sentTx = tx
|
||||
b.sentTxHash = tx.Hash()
|
||||
|
||||
if b.autoMine {
|
||||
// Synthesize a "mined" receipt at head+1
|
||||
num := b.chain.CurrentHeader().Number.Uint64() + 1
|
||||
receipt := &types.Receipt{
|
||||
TxHash: tx.Hash(),
|
||||
Status: types.ReceiptStatusSuccessful,
|
||||
BlockHash: fakeBlockHash(tx.Hash()),
|
||||
BlockNumber: new(big.Int).SetUint64(num),
|
||||
TransactionIndex: 0,
|
||||
CumulativeGasUsed: 21000,
|
||||
GasUsed: 21000,
|
||||
}
|
||||
// Broadcast a ChainEvent that includes the receipts and txs
|
||||
b.chainFeed.Send(core.ChainEvent{
|
||||
Header: &types.Header{
|
||||
Number: new(big.Int).SetUint64(num),
|
||||
},
|
||||
Receipts: types.Receipts{receipt},
|
||||
Transactions: types.Transactions{tx},
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (b testBackend) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
|
||||
func (b *testBackend) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
|
||||
// Treat the auto-mined tx as canonically placed at head+1.
|
||||
if b.autoMine && txHash == b.sentTxHash {
|
||||
num := b.chain.CurrentHeader().Number.Uint64() + 1
|
||||
return true, b.sentTx, fakeBlockHash(txHash), num, 0
|
||||
}
|
||||
tx, blockHash, blockNumber, index := rawdb.ReadCanonicalTransaction(b.db, txHash)
|
||||
return tx != nil, tx, blockHash, blockNumber, index
|
||||
}
|
||||
func (b testBackend) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) {
|
||||
func (b *testBackend) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) {
|
||||
if b.autoMine && tx != nil && tx.Hash() == b.sentTxHash &&
|
||||
blockHash == fakeBlockHash(tx.Hash()) &&
|
||||
blockIndex == 0 &&
|
||||
blockNumber == b.chain.CurrentHeader().Number.Uint64()+1 {
|
||||
return &types.Receipt{
|
||||
Type: tx.Type(),
|
||||
Status: types.ReceiptStatusSuccessful,
|
||||
CumulativeGasUsed: 21000,
|
||||
GasUsed: 21000,
|
||||
EffectiveGasPrice: big.NewInt(1),
|
||||
BlockHash: blockHash,
|
||||
BlockNumber: new(big.Int).SetUint64(blockNumber),
|
||||
TransactionIndex: 0,
|
||||
TxHash: tx.Hash(),
|
||||
}, nil
|
||||
}
|
||||
return b.chain.GetCanonicalReceipt(tx, blockHash, blockNumber, blockIndex)
|
||||
}
|
||||
func (b testBackend) TxIndexDone() bool {
|
||||
|
|
@ -3889,3 +3948,109 @@ func (b configTimeBackend) HeaderByNumber(_ context.Context, n rpc.BlockNumber)
|
|||
func (b configTimeBackend) CurrentHeader() *types.Header {
|
||||
return &types.Header{Time: b.time}
|
||||
}
|
||||
|
||||
func (b *testBackend) RPCTxSyncDefaultTimeout() time.Duration {
|
||||
if b.syncDefaultTimeout != 0 {
|
||||
return b.syncDefaultTimeout
|
||||
}
|
||||
return 2 * time.Second
|
||||
}
|
||||
func (b *testBackend) RPCTxSyncMaxTimeout() time.Duration {
|
||||
if b.syncMaxTimeout != 0 {
|
||||
return b.syncMaxTimeout
|
||||
}
|
||||
return 5 * time.Minute
|
||||
}
|
||||
func (b *backendMock) RPCTxSyncDefaultTimeout() time.Duration { return 2 * time.Second }
|
||||
func (b *backendMock) RPCTxSyncMaxTimeout() time.Duration { return 5 * time.Minute }
|
||||
|
||||
func makeSignedRaw(t *testing.T, api *TransactionAPI, from, to common.Address, value *big.Int) (hexutil.Bytes, *types.Transaction) {
|
||||
t.Helper()
|
||||
|
||||
fillRes, err := api.FillTransaction(context.Background(), TransactionArgs{
|
||||
From: &from,
|
||||
To: &to,
|
||||
Value: (*hexutil.Big)(value),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("FillTransaction failed: %v", err)
|
||||
}
|
||||
signRes, err := api.SignTransaction(context.Background(), argsFromTransaction(fillRes.Tx, from))
|
||||
if err != nil {
|
||||
t.Fatalf("SignTransaction failed: %v", err)
|
||||
}
|
||||
return signRes.Raw, signRes.Tx
|
||||
}
|
||||
|
||||
// makeSelfSignedRaw is a convenience for a 0-ETH self-transfer.
|
||||
func makeSelfSignedRaw(t *testing.T, api *TransactionAPI, addr common.Address) (hexutil.Bytes, *types.Transaction) {
|
||||
return makeSignedRaw(t, api, addr, addr, big.NewInt(0))
|
||||
}
|
||||
|
||||
func TestSendRawTransactionSync_Success(t *testing.T) {
|
||||
t.Parallel()
|
||||
genesis := &core.Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
Alloc: types.GenesisAlloc{},
|
||||
}
|
||||
b := newTestBackend(t, 0, genesis, ethash.NewFaker(), nil)
|
||||
b.autoMine = true // immediately “mines” the tx in-memory
|
||||
|
||||
api := NewTransactionAPI(b, new(AddrLocker))
|
||||
|
||||
raw, _ := makeSelfSignedRaw(t, api, b.acc.Address)
|
||||
|
||||
receipt, err := api.SendRawTransactionSync(context.Background(), raw, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if receipt == nil {
|
||||
t.Fatalf("expected non-nil receipt")
|
||||
}
|
||||
if _, ok := receipt["blockNumber"]; !ok {
|
||||
t.Fatalf("expected blockNumber in receipt, got %#v", receipt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendRawTransactionSync_Timeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
genesis := &core.Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
Alloc: types.GenesisAlloc{},
|
||||
}
|
||||
b := newTestBackend(t, 0, genesis, ethash.NewFaker(), nil)
|
||||
b.autoMine = false // don't mine, should time out
|
||||
|
||||
api := NewTransactionAPI(b, new(AddrLocker))
|
||||
|
||||
raw, _ := makeSelfSignedRaw(t, api, b.acc.Address)
|
||||
|
||||
timeout := hexutil.Uint64(200) // 200ms
|
||||
receipt, err := api.SendRawTransactionSync(context.Background(), raw, &timeout)
|
||||
|
||||
if receipt != nil {
|
||||
t.Fatalf("expected nil receipt, got %#v", receipt)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("expected timeout error, got nil")
|
||||
}
|
||||
// assert error shape & data (hash)
|
||||
var de interface {
|
||||
ErrorCode() int
|
||||
ErrorData() interface{}
|
||||
}
|
||||
if !errors.As(err, &de) {
|
||||
t.Fatalf("expected data error with code/data, got %T %v", err, err)
|
||||
}
|
||||
if de.ErrorCode() != errCodeTxSyncTimeout {
|
||||
t.Fatalf("expected code %d, got %d", errCodeTxSyncTimeout, de.ErrorCode())
|
||||
}
|
||||
tx := new(types.Transaction)
|
||||
if e := tx.UnmarshalBinary(raw); e != nil {
|
||||
t.Fatal(e)
|
||||
}
|
||||
if got, want := de.ErrorData(), tx.Hash().Hex(); got != want {
|
||||
t.Fatalf("expected ErrorData=%s, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ type Backend interface {
|
|||
RPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protection
|
||||
RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs
|
||||
UnprotectedAllowed() bool // allows only for EIP155 transactions.
|
||||
RPCTxSyncDefaultTimeout() time.Duration
|
||||
RPCTxSyncMaxTimeout() time.Duration
|
||||
|
||||
// Blockchain API
|
||||
SetHead(number uint64)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"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/vm"
|
||||
|
|
@ -33,6 +34,11 @@ type revertError struct {
|
|||
reason string // revert reason hex encoded
|
||||
}
|
||||
|
||||
type txSyncTimeoutError struct {
|
||||
msg string
|
||||
hash common.Hash
|
||||
}
|
||||
|
||||
// ErrorCode returns the JSON error code for a revert.
|
||||
// See: https://ethereum.org/en/developers/docs/apis/json-rpc/#error-codes
|
||||
func (e *revertError) ErrorCode() int {
|
||||
|
|
@ -108,6 +114,7 @@ const (
|
|||
errCodeInvalidParams = -32602
|
||||
errCodeReverted = -32000
|
||||
errCodeVMError = -32015
|
||||
errCodeTxSyncTimeout = 4
|
||||
)
|
||||
|
||||
func txValidationError(err error) *invalidTxError {
|
||||
|
|
@ -168,3 +175,7 @@ type blockGasLimitReachedError struct{ message string }
|
|||
|
||||
func (e *blockGasLimitReachedError) Error() string { return e.message }
|
||||
func (e *blockGasLimitReachedError) ErrorCode() int { return errCodeBlockGasLimitReached }
|
||||
|
||||
func (e *txSyncTimeoutError) Error() string { return e.msg }
|
||||
func (e *txSyncTimeoutError) ErrorCode() int { return errCodeTxSyncTimeout }
|
||||
func (e *txSyncTimeoutError) ErrorData() interface{} { return e.hash.Hex() }
|
||||
|
|
|
|||
Loading…
Reference in a new issue