mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
Add more stuff
This commit is contained in:
parent
733df027e9
commit
dd1a5901d4
5 changed files with 336 additions and 432 deletions
221
miner/builder.go
Normal file
221
miner/builder.go
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
package miner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BuilderConfig struct {
|
||||||
|
ChainConfig *params.ChainConfig
|
||||||
|
Engine consensus.Engine
|
||||||
|
EthBackend Backend
|
||||||
|
Chain *core.BlockChain
|
||||||
|
GasCeil uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type BuilderArgs struct {
|
||||||
|
ParentHash common.Hash
|
||||||
|
FeeRecipient common.Address
|
||||||
|
Extra []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type Builder struct {
|
||||||
|
env *environment
|
||||||
|
wrk *worker
|
||||||
|
args *BuilderArgs
|
||||||
|
|
||||||
|
profitPre *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBuilder(config *BuilderConfig, args *BuilderArgs) (*Builder, error) {
|
||||||
|
b := &Builder{
|
||||||
|
args: args,
|
||||||
|
}
|
||||||
|
|
||||||
|
b.wrk = &worker{
|
||||||
|
config: &Config{
|
||||||
|
GasCeil: config.GasCeil,
|
||||||
|
},
|
||||||
|
eth: config.EthBackend,
|
||||||
|
chainConfig: config.ChainConfig,
|
||||||
|
engine: config.Engine,
|
||||||
|
chain: config.Chain,
|
||||||
|
}
|
||||||
|
|
||||||
|
workerParams := &generateParams{
|
||||||
|
parentHash: args.ParentHash,
|
||||||
|
forceTime: false,
|
||||||
|
coinbase: args.FeeRecipient,
|
||||||
|
extra: args.Extra,
|
||||||
|
}
|
||||||
|
env, err := b.wrk.prepareWork(workerParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
env.gasPool = new(core.GasPool).AddGas(env.header.GasLimit)
|
||||||
|
b.env = env
|
||||||
|
b.profitPre = env.state.GetBalance(env.coinbase)
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type SBundle struct {
|
||||||
|
BlockNumber *big.Int `json:"blockNumber,omitempty"` // if BlockNumber is set it must match DecryptionCondition!
|
||||||
|
MaxBlock *big.Int `json:"maxBlock,omitempty"`
|
||||||
|
Txs types.Transactions `json:"txs"`
|
||||||
|
RevertingHashes []common.Hash `json:"revertingHashes,omitempty"`
|
||||||
|
RefundPercent *int `json:"percent,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) AddTransaction(txn *types.Transaction) (*types.SimulateTransactionResult, error) {
|
||||||
|
logs, err := b.wrk.commitTransaction(b.env, txn)
|
||||||
|
if err != nil {
|
||||||
|
return &types.SimulateTransactionResult{
|
||||||
|
Success: false,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return receiptToSimResult(&types.Receipt{Logs: logs}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) AddBundles(bundles []*SBundle) error {
|
||||||
|
for _, bundle := range bundles {
|
||||||
|
if err := b.AddBundle(bundle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) AddBundle(bundle *SBundle) error {
|
||||||
|
work := b.env
|
||||||
|
|
||||||
|
// Assume static 28000 gas transfers for both mev-share and proposer payments
|
||||||
|
refundTransferCost := new(big.Int).Mul(big.NewInt(28000), work.header.BaseFee)
|
||||||
|
|
||||||
|
// create ephemeral addr and private key for payment txn
|
||||||
|
ephemeralPrivKey, err := crypto.GenerateKey()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ephemeralAddr := crypto.PubkeyToAddress(ephemeralPrivKey.PublicKey)
|
||||||
|
|
||||||
|
// apply bundle
|
||||||
|
profitPreBundle := work.state.GetBalance(b.env.coinbase)
|
||||||
|
if err := b.wrk.rawCommitTransactions(work, bundle.Txs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
profitPostBundle := work.state.GetBalance(b.env.coinbase)
|
||||||
|
|
||||||
|
// calc & refund user if bundle has multiple txns and wants refund
|
||||||
|
if len(bundle.Txs) > 1 && bundle.RefundPercent != nil {
|
||||||
|
// Note: PoC logic, this could be gamed by not sending any eth to coinbase
|
||||||
|
refundPrct := *bundle.RefundPercent
|
||||||
|
if refundPrct == 0 {
|
||||||
|
// default refund
|
||||||
|
refundPrct = 10
|
||||||
|
}
|
||||||
|
bundleProfit := new(big.Int).Sub(profitPostBundle, profitPreBundle)
|
||||||
|
refundAmt := new(big.Int).Div(bundleProfit, big.NewInt(int64(refundPrct)))
|
||||||
|
// subtract payment txn transfer costs
|
||||||
|
refundAmt = new(big.Int).Sub(refundAmt, refundTransferCost)
|
||||||
|
|
||||||
|
currNonce := work.state.GetNonce(ephemeralAddr)
|
||||||
|
// HACK to include payment txn
|
||||||
|
// multi refund block untested
|
||||||
|
userTx := bundle.Txs[0] // NOTE : assumes first txn is refund recipient
|
||||||
|
refundAddr, err := types.Sender(types.LatestSignerForChainID(userTx.ChainId()), userTx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
paymentTx, err := types.SignTx(types.NewTx(&types.LegacyTx{
|
||||||
|
Nonce: currNonce,
|
||||||
|
To: &refundAddr,
|
||||||
|
Value: refundAmt,
|
||||||
|
Gas: 28000,
|
||||||
|
GasPrice: work.header.BaseFee,
|
||||||
|
}), work.signer, ephemeralPrivKey)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// commit payment txn
|
||||||
|
if err := b.wrk.rawCommitTransactions(work, types.Transactions{paymentTx}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) FillPending() error {
|
||||||
|
if err := b.wrk.commitPendingTxs(b.env); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) BuildBlock() (*types.Block, error) {
|
||||||
|
work := b.env
|
||||||
|
|
||||||
|
// Assume static 28000 gas transfers for both mev-share and proposer payments
|
||||||
|
refundTransferCost := new(big.Int).Mul(big.NewInt(28000), work.header.BaseFee)
|
||||||
|
|
||||||
|
// create ephemeral addr and private key for payment txn
|
||||||
|
ephemeralPrivKey, err := crypto.GenerateKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ephemeralAddr := crypto.PubkeyToAddress(ephemeralPrivKey.PublicKey)
|
||||||
|
|
||||||
|
profitPost := work.state.GetBalance(b.env.coinbase)
|
||||||
|
proposerProfit := new(big.Int).Set(profitPost) // = post-pre-transfer_cost
|
||||||
|
proposerProfit = proposerProfit.Sub(profitPost, b.profitPre)
|
||||||
|
proposerProfit = proposerProfit.Sub(proposerProfit, refundTransferCost)
|
||||||
|
|
||||||
|
currNonce := work.state.GetNonce(ephemeralAddr)
|
||||||
|
paymentTx, err := types.SignTx(types.NewTx(&types.LegacyTx{
|
||||||
|
Nonce: currNonce,
|
||||||
|
To: &b.args.FeeRecipient,
|
||||||
|
Value: proposerProfit,
|
||||||
|
Gas: 28000,
|
||||||
|
GasPrice: work.header.BaseFee,
|
||||||
|
}), work.signer, ephemeralPrivKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not sign proposer payment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// commit payment txn
|
||||||
|
if err := b.wrk.rawCommitTransactions(work, types.Transactions{paymentTx}); err != nil {
|
||||||
|
return nil, fmt.Errorf("could not sign proposer payment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := b.wrk.engine.FinalizeAndAssemble(b.wrk.chain, work.header, work.state, work.txs, nil, work.receipts, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return block, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func receiptToSimResult(receipt *types.Receipt) *types.SimulateTransactionResult {
|
||||||
|
result := &types.SimulateTransactionResult{
|
||||||
|
Success: true,
|
||||||
|
Logs: []*types.SimulatedLog{},
|
||||||
|
}
|
||||||
|
for _, log := range receipt.Logs {
|
||||||
|
result.Logs = append(result.Logs, &types.SimulatedLog{
|
||||||
|
Addr: log.Address,
|
||||||
|
Topics: log.Topics,
|
||||||
|
Data: log.Data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
97
miner/builder_test.go
Normal file
97
miner/builder_test.go
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
package miner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/clique"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuilder_AddTxn_Simple(t *testing.T) {
|
||||||
|
config, backend := newMockBuilderConfig(t)
|
||||||
|
|
||||||
|
builder, err := NewBuilder(config, &BuilderArgs{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tx1 := backend.newRandomTx(true)
|
||||||
|
|
||||||
|
res, err := builder.AddTransaction(tx1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, res.Success)
|
||||||
|
require.Len(t, builder.env.receipts, 1)
|
||||||
|
|
||||||
|
// we cannot add the same transaction again. Note that by design the
|
||||||
|
// function does not error but returns the SimulateTransactionResult.success = false
|
||||||
|
res, err = builder.AddTransaction(tx1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, res.Success)
|
||||||
|
require.Len(t, builder.env.receipts, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilder_FillTransactions(t *testing.T) {
|
||||||
|
config, backend := newMockBuilderConfig(t)
|
||||||
|
|
||||||
|
builder, err := NewBuilder(config, &BuilderArgs{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tx1 := backend.newRandomTx(true)
|
||||||
|
errArr := backend.TxPool().Add(types.Transactions{tx1}, false, true)
|
||||||
|
require.NoError(t, errArr[0])
|
||||||
|
|
||||||
|
tx2 := backend.newRandomTx(true)
|
||||||
|
errArr = backend.TxPool().Add(types.Transactions{tx2}, false, true)
|
||||||
|
require.NoError(t, errArr[0])
|
||||||
|
|
||||||
|
require.NoError(t, builder.FillPending())
|
||||||
|
require.Len(t, builder.env.receipts, 2)
|
||||||
|
|
||||||
|
require.Equal(t, tx1.Hash(), builder.env.receipts[0].TxHash)
|
||||||
|
require.Equal(t, tx2.Hash(), builder.env.receipts[1].TxHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilder_Bundle(t *testing.T) {
|
||||||
|
t.Skip("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilder_BuildBlock(t *testing.T) {
|
||||||
|
t.Skip("TODO")
|
||||||
|
|
||||||
|
config, backend := newMockBuilderConfig(t)
|
||||||
|
|
||||||
|
builder, err := NewBuilder(config, &BuilderArgs{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tx1 := backend.newRandomTx(true)
|
||||||
|
|
||||||
|
_, err = builder.AddTransaction(tx1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
block, err := builder.BuildBlock()
|
||||||
|
require.NoError(t, err)
|
||||||
|
fmt.Println(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockBuilderConfig(t *testing.T) (*BuilderConfig, *testWorkerBackend) {
|
||||||
|
var (
|
||||||
|
db = rawdb.NewMemoryDatabase()
|
||||||
|
config = *params.AllCliqueProtocolChanges
|
||||||
|
)
|
||||||
|
config.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000}
|
||||||
|
engine := clique.New(config.Clique, db)
|
||||||
|
|
||||||
|
w, backend := newTestWorker(t, &config, engine, db, 0)
|
||||||
|
w.close()
|
||||||
|
|
||||||
|
bConfig := &BuilderConfig{
|
||||||
|
ChainConfig: w.chainConfig,
|
||||||
|
Engine: w.engine,
|
||||||
|
EthBackend: w.eth,
|
||||||
|
Chain: w.chain,
|
||||||
|
GasCeil: 10000000,
|
||||||
|
}
|
||||||
|
return bConfig, backend
|
||||||
|
}
|
||||||
|
|
@ -1,275 +0,0 @@
|
||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
|
||||||
"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/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/ethereum/go-ethereum/suave/builder/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
type builder struct {
|
|
||||||
config *builderConfig
|
|
||||||
txns []*types.Transaction
|
|
||||||
receipts []*types.Receipt
|
|
||||||
state *state.StateDB
|
|
||||||
gasPool *core.GasPool
|
|
||||||
gasUsed *uint64
|
|
||||||
signer types.Signer
|
|
||||||
args api.BuildBlockArgs
|
|
||||||
coinbasePreBalance *big.Int
|
|
||||||
engine consensus.Engine
|
|
||||||
}
|
|
||||||
|
|
||||||
type builderConfig struct {
|
|
||||||
preState *state.StateDB
|
|
||||||
header *types.Header
|
|
||||||
config *params.ChainConfig
|
|
||||||
context core.ChainContext
|
|
||||||
chainReader consensus.ChainHeaderReader
|
|
||||||
|
|
||||||
// newpayloadTimeout is the maximum timeout allowance for creating payload.
|
|
||||||
// The default value is 2 seconds but node operator can set it to arbitrary
|
|
||||||
// large value. A large timeout allowance may cause Geth to fail creating
|
|
||||||
// a non-empty payload within the specified time and eventually miss the slot
|
|
||||||
// in case there are some computation expensive transactions in txpool.
|
|
||||||
newpayloadTimeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBuilder(config *builderConfig) *builder {
|
|
||||||
gp := core.GasPool(config.header.GasLimit)
|
|
||||||
var gasUsed uint64
|
|
||||||
|
|
||||||
return &builder{
|
|
||||||
config: config,
|
|
||||||
state: config.preState.Copy(),
|
|
||||||
gasPool: &gp,
|
|
||||||
gasUsed: &gasUsed,
|
|
||||||
signer: types.MakeSigner(config.config, config.header.Number, config.header.Time),
|
|
||||||
coinbasePreBalance: config.preState.GetBalance(config.header.Coinbase),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *builder) takeSnapshot() func() {
|
|
||||||
indx := len(b.txns)
|
|
||||||
snap := b.state.Snapshot()
|
|
||||||
|
|
||||||
return func() {
|
|
||||||
b.txns = b.txns[:indx]
|
|
||||||
b.receipts = b.receipts[:indx]
|
|
||||||
b.state.RevertToSnapshot(snap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *builder) AddBundle(bundle api.Bundle) error {
|
|
||||||
revertFn := b.takeSnapshot()
|
|
||||||
|
|
||||||
// create ephemeral addr and private key for payment txn
|
|
||||||
ephemeralPrivKey, err := crypto.GenerateKey()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ephemeralAddr := crypto.PubkeyToAddress(ephemeralPrivKey.PublicKey)
|
|
||||||
|
|
||||||
// Assume static 28000 gas transfers for both mev-share and proposer payments
|
|
||||||
refundTransferCost := new(big.Int).Mul(big.NewInt(28000), b.config.header.BaseFee)
|
|
||||||
|
|
||||||
// apply bundle
|
|
||||||
profitPreBundle := b.state.GetBalance(b.config.header.Coinbase)
|
|
||||||
if err := b.AddTransactions(bundle.Txs); err != nil {
|
|
||||||
revertFn()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
profitPostBundle := b.state.GetBalance(b.config.header.Coinbase)
|
|
||||||
|
|
||||||
// calc & refund user if bundle has multiple txns and wants refund
|
|
||||||
if len(bundle.Txs) > 1 && bundle.RefundPercent != nil {
|
|
||||||
// Note: PoC logic, this could be gamed by not sending any eth to coinbase
|
|
||||||
refundPrct := *bundle.RefundPercent
|
|
||||||
if refundPrct == 0 {
|
|
||||||
// default refund
|
|
||||||
refundPrct = 10
|
|
||||||
}
|
|
||||||
bundleProfit := new(big.Int).Sub(profitPostBundle, profitPreBundle)
|
|
||||||
refundAmt := new(big.Int).Div(bundleProfit, big.NewInt(int64(refundPrct)))
|
|
||||||
// subtract payment txn transfer costs
|
|
||||||
refundAmt = new(big.Int).Sub(refundAmt, refundTransferCost)
|
|
||||||
|
|
||||||
currNonce := b.state.GetNonce(ephemeralAddr)
|
|
||||||
|
|
||||||
// HACK to include payment txn
|
|
||||||
// multi refund block untested
|
|
||||||
userTx := bundle.Txs[0] // NOTE : assumes first txn is refund recipient
|
|
||||||
refundAddr, err := types.Sender(types.LatestSignerForChainID(userTx.ChainId()), userTx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
paymentTx, err := types.SignTx(types.NewTx(&types.LegacyTx{
|
|
||||||
Nonce: currNonce,
|
|
||||||
To: &refundAddr,
|
|
||||||
Value: refundAmt,
|
|
||||||
Gas: 28000,
|
|
||||||
GasPrice: b.config.header.BaseFee,
|
|
||||||
}), b.signer, ephemeralPrivKey)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// commit payment txn
|
|
||||||
if _, err := b.AddTransaction(paymentTx); err != nil {
|
|
||||||
revertFn()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *builder) AddTransactions(txns types.Transactions) error {
|
|
||||||
revertFn := b.takeSnapshot()
|
|
||||||
|
|
||||||
for _, txn := range txns {
|
|
||||||
if _, err := b.AddTransaction(txn); err != nil {
|
|
||||||
revertFn()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *builder) AddTransaction(txn *types.Transaction) (*types.SimulateTransactionResult, error) {
|
|
||||||
dummyAuthor := common.Address{}
|
|
||||||
|
|
||||||
vmConfig := vm.Config{
|
|
||||||
NoBaseFee: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
snap := b.state.Snapshot()
|
|
||||||
|
|
||||||
b.state.SetTxContext(txn.Hash(), len(b.txns))
|
|
||||||
receipt, err := core.ApplyTransaction(b.config.config, b.config.context, &dummyAuthor, b.gasPool, b.state, b.config.header, txn, b.gasUsed, vmConfig)
|
|
||||||
if err != nil {
|
|
||||||
b.state.RevertToSnapshot(snap)
|
|
||||||
|
|
||||||
result := &types.SimulateTransactionResult{
|
|
||||||
Success: false,
|
|
||||||
Error: err.Error(),
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
b.txns = append(b.txns, txn)
|
|
||||||
b.receipts = append(b.receipts, receipt)
|
|
||||||
|
|
||||||
result := &types.SimulateTransactionResult{
|
|
||||||
Success: true,
|
|
||||||
Logs: []*types.SimulatedLog{},
|
|
||||||
}
|
|
||||||
for _, log := range receipt.Logs {
|
|
||||||
result.Logs = append(result.Logs, &types.SimulatedLog{
|
|
||||||
Addr: log.Address,
|
|
||||||
Topics: log.Topics,
|
|
||||||
Data: log.Data,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *builder) commitPendingTxs() error {
|
|
||||||
interrupt := new(atomic.Int32)
|
|
||||||
timer := time.AfterFunc(b.config.newpayloadTimeout, func() {
|
|
||||||
interrupt.Store(commitInterruptTimeout)
|
|
||||||
})
|
|
||||||
defer timer.Stop()
|
|
||||||
if err := b.fillTransactions(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *builder) fillTransactions() error {
|
|
||||||
// Split the pending transactions into locals and remotes
|
|
||||||
// Fill the block with all available pending transactions.
|
|
||||||
pending := w.eth.TxPool().Pending(true)
|
|
||||||
localTxs, remoteTxs := make(map[common.Address]types.Transactions), pending
|
|
||||||
for _, account := range w.eth.TxPool().Locals() {
|
|
||||||
if txs := remoteTxs[account]; len(txs) > 0 {
|
|
||||||
delete(remoteTxs, account)
|
|
||||||
localTxs[account] = txs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(localTxs) > 0 {
|
|
||||||
txs := types.NewTransactionsByPriceAndNonce(env.signer, localTxs, env.header.BaseFee)
|
|
||||||
if err := b.commitTransactions(env, txs, interrupt); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(remoteTxs) > 0 {
|
|
||||||
txs := types.NewTransactionsByPriceAndNonce(env.signer, remoteTxs, env.header.BaseFee)
|
|
||||||
if err := w.commitTransactions(env, txs, interrupt); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *builder) BuildBlock() error {
|
|
||||||
if b.args.FillPending {
|
|
||||||
if err := b.commitPendingTxs(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create ephemeral addr and private key for payment txn
|
|
||||||
ephemeralPrivKey, err := crypto.GenerateKey()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ephemeralAddr := crypto.PubkeyToAddress(ephemeralPrivKey.PublicKey)
|
|
||||||
|
|
||||||
// Assume static 28000 gas transfers for both mev-share and proposer payments
|
|
||||||
refundTransferCost := new(big.Int).Mul(big.NewInt(28000), b.config.header.BaseFee)
|
|
||||||
|
|
||||||
profitPost := b.state.GetBalance(b.config.header.Coinbase)
|
|
||||||
proposerProfit := new(big.Int).Set(profitPost) // = post-pre-transfer_cost
|
|
||||||
proposerProfit = proposerProfit.Sub(profitPost, b.coinbasePreBalance)
|
|
||||||
proposerProfit = proposerProfit.Sub(proposerProfit, refundTransferCost)
|
|
||||||
|
|
||||||
currNonce := b.state.GetNonce(ephemeralAddr)
|
|
||||||
paymentTx, err := types.SignTx(types.NewTx(&types.LegacyTx{
|
|
||||||
Nonce: currNonce,
|
|
||||||
To: &b.args.FeeRecipient,
|
|
||||||
Value: proposerProfit,
|
|
||||||
Gas: 28000,
|
|
||||||
GasPrice: b.config.header.BaseFee,
|
|
||||||
}), b.signer, ephemeralPrivKey)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not sign proposer payment: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// commit payment txn
|
|
||||||
if _, err := b.AddTransaction(paymentTx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
block, err := b.engine.FinalizeAndAssemble(b.config.chainReader, b.config.header, b.state, b.txns, []*types.Header{}, b.receipts, b.args.Withdrawals)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("-- block --", block)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/big"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBuilder_AddTxn_Simple(t *testing.T) {
|
|
||||||
to := common.Address{0x01, 0x10, 0xab}
|
|
||||||
|
|
||||||
mock := newMockBuilder(t)
|
|
||||||
txn := mock.state.newTransfer(t, to, big.NewInt(1))
|
|
||||||
|
|
||||||
_, err := mock.builder.AddTransaction(txn)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
mock.expect(t, expectedResult{
|
|
||||||
txns: []*types.Transaction{
|
|
||||||
txn,
|
|
||||||
},
|
|
||||||
balances: map[common.Address]*big.Int{
|
|
||||||
to: big.NewInt(1),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMockBuilder(t *testing.T) *mockBuilder {
|
|
||||||
// create a dummy header at 0
|
|
||||||
header := &types.Header{
|
|
||||||
Number: big.NewInt(0),
|
|
||||||
GasLimit: 1000000000000,
|
|
||||||
Time: 1000,
|
|
||||||
Difficulty: big.NewInt(1),
|
|
||||||
}
|
|
||||||
|
|
||||||
mState := newMockState(t)
|
|
||||||
|
|
||||||
m := &mockBuilder{
|
|
||||||
state: mState,
|
|
||||||
}
|
|
||||||
|
|
||||||
stateRef, err := mState.stateAt(mState.stateRoot)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
config := &builderConfig{
|
|
||||||
header: header,
|
|
||||||
preState: stateRef,
|
|
||||||
config: mState.chainConfig,
|
|
||||||
context: m, // m implements ChainContext with panics
|
|
||||||
}
|
|
||||||
m.builder = newBuilder(config)
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockBuilder struct {
|
|
||||||
builder *builder
|
|
||||||
state *mockState
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockBuilder) Engine() consensus.Engine {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockBuilder) GetHeader(common.Hash, uint64) *types.Header {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
|
|
||||||
type expectedResult struct {
|
|
||||||
txns []*types.Transaction
|
|
||||||
balances map[common.Address]*big.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockBuilder) expect(t *testing.T, res expectedResult) {
|
|
||||||
// validate txns
|
|
||||||
if len(res.txns) != len(m.builder.txns) {
|
|
||||||
t.Fatalf("expected %d txns, got %d", len(res.txns), len(m.builder.txns))
|
|
||||||
}
|
|
||||||
for indx, txn := range res.txns {
|
|
||||||
if txn.Hash() != m.builder.txns[indx].Hash() {
|
|
||||||
t.Fatalf("expected txn %d to be %s, got %s", indx, txn.Hash(), m.builder.txns[indx].Hash())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The receipts must be the same as the txns
|
|
||||||
if len(res.txns) != len(m.builder.receipts) {
|
|
||||||
t.Fatalf("expected %d receipts, got %d", len(res.txns), len(m.builder.receipts))
|
|
||||||
}
|
|
||||||
for indx, txn := range res.txns {
|
|
||||||
if txn.Hash() != m.builder.receipts[indx].TxHash {
|
|
||||||
t.Fatalf("expected receipt %d to be %s, got %s", indx, txn.Hash(), m.builder.receipts[indx].TxHash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The gas left in the pool must be the header gas limit minus
|
|
||||||
// the total gas consumed by all the transactions in the block.
|
|
||||||
totalGasConsumed := uint64(0)
|
|
||||||
for _, receipt := range m.builder.receipts {
|
|
||||||
totalGasConsumed += receipt.GasUsed
|
|
||||||
}
|
|
||||||
if m.builder.gasPool.Gas() != m.builder.config.header.GasLimit-totalGasConsumed {
|
|
||||||
t.Fatalf("expected gas pool to be %d, got %d", m.builder.config.header.GasLimit-totalGasConsumed, m.builder.gasPool.Gas())
|
|
||||||
}
|
|
||||||
|
|
||||||
// The 'gasUsed' must match the total gas consumed by all the transactions
|
|
||||||
if *m.builder.gasUsed != totalGasConsumed {
|
|
||||||
t.Fatalf("expected gas used to be %d, got %d", totalGasConsumed, m.builder.gasUsed)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The state must match the expected balances
|
|
||||||
for addr, expectedBalance := range res.balances {
|
|
||||||
balance := m.builder.state.GetBalance(addr)
|
|
||||||
if balance.Cmp(expectedBalance) != 0 {
|
|
||||||
t.Fatalf("expected balance of %s to be %d, got %d", addr, expectedBalance, balance)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/miner"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/suave/builder/api"
|
"github.com/ethereum/go-ethereum/suave/builder/api"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
@ -40,7 +41,7 @@ type Config struct {
|
||||||
|
|
||||||
type SessionManager struct {
|
type SessionManager struct {
|
||||||
sem chan struct{}
|
sem chan struct{}
|
||||||
sessions map[string]*builder
|
sessions map[string]*miner.Builder
|
||||||
sessionTimers map[string]*time.Timer
|
sessionTimers map[string]*time.Timer
|
||||||
sessionsLock sync.RWMutex
|
sessionsLock sync.RWMutex
|
||||||
blockchain blockchain
|
blockchain blockchain
|
||||||
|
|
@ -65,7 +66,7 @@ func NewSessionManager(blockchain blockchain, config *Config) *SessionManager {
|
||||||
|
|
||||||
s := &SessionManager{
|
s := &SessionManager{
|
||||||
sem: sem,
|
sem: sem,
|
||||||
sessions: make(map[string]*builder),
|
sessions: make(map[string]*miner.Builder),
|
||||||
sessionTimers: make(map[string]*time.Timer),
|
sessionTimers: make(map[string]*time.Timer),
|
||||||
blockchain: blockchain,
|
blockchain: blockchain,
|
||||||
config: config,
|
config: config,
|
||||||
|
|
@ -84,41 +85,22 @@ func (s *SessionManager) NewSession(ctx context.Context, args *api.BuildBlockArg
|
||||||
return "", ctx.Err()
|
return "", ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
parent := s.blockchain.CurrentHeader()
|
builderCfg := &miner.BuilderConfig{
|
||||||
chainConfig := s.blockchain.Config()
|
ChainConfig: s.blockchain.Config(),
|
||||||
|
Engine: s.blockchain.Engine(),
|
||||||
header := &types.Header{
|
// TODO
|
||||||
ParentHash: parent.Hash(),
|
|
||||||
Number: new(big.Int).Add(parent.Number, common.Big1),
|
|
||||||
GasLimit: core.CalcGasLimit(parent.GasLimit, s.config.GasCeil),
|
|
||||||
Time: 1000, // TODO: fix this
|
|
||||||
Coinbase: common.Address{}, // TODO: fix this
|
|
||||||
Difficulty: big.NewInt(1),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set baseFee and GasLimit if we are on an EIP-1559 chain
|
builderArgs := &miner.BuilderArgs{
|
||||||
if chainConfig.IsLondon(header.Number) {
|
ParentHash: args.Parent,
|
||||||
header.BaseFee = CalcBaseFee(chainConfig, parent)
|
|
||||||
if !chainConfig.IsLondon(parent.Number) {
|
|
||||||
parentGasLimit := parent.GasLimit * chainConfig.ElasticityMultiplier()
|
|
||||||
header.GasLimit = core.CalcGasLimit(parentGasLimit, s.config.GasCeil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stateRef, err := s.blockchain.StateAt(parent.Root)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &builderConfig{
|
|
||||||
preState: stateRef,
|
|
||||||
header: header,
|
|
||||||
config: s.blockchain.Config(),
|
|
||||||
context: s.blockchain,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
id := uuid.New().String()[:7]
|
id := uuid.New().String()[:7]
|
||||||
s.sessions[id] = newBuilder(cfg)
|
session, err := miner.NewBuilder(builderCfg, builderArgs)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
s.sessions[id] = session
|
||||||
|
|
||||||
// start session timer
|
// start session timer
|
||||||
s.sessionTimers[id] = time.AfterFunc(s.config.SessionIdleTimeout, func() {
|
s.sessionTimers[id] = time.AfterFunc(s.config.SessionIdleTimeout, func() {
|
||||||
|
|
@ -140,7 +122,7 @@ func (s *SessionManager) NewSession(ctx context.Context, args *api.BuildBlockArg
|
||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SessionManager) getSession(sessionId string) (*builder, error) {
|
func (s *SessionManager) getSession(sessionId string) (*miner.Builder, error) {
|
||||||
s.sessionsLock.RLock()
|
s.sessionsLock.RLock()
|
||||||
defer s.sessionsLock.RUnlock()
|
defer s.sessionsLock.RUnlock()
|
||||||
|
|
||||||
|
|
@ -168,7 +150,7 @@ func (s *SessionManager) AddBundle(sessionId string, bundle api.Bundle) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return builder.AddBundle(bundle)
|
return builder.AddBundle(nil) // TODO: Use api.Bundle type
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SessionManager) BuildBlock(sessionId string) error {
|
func (s *SessionManager) BuildBlock(sessionId string) error {
|
||||||
|
|
@ -176,7 +158,8 @@ func (s *SessionManager) BuildBlock(sessionId string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return builder.BuildBlock()
|
_, err = builder.BuildBlock() // TODO: Return more info
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// CalcBaseFee calculates the basefee of the header.
|
// CalcBaseFee calculates the basefee of the header.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue