Skip to content

Commit

Permalink
feat: only take non-mem txs to calculate bundle price && add api to s…
Browse files Browse the repository at this point in the history
…imulate gasless bundle (#42)
  • Loading branch information
irrun authored Jul 23, 2024
1 parent aed1d66 commit dc3c294
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 45 deletions.
21 changes: 21 additions & 0 deletions core/types/bundle_gasless.go
Original file line number Diff line number Diff line change
@@ -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
}
4 changes: 4 additions & 0 deletions eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 10 additions & 0 deletions ethclient/ethclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions internal/ethapi/api_bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions internal/ethapi/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,10 @@ func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) er
func (b testBackend) SendBundle(ctx context.Context, bundle *types.Bundle) error {
panic("implement me")
}
func (b *testBackend) SimulateGaslessBundle(bundle *types.Bundle) (*types.SimulateGaslessBundleResp, error) {
//TODO implement me
panic("implement me")
}
func (b testBackend) BundlePrice() *big.Int {
panic("implement me")
}
Expand Down
1 change: 1 addition & 0 deletions internal/ethapi/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 5 additions & 1 deletion internal/ethapi/transaction_args_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,11 @@ func (b *backendMock) SubscribeNewVoteEvent(ch chan<- core.NewVoteEvent) event.S
}
func (b *backendMock) SendTx(ctx context.Context, signedTx *types.Transaction) error { return nil }
func (b *backendMock) SendBundle(ctx context.Context, bundle *types.Bundle) error { return nil }
func (b *backendMock) BundlePrice() *big.Int { return nil }
func (b *backendMock) SimulateGaslessBundle(bundle *types.Bundle) (*types.SimulateGaslessBundleResp, error) {
//TODO implement me
panic("implement me")
}
func (b *backendMock) BundlePrice() *big.Int { return nil }
func (b *backendMock) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) {
return false, nil, [32]byte{}, 0, 0, nil
}
Expand Down
65 changes: 46 additions & 19 deletions miner/miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
package miner

import (
"errors"
"fmt"
"math/big"
"sync"
Expand Down Expand Up @@ -310,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)
Expand Down Expand Up @@ -352,30 +392,17 @@ 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) {
// Handle upgrade build-in system contract code
systemcontracts.UpgradeBuildInSystemContract(miner.worker.chainConfig, header.Number, parent.Time, header.Time, env.state)
}

s, err := miner.worker.simulateBundles(env, []*types.Bundle{bundle})
if err != nil {
return nil, err
}

if len(s) == 0 {
return nil, errors.New("no valid sim result")
}

return s[0].BundleGasPrice, nil
return env, nil
}
100 changes: 75 additions & 25 deletions miner/worker_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,39 +438,52 @@ func (w *worker) simulateBundle(
return nil, err
}

bundleGasUsed += receipt.GasUsed
if !w.eth.TxPool().Has(tx.Hash()) {
bundleGasUsed += receipt.GasUsed

txGasUsed := new(big.Int).SetUint64(receipt.GasUsed)
effectiveTip, err := tx.EffectiveGasTip(env.header.BaseFee)
if err != nil {
return nil, err
}
txGasFees := new(big.Int).Mul(txGasUsed, effectiveTip)
txGasUsed := new(big.Int).SetUint64(receipt.GasUsed)
effectiveTip, er := tx.EffectiveGasTip(env.header.BaseFee)
if er != nil {
return nil, er
}

if env.header.BaseFee != nil {
log.Info("simulate bundle: header base fee", "value", env.header.BaseFee.String())
effectiveTip.Add(effectiveTip, env.header.BaseFee)
}

txGasFees := new(big.Int).Mul(txGasUsed, effectiveTip)

if tx.Type() == types.BlobTxType {
blobFee := new(big.Int).SetUint64(receipt.BlobGasUsed)
blobFee.Mul(blobFee, receipt.BlobGasPrice)
txGasFees.Add(txGasFees, blobFee)
if tx.Type() == types.BlobTxType {
blobFee := new(big.Int).SetUint64(receipt.BlobGasUsed)
blobFee.Mul(blobFee, receipt.BlobGasPrice)
txGasFees.Add(txGasFees, blobFee)
}
bundleGasFees.Add(bundleGasFees, txGasFees)
sysBalanceAfter := state.GetBalance(consensus.SystemAddress)
sysDelta := new(uint256.Int).Sub(sysBalanceAfter, sysBalanceBefore)
sysDelta.Sub(sysDelta, uint256.MustFromBig(txGasFees))
ethSentToSystem.Add(ethSentToSystem, sysDelta.ToBig())
}
bundleGasFees.Add(bundleGasFees, txGasFees)
sysBalanceAfter := state.GetBalance(consensus.SystemAddress)
sysDelta := new(uint256.Int).Sub(sysBalanceAfter, sysBalanceBefore)
sysDelta.Sub(sysDelta, uint256.MustFromBig(txGasFees))
ethSentToSystem.Add(ethSentToSystem, sysDelta.ToBig())
}

bundleGasPrice := new(big.Int).Div(bundleGasFees, new(big.Int).SetUint64(bundleGasUsed))
// if all txs in the bundle are from mempool, we accept the bundle without checking gas price
bundleGasPrice := big.NewInt(0)

if bundleGasPrice.Cmp(big.NewInt(w.config.MevGasPriceFloor)) < 0 {
err := errBundlePriceTooLow
log.Warn("fail to simulate bundle", "hash", bundle.Hash().String(), "err", err)
if bundleGasUsed != 0 {
bundleGasPrice = new(big.Int).Div(bundleGasFees, new(big.Int).SetUint64(bundleGasUsed))

if prune {
log.Warn("prune bundle", "hash", bundle.Hash().String())
w.eth.TxPool().PruneBundle(bundle.Hash())
}
if bundleGasPrice.Cmp(big.NewInt(w.config.MevGasPriceFloor)) < 0 {
err := errBundlePriceTooLow
log.Warn("fail to simulate bundle", "hash", bundle.Hash().String(), "err", err)

if prune {
log.Warn("prune bundle", "hash", bundle.Hash().String())
w.eth.TxPool().PruneBundle(bundle.Hash())
}

return nil, err
return nil, err
}
}

return &types.SimulatedBundle{
Expand All @@ -482,6 +495,43 @@ 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 {
Expand Down

0 comments on commit dc3c294

Please sign in to comment.