diff --git a/core/types/bundle_gasless.go b/core/types/bundle_gasless.go new file mode 100644 index 0000000000..2ffcefe7ef --- /dev/null +++ b/core/types/bundle_gasless.go @@ -0,0 +1,21 @@ +package types + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +type SimulateGaslessBundleArgs struct { + Txs []hexutil.Bytes `json:"txs"` +} + +type GaslessTxSimResult struct { + Hash common.Hash + GasUsed uint64 + Valid bool +} + +type SimulateGaslessBundleResp struct { + Results []GaslessTxSimResult + BasedBlockNumber int64 +} diff --git a/eth/api_backend.go b/eth/api_backend.go index 1679ac79bf..afbf154054 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -304,6 +304,10 @@ func (b *EthAPIBackend) SendBundle(ctx context.Context, bundle *types.Bundle) er return b.eth.txPool.AddBundle(bundle) } +func (b *EthAPIBackend) SimulateGaslessBundle(bundle *types.Bundle) (*types.SimulateGaslessBundleResp, error) { + return b.Miner().SimulateGaslessBundle(bundle) +} + func (b *EthAPIBackend) BundlePrice() *big.Int { bundles := b.eth.txPool.AllBundles() gasFloor := big.NewInt(b.eth.config.Miner.MevGasPriceFloor) diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index d52354c806..a66598dc5c 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -779,6 +779,16 @@ func (ec *Client) BestBidGasFee(ctx context.Context, parentHash common.Hash) (*b return fee, nil } +// SimulateGaslessBundle simulates a gasless bundle +func (ec *Client) SimulateGaslessBundle(ctx context.Context, args types.SimulateGaslessBundleArgs) (*types.SimulateGaslessBundleResp, error) { + var bundle types.SimulateGaslessBundleResp + err := ec.c.CallContext(ctx, &bundle, "eth_simulateGaslessBundle", args) + if err != nil { + return nil, err + } + return &bundle, nil +} + // SendBundle sends a bundle func (ec *Client) SendBundle(ctx context.Context, args types.SendBundleArgs) (common.Hash, error) { var hash common.Hash diff --git a/internal/ethapi/api_bundle.go b/internal/ethapi/api_bundle.go index cf9e5034e9..f5aae60d6c 100644 --- a/internal/ethapi/api_bundle.go +++ b/internal/ethapi/api_bundle.go @@ -25,6 +25,29 @@ func (s *PrivateTxBundleAPI) BundlePrice(ctx context.Context) *big.Int { return s.b.BundlePrice() } +// SimulateGaslessBundle simulates the execution of a list of transactions with order +func (s *PrivateTxBundleAPI) SimulateGaslessBundle(_ context.Context, args types.SimulateGaslessBundleArgs) (*types.SimulateGaslessBundleResp, error) { + if len(args.Txs) == 0 { + return nil, newBundleError(errors.New("bundle missing txs")) + } + + var txs types.Transactions + + for _, encodedTx := range args.Txs { + tx := new(types.Transaction) + if err := tx.UnmarshalBinary(encodedTx); err != nil { + return nil, err + } + txs = append(txs, tx) + } + + bundle := &types.Bundle{ + Txs: txs, + } + + return s.b.SimulateGaslessBundle(bundle) +} + // SendBundle will add the signed transaction to the transaction pool. // The sender is responsible for signing the transaction and using the correct nonce and ensuring validity func (s *PrivateTxBundleAPI) SendBundle(ctx context.Context, args types.SendBundleArgs) (common.Hash, error) { diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index c69b33cce5..f9cf795128 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -79,6 +79,7 @@ type Backend interface { // Transaction pool API SendTx(ctx context.Context, signedTx *types.Transaction) error SendBundle(ctx context.Context, bundle *types.Bundle) error + SimulateGaslessBundle(bundle *types.Bundle) (*types.SimulateGaslessBundleResp, error) BundlePrice() *big.Int GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) GetPoolTransactions() (types.Transactions, error) diff --git a/miner/miner.go b/miner/miner.go index c909b30216..d4a21b6ae3 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -309,6 +309,47 @@ func (miner *Miner) GasCeil() uint64 { func (miner *Miner) SimulateBundle(bundle *types.Bundle) (*big.Int, error) { parent := miner.eth.BlockChain().CurrentBlock() + + parentState, err := miner.eth.BlockChain().StateAt(parent.Root) + if err != nil { + return nil, err + } + + env, err := miner.prepareSimulationEnv(parent, parentState) + if err != nil { + return nil, err + } + + s, err := miner.worker.simulateBundle(env, bundle, parentState, env.gasPool, 0, true, true) + if err != nil { + return nil, err + } + + return s.BundleGasPrice, nil +} + +func (miner *Miner) SimulateGaslessBundle(bundle *types.Bundle) (*types.SimulateGaslessBundleResp, error) { + parent := miner.eth.BlockChain().CurrentBlock() + + parentState, err := miner.eth.BlockChain().StateAt(parent.Root) + if err != nil { + return nil, err + } + + env, err := miner.prepareSimulationEnv(parent, parentState) + if err != nil { + return nil, err + } + + resp, err := miner.worker.simulateGaslessBundle(env, bundle) + if err != nil { + return nil, err + } + + return resp, nil +} + +func (miner *Miner) prepareSimulationEnv(parent *types.Header, state *state.StateDB) (*environment, error) { timestamp := time.Now().Unix() if parent.Time >= uint64(timestamp) { timestamp = int64(parent.Time + 1) @@ -351,15 +392,11 @@ func (miner *Miner) SimulateBundle(bundle *types.Bundle) (*big.Int, error) { // } } - state, err := miner.eth.BlockChain().StateAt(parent.Root) - if err != nil { - return nil, err - } - env := &environment{ - header: header, - state: state.Copy(), - signer: types.MakeSigner(miner.worker.chainConfig, header.Number, header.Time), + header: header, + state: state.Copy(), + signer: types.MakeSigner(miner.worker.chainConfig, header.Number, header.Time), + gasPool: prepareGasPool(header.GasLimit), } if !miner.worker.chainConfig.IsFeynman(header.Number, header.Time) { @@ -367,12 +404,5 @@ func (miner *Miner) SimulateBundle(bundle *types.Bundle) (*big.Int, error) { systemcontracts.UpgradeBuildInSystemContract(miner.worker.chainConfig, header.Number, parent.Time, header.Time, env.state) } - gasPool := prepareGasPool(env.header.GasLimit) - s, err := miner.worker.simulateBundle(env, bundle, state, gasPool, 0, true, true) - - if err != nil { - return nil, err - } - - return s.BundleGasPrice, nil + return env, nil } diff --git a/miner/worker_builder.go b/miner/worker_builder.go index 8a070639c5..f45bd46e5e 100644 --- a/miner/worker_builder.go +++ b/miner/worker_builder.go @@ -495,6 +495,44 @@ func (w *worker) simulateBundle( }, nil } +func (w *worker) simulateGaslessBundle(env *environment, bundle *types.Bundle) (*types.SimulateGaslessBundleResp, error) { + result := make([]types.GaslessTxSimResult, 0) + + txIdx := 0 + for _, tx := range bundle.Txs { + env.state.SetTxContext(tx.Hash(), txIdx) + + var ( + snap = env.state.Snapshot() + gp = env.gasPool.Gas() + valid = true + ) + + receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &w.coinbase, env.gasPool, env.state, env.header, tx, + &env.header.GasUsed, *w.chain.GetVMConfig()) + if err != nil { + env.state.RevertToSnapshot(snap) + env.gasPool.SetGas(gp) + valid = false + log.Warn("fail to simulate gasless bundle, skipped", "txHash", tx.Hash(), "err", err) + } else { + txIdx++ + } + + result = append(result, types.GaslessTxSimResult{ + Hash: tx.Hash(), + GasUsed: receipt.GasUsed, + Valid: valid, + }) + } + + return &types.SimulateGaslessBundleResp{ + Results: result, + BasedBlockNumber: env.header.Number.Int64(), + }, nil + +} + func containsHash(arr []common.Hash, match common.Hash) bool { for _, elem := range arr { if elem == match {