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/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/miner"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/suave/builder/api"
|
||||
"github.com/google/uuid"
|
||||
|
|
@ -40,7 +41,7 @@ type Config struct {
|
|||
|
||||
type SessionManager struct {
|
||||
sem chan struct{}
|
||||
sessions map[string]*builder
|
||||
sessions map[string]*miner.Builder
|
||||
sessionTimers map[string]*time.Timer
|
||||
sessionsLock sync.RWMutex
|
||||
blockchain blockchain
|
||||
|
|
@ -65,7 +66,7 @@ func NewSessionManager(blockchain blockchain, config *Config) *SessionManager {
|
|||
|
||||
s := &SessionManager{
|
||||
sem: sem,
|
||||
sessions: make(map[string]*builder),
|
||||
sessions: make(map[string]*miner.Builder),
|
||||
sessionTimers: make(map[string]*time.Timer),
|
||||
blockchain: blockchain,
|
||||
config: config,
|
||||
|
|
@ -84,41 +85,22 @@ func (s *SessionManager) NewSession(ctx context.Context, args *api.BuildBlockArg
|
|||
return "", ctx.Err()
|
||||
}
|
||||
|
||||
parent := s.blockchain.CurrentHeader()
|
||||
chainConfig := s.blockchain.Config()
|
||||
|
||||
header := &types.Header{
|
||||
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),
|
||||
builderCfg := &miner.BuilderConfig{
|
||||
ChainConfig: s.blockchain.Config(),
|
||||
Engine: s.blockchain.Engine(),
|
||||
// TODO
|
||||
}
|
||||
|
||||
// Set baseFee and GasLimit if we are on an EIP-1559 chain
|
||||
if chainConfig.IsLondon(header.Number) {
|
||||
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,
|
||||
builderArgs := &miner.BuilderArgs{
|
||||
ParentHash: args.Parent,
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
func (s *SessionManager) getSession(sessionId string) (*builder, error) {
|
||||
func (s *SessionManager) getSession(sessionId string) (*miner.Builder, error) {
|
||||
s.sessionsLock.RLock()
|
||||
defer s.sessionsLock.RUnlock()
|
||||
|
||||
|
|
@ -168,7 +150,7 @@ func (s *SessionManager) AddBundle(sessionId string, bundle api.Bundle) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return builder.AddBundle(bundle)
|
||||
return builder.AddBundle(nil) // TODO: Use api.Bundle type
|
||||
}
|
||||
|
||||
func (s *SessionManager) BuildBlock(sessionId string) error {
|
||||
|
|
@ -176,7 +158,8 @@ func (s *SessionManager) BuildBlock(sessionId string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return builder.BuildBlock()
|
||||
_, err = builder.BuildBlock() // TODO: Return more info
|
||||
return err
|
||||
}
|
||||
|
||||
// CalcBaseFee calculates the basefee of the header.
|
||||
|
|
|
|||
Loading…
Reference in a new issue