mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
add suave package and builder
This commit is contained in:
parent
c66ca8bf7a
commit
28a45e3492
21 changed files with 1115 additions and 5 deletions
|
|
@ -196,6 +196,9 @@ var (
|
|||
utils.MetricsInfluxDBBucketFlag,
|
||||
utils.MetricsInfluxDBOrganizationFlag,
|
||||
}
|
||||
suaveApiFlags = []cli.Flag{
|
||||
utils.SuaveEnabled,
|
||||
}
|
||||
)
|
||||
|
||||
var app = flags.NewApp("the go-ethereum command line interface")
|
||||
|
|
@ -245,6 +248,7 @@ func init() {
|
|||
consoleFlags,
|
||||
debug.Flags,
|
||||
metricsFlags,
|
||||
suaveApiFlags,
|
||||
)
|
||||
flags.AutoEnvVars(app.Flags, "GETH")
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/suave"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/triedb/hashdb"
|
||||
"github.com/ethereum/go-ethereum/trie/triedb/pathdb"
|
||||
|
|
@ -908,6 +909,13 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
|
|||
Value: metrics.DefaultConfig.InfluxDBOrganization,
|
||||
Category: flags.MetricsCategory,
|
||||
}
|
||||
|
||||
// SUAVE namespace rpc settings
|
||||
SuaveEnabled = &cli.BoolFlag{
|
||||
Name: "suave",
|
||||
Usage: "Enable the suave",
|
||||
Category: flags.SuaveCategory,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -1344,6 +1352,10 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
|
|||
}
|
||||
}
|
||||
|
||||
func SetSuaveConfig(ctx *cli.Context, cfg *suave.Config) {
|
||||
cfg.Enabled = ctx.IsSet(SuaveEnabled.Name)
|
||||
}
|
||||
|
||||
// SetNodeConfig applies node-related command line flags to the config.
|
||||
func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
|
||||
SetP2PConfig(ctx, &cfg.P2P)
|
||||
|
|
@ -1859,11 +1871,18 @@ func SetDNSDiscoveryDefaults(cfg *ethconfig.Config, genesis common.Hash) {
|
|||
|
||||
// RegisterEthService adds an Ethereum client to the stack.
|
||||
// The second return value is the full node instance.
|
||||
func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend, *eth.Ethereum) {
|
||||
func RegisterEthService(stack *node.Node, cfg *ethconfig.Config, suaveConfig *suave.Config) (ethapi.Backend, *eth.Ethereum) {
|
||||
backend, err := eth.New(stack, cfg)
|
||||
if err != nil {
|
||||
Fatalf("Failed to register the Ethereum service: %v", err)
|
||||
}
|
||||
if suaveConfig.Enabled {
|
||||
log.Info("Enable suave service")
|
||||
if err := suave.Register(stack, backend, suaveConfig); err != nil {
|
||||
Fatalf("Failed to register the suave service: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
stack.RegisterAPIs(tracers.APIs(backend.APIBackend))
|
||||
return backend.APIBackend, backend
|
||||
}
|
||||
|
|
|
|||
95
core/types/sbundle.go
Normal file
95
core/types/sbundle.go
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
// Simplified Share Bundle Type for PoC
|
||||
|
||||
type SBundle struct {
|
||||
BlockNumber *big.Int `json:"blockNumber,omitempty"` // if BlockNumber is set it must match DecryptionCondition!
|
||||
MaxBlock *big.Int `json:"maxBlock,omitempty"`
|
||||
Txs Transactions `json:"txs"`
|
||||
RevertingHashes []common.Hash `json:"revertingHashes,omitempty"`
|
||||
RefundPercent *int `json:"percent,omitempty"`
|
||||
}
|
||||
|
||||
type RpcSBundle struct {
|
||||
BlockNumber *hexutil.Big `json:"blockNumber,omitempty"`
|
||||
MaxBlock *hexutil.Big `json:"maxBlock,omitempty"`
|
||||
Txs []hexutil.Bytes `json:"txs"`
|
||||
RevertingHashes []common.Hash `json:"revertingHashes,omitempty"`
|
||||
RefundPercent *int `json:"percent,omitempty"`
|
||||
}
|
||||
|
||||
func (s *SBundle) MarshalJSON() ([]byte, error) {
|
||||
txs := []hexutil.Bytes{}
|
||||
for _, tx := range s.Txs {
|
||||
txBytes, err := tx.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txs = append(txs, txBytes)
|
||||
}
|
||||
|
||||
var blockNumber *hexutil.Big
|
||||
if s.BlockNumber != nil {
|
||||
blockNumber = new(hexutil.Big)
|
||||
*blockNumber = hexutil.Big(*s.BlockNumber)
|
||||
}
|
||||
|
||||
return json.Marshal(&RpcSBundle{
|
||||
BlockNumber: blockNumber,
|
||||
Txs: txs,
|
||||
RevertingHashes: s.RevertingHashes,
|
||||
RefundPercent: s.RefundPercent,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *SBundle) UnmarshalJSON(data []byte) error {
|
||||
var rpcSBundle RpcSBundle
|
||||
if err := json.Unmarshal(data, &rpcSBundle); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var txs Transactions
|
||||
for _, txBytes := range rpcSBundle.Txs {
|
||||
var tx Transaction
|
||||
err := tx.UnmarshalBinary(txBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txs = append(txs, &tx)
|
||||
}
|
||||
|
||||
s.BlockNumber = (*big.Int)(rpcSBundle.BlockNumber)
|
||||
s.MaxBlock = (*big.Int)(rpcSBundle.MaxBlock)
|
||||
s.Txs = txs
|
||||
s.RevertingHashes = rpcSBundle.RevertingHashes
|
||||
s.RefundPercent = rpcSBundle.RefundPercent
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type RPCMevShareBundle struct {
|
||||
Version string `json:"version"`
|
||||
Inclusion struct {
|
||||
Block string `json:"block"`
|
||||
MaxBlock string `json:"maxBlock"`
|
||||
} `json:"inclusion"`
|
||||
Body []struct {
|
||||
Tx string `json:"tx"`
|
||||
CanRevert bool `json:"canRevert"`
|
||||
} `json:"body"`
|
||||
Validity struct {
|
||||
Refund []struct {
|
||||
BodyIdx int `json:"bodyIdx"`
|
||||
Percent int `json:"percent"`
|
||||
} `json:"refund"`
|
||||
} `json:"validity"`
|
||||
}
|
||||
32
core/types/suave_structs.go
Normal file
32
core/types/suave_structs.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Code generated by suave/gen in https://github.com/flashbots/suave-geth.
|
||||
// DO NOT EDIT.
|
||||
// Hash: c60f303834fbdbbd940aae7cb3679cf3755a25f7384f1052c20bf6c38d9a0451
|
||||
package types
|
||||
|
||||
import "github.com/ethereum/go-ethereum/common"
|
||||
|
||||
type DataId [16]byte
|
||||
|
||||
// Structs
|
||||
|
||||
type BuildBlockArgs struct {
|
||||
Slot uint64
|
||||
ProposerPubkey []byte
|
||||
Parent common.Hash
|
||||
Timestamp uint64
|
||||
FeeRecipient common.Address
|
||||
GasLimit uint64
|
||||
Random common.Hash
|
||||
Withdrawals []*Withdrawal
|
||||
Extra []byte
|
||||
FillPending bool
|
||||
}
|
||||
|
||||
type DataRecord struct {
|
||||
Id DataId
|
||||
Salt DataId
|
||||
DecryptionCondition uint64
|
||||
AllowedPeekers []common.Address
|
||||
AllowedStores []common.Address
|
||||
Version string
|
||||
}
|
||||
|
|
@ -415,3 +415,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) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
|
||||
return b.eth.stateAtTransaction(ctx, block, txIndex, reexec)
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) BuildBlockFromTxs(ctx context.Context, buildArgs *types.BuildBlockArgs, txs types.Transactions) (*types.Block, *big.Int, error) {
|
||||
return b.eth.Miner().BuildBlockFromTxs(ctx, buildArgs, txs)
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) BuildBlockFromBundles(ctx context.Context, buildArgs *types.BuildBlockArgs, bundles []types.SBundle) (*types.Block, *big.Int, error) {
|
||||
return b.eth.Miner().BuildBlockFromBundles(ctx, buildArgs, bundles)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@ import (
|
|||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
suave_builder "github.com/ethereum/go-ethereum/suave/builder"
|
||||
suave_builder_api "github.com/ethereum/go-ethereum/suave/builder/api"
|
||||
)
|
||||
|
||||
// Config contains the configuration options of the ETH protocol.
|
||||
|
|
@ -309,6 +311,19 @@ func makeExtraData(extra []byte) []byte {
|
|||
func (s *Ethereum) APIs() []rpc.API {
|
||||
apis := ethapi.GetAPIs(s.APIBackend)
|
||||
|
||||
// Append SUAVE-enabled node backend
|
||||
apis = append(apis, rpc.API{
|
||||
Namespace: "suavex",
|
||||
Service: backends.NewEthBackendServer(s.APIBackend),
|
||||
})
|
||||
|
||||
sessionManager := suave_builder.NewSessionManager(s.blockchain, &suave_builder.Config{})
|
||||
|
||||
apis = append(apis, rpc.API{
|
||||
Namespace: "suavex",
|
||||
Service: suave_builder_api.NewServer(sessionManager),
|
||||
})
|
||||
|
||||
// Append any APIs exposed explicitly by the consensus engine
|
||||
apis = append(apis, s.engine.APIs(s.BlockChain())...)
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/internal/blocktest"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/exp/slices"
|
||||
|
|
@ -597,6 +598,24 @@ func (b testBackend) ServiceFilter(ctx context.Context, session *bloombits.Match
|
|||
panic("implement me")
|
||||
}
|
||||
|
||||
func (n *testBackend) BuildBlockFromTxs(ctx context.Context, buildArgs *types.BuildBlockArgs, txs types.Transactions) (*types.Block, *big.Int, error) {
|
||||
block := types.NewBlock(&types.Header{GasUsed: 1000, BaseFee: big.NewInt(1)}, txs, nil, nil, trie.NewStackTrie(nil))
|
||||
return block, big.NewInt(11000), nil
|
||||
}
|
||||
|
||||
func (n *testBackend) BuildBlockFromBundles(ctx context.Context, buildArgs *types.BuildBlockArgs, bundles []types.SBundle) (*types.Block, *big.Int, error) {
|
||||
var txs types.Transactions
|
||||
for _, bundle := range bundles {
|
||||
txs = append(txs, bundle.Txs...)
|
||||
}
|
||||
block := types.NewBlock(&types.Header{GasUsed: 1000, BaseFee: big.NewInt(1)}, txs, nil, nil, trie.NewStackTrie(nil))
|
||||
return block, big.NewInt(11000), nil
|
||||
}
|
||||
|
||||
func (n *testBackend) Call(ctx context.Context, contractAddr common.Address, input []byte) ([]byte, error) {
|
||||
return []byte{0x1}, nil
|
||||
}
|
||||
|
||||
func TestEstimateGas(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Initialize test accounts
|
||||
|
|
|
|||
|
|
@ -97,6 +97,10 @@ type Backend interface {
|
|||
SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription
|
||||
BloomStatus() (uint64, uint64)
|
||||
ServiceFilter(ctx context.Context, session *bloombits.MatcherSession)
|
||||
|
||||
// SUAVE Execution Methods
|
||||
BuildBlockFromTxs(ctx context.Context, buildArgs *types.BuildBlockArgs, txs types.Transactions) (*types.Block, *big.Int, error)
|
||||
BuildBlockFromBundles(ctx context.Context, buildArgs *types.BuildBlockArgs, bundles []types.SBundle) (*types.Block, *big.Int, error)
|
||||
}
|
||||
|
||||
func GetAPIs(apiBackend Backend) []rpc.API {
|
||||
|
|
|
|||
|
|
@ -365,3 +365,15 @@ func (b *backendMock) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent)
|
|||
}
|
||||
|
||||
func (b *backendMock) Engine() consensus.Engine { return nil }
|
||||
|
||||
func (n *backendMock) BuildBlockFromTxs(ctx context.Context, buildArgs *types.BuildBlockArgs, txs types.Transactions) (*types.Block, *big.Int, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
func (n *backendMock) BuildBlockFromBundles(ctx context.Context, buildArgs *types.BuildBlockArgs, bundles []types.SBundle) (*types.Block, *big.Int, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
func (n *backendMock) Call(ctx context.Context, contractAddr common.Address, input []byte) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ const (
|
|||
MiscCategory = "MISC"
|
||||
TestingCategory = "TESTING"
|
||||
DeprecatedCategory = "ALIASED (deprecated)"
|
||||
SuaveCategory = "SUAVE"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
package miner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
|
@ -244,3 +245,11 @@ func (miner *Miner) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscript
|
|||
func (miner *Miner) BuildPayload(args *BuildPayloadArgs) (*Payload, error) {
|
||||
return miner.worker.buildPayload(args)
|
||||
}
|
||||
|
||||
func (miner *Miner) BuildBlockFromTxs(ctx context.Context, buildArgs *types.BuildBlockArgs, txs types.Transactions) (*types.Block, *big.Int, error) {
|
||||
return miner.worker.buildBlockFromTxs(ctx, buildArgs, txs)
|
||||
}
|
||||
|
||||
func (miner *Miner) BuildBlockFromBundles(ctx context.Context, buildArgs *types.BuildBlockArgs, bundles []types.SBundle) (*types.Block, *big.Int, error) {
|
||||
return miner.worker.buildBlockFromBundles(ctx, buildArgs, bundles)
|
||||
}
|
||||
|
|
|
|||
235
miner/worker.go
235
miner/worker.go
|
|
@ -17,6 +17,7 @@
|
|||
package miner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
|
@ -33,6 +34,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
"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/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -888,11 +890,13 @@ func (w *worker) commitTransactions(env *environment, txs *transactionsByPriceAn
|
|||
|
||||
// generateParams wraps various of settings for generating sealing task.
|
||||
type generateParams struct {
|
||||
timestamp uint64 // The timstamp for sealing task
|
||||
forceTime bool // Flag whether the given timestamp is immutable or not
|
||||
parentHash common.Hash // Parent block hash, empty means the latest chain head
|
||||
coinbase common.Address // The fee recipient address for including transaction
|
||||
timestamp uint64 // The timstamp for sealing task
|
||||
forceTime bool // Flag whether the given timestamp is immutable or not
|
||||
parentHash common.Hash // Parent block hash, empty means the latest chain head
|
||||
coinbase common.Address // The fee recipient address for including transaction
|
||||
gasLimit uint64
|
||||
random common.Hash // The randomness generated by beacon chain, empty before the merge
|
||||
extra []byte // The extra data to include in the block header
|
||||
withdrawals types.Withdrawals // List of withdrawals to include in block.
|
||||
beaconRoot *common.Hash // The beacon root (cancun field).
|
||||
noTxs bool // Flag whether an empty block without any transaction is expected
|
||||
|
|
@ -1212,3 +1216,226 @@ func signalToErr(signal int32) error {
|
|||
panic(fmt.Errorf("undefined signal %d", signal))
|
||||
}
|
||||
}
|
||||
|
||||
// SUAVE
|
||||
|
||||
func (w *worker) rawCommitTransactions(env *environment, txs types.Transactions) error {
|
||||
gasLimit := env.header.GasLimit
|
||||
if env.gasPool == nil {
|
||||
env.gasPool = new(core.GasPool).AddGas(gasLimit)
|
||||
}
|
||||
|
||||
// TODO: logs should be a part of the env and returned to whoever requested the block
|
||||
// var coalescedLogs []*types.Log
|
||||
|
||||
for _, tx := range txs {
|
||||
// If we don't have enough gas for any further transactions then we're done.
|
||||
if env.gasPool.Gas() < params.TxGas {
|
||||
log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", params.TxGas)
|
||||
break
|
||||
}
|
||||
// Error may be ignored here. The error has already been checked
|
||||
// during transaction acceptance is the transaction pool.
|
||||
from, _ := types.Sender(env.signer, tx)
|
||||
|
||||
// Check whether the tx is replay protected. If we're not in the EIP155 hf
|
||||
// phase, start ignoring the sender until we do.
|
||||
if tx.Protected() && !w.chainConfig.IsEIP155(env.header.Number) {
|
||||
log.Trace("Ignoring reply protected transaction", "hash", tx.Hash(), "eip155", w.chainConfig.EIP155Block)
|
||||
|
||||
return fmt.Errorf("invalid reply protected tx %s", tx.Hash())
|
||||
}
|
||||
|
||||
// Start executing the transaction
|
||||
env.state.SetTxContext(tx.Hash(), env.tcount)
|
||||
|
||||
// logs, err := w.commitTransaction(env, tx)
|
||||
_, err := w.commitTransaction(env, tx)
|
||||
|
||||
switch {
|
||||
case errors.Is(err, core.ErrNonceTooLow):
|
||||
log.Debug("Skipping transaction with low nonce", "hash", tx.Hash(), "sender", from, "nonce", tx.Nonce())
|
||||
return err
|
||||
case errors.Is(err, nil):
|
||||
// coalescedLogs = append(coalescedLogs, logs...)
|
||||
env.tcount++
|
||||
default:
|
||||
// Transaction is regarded as invalid, drop all consecutive transactions from
|
||||
// the same sender because of `nonce-too-high` clause.
|
||||
log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *worker) commitPendingTxs(work *environment) error {
|
||||
interrupt := new(atomic.Int32)
|
||||
timer := time.AfterFunc(w.newpayloadTimeout, func() {
|
||||
interrupt.Store(commitInterruptTimeout)
|
||||
})
|
||||
defer timer.Stop()
|
||||
if err := w.fillTransactions(nil, work); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *worker) buildBlockFromTxs(ctx context.Context, args *types.BuildBlockArgs, txs types.Transactions) (*types.Block, *big.Int, error) {
|
||||
params := &generateParams{
|
||||
timestamp: args.Timestamp,
|
||||
forceTime: true,
|
||||
parentHash: args.Parent,
|
||||
coinbase: args.FeeRecipient,
|
||||
gasLimit: args.GasLimit,
|
||||
random: args.Random,
|
||||
extra: args.Extra,
|
||||
withdrawals: args.Withdrawals,
|
||||
// noUncle: true,
|
||||
noTxs: false,
|
||||
}
|
||||
|
||||
work, err := w.prepareWork(params)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer work.discard()
|
||||
|
||||
profitPre := work.state.GetBalance(args.FeeRecipient)
|
||||
|
||||
if err := w.rawCommitTransactions(work, txs); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if args.FillPending {
|
||||
if err := w.commitPendingTxs(work); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
profitPost := work.state.GetBalance(args.FeeRecipient)
|
||||
// TODO : Is it okay to set Uncle List to nil?
|
||||
block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, nil, work.receipts, params.withdrawals)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
blockProfit := new(big.Int).Sub(profitPost, profitPre)
|
||||
return block, blockProfit, nil
|
||||
}
|
||||
|
||||
func (w *worker) buildBlockFromBundles(ctx context.Context, args *types.BuildBlockArgs, bundles []types.SBundle) (*types.Block, *big.Int, error) {
|
||||
// create ephemeral addr and private key for payment txn
|
||||
ephemeralPrivKey, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ephemeralAddr := crypto.PubkeyToAddress(ephemeralPrivKey.PublicKey)
|
||||
|
||||
params := &generateParams{
|
||||
timestamp: args.Timestamp,
|
||||
forceTime: true,
|
||||
parentHash: args.Parent,
|
||||
coinbase: ephemeralAddr, // NOTE : overriding BuildBlockArgs.FeeRecipient TODO : make customizable
|
||||
gasLimit: args.GasLimit,
|
||||
random: args.Random,
|
||||
extra: args.Extra,
|
||||
withdrawals: args.Withdrawals,
|
||||
// noUncle: true,
|
||||
noTxs: false,
|
||||
}
|
||||
|
||||
work, err := w.prepareWork(params)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer work.discard()
|
||||
|
||||
// Assume static 28000 gas transfers for both mev-share and proposer payments
|
||||
refundTransferCost := new(big.Int).Mul(big.NewInt(28000), work.header.BaseFee)
|
||||
|
||||
profitPre := work.state.GetBalance(params.coinbase)
|
||||
|
||||
for _, bundle := range bundles {
|
||||
// NOTE: failing bundles will cause the block to not be built!
|
||||
|
||||
// apply bundle
|
||||
profitPreBundle := work.state.GetBalance(params.coinbase)
|
||||
if err := w.rawCommitTransactions(work, bundle.Txs); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
profitPostBundle := work.state.GetBalance(params.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 nil, nil, 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 nil, nil, err
|
||||
}
|
||||
|
||||
// commit payment txn
|
||||
if err := w.rawCommitTransactions(work, types.Transactions{paymentTx}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
if args.FillPending {
|
||||
if err := w.commitPendingTxs(work); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
profitPost := work.state.GetBalance(params.coinbase)
|
||||
proposerProfit := new(big.Int).Set(profitPost) // = post-pre-transfer_cost
|
||||
proposerProfit = proposerProfit.Sub(profitPost, profitPre)
|
||||
proposerProfit = proposerProfit.Sub(proposerProfit, refundTransferCost)
|
||||
|
||||
currNonce := work.state.GetNonce(ephemeralAddr)
|
||||
paymentTx, err := types.SignTx(types.NewTx(&types.LegacyTx{
|
||||
Nonce: currNonce,
|
||||
To: &args.FeeRecipient,
|
||||
Value: proposerProfit,
|
||||
Gas: 28000,
|
||||
GasPrice: work.header.BaseFee,
|
||||
}), work.signer, ephemeralPrivKey)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not sign proposer payment: %w", err)
|
||||
}
|
||||
|
||||
// commit payment txn
|
||||
if err := w.rawCommitTransactions(work, types.Transactions{paymentTx}); err != nil {
|
||||
return nil, nil, fmt.Errorf("could not sign proposer payment: %w", err)
|
||||
}
|
||||
|
||||
log.Info("buildBlockFromBundles", "num_bundles", len(bundles), "num_txns", len(work.txs), "profit", proposerProfit)
|
||||
// TODO : Is it okay to set Uncle List to nil?
|
||||
block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, nil, work.receipts, params.withdrawals)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return block, proposerProfit, nil
|
||||
}
|
||||
|
|
|
|||
12
suave/builder/api/api.go
Normal file
12
suave/builder/api/api.go
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
type API interface {
|
||||
NewSession(ctx context.Context) (string, error)
|
||||
AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) (*types.SimulateTransactionResult, error)
|
||||
}
|
||||
42
suave/builder/api/api_client.go
Normal file
42
suave/builder/api/api_client.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
var _ API = (*APIClient)(nil)
|
||||
|
||||
type APIClient struct {
|
||||
rpc rpcClient
|
||||
}
|
||||
|
||||
func NewClient(endpoint string) (*APIClient, error) {
|
||||
clt, err := rpc.Dial(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewClientFromRPC(clt), nil
|
||||
}
|
||||
|
||||
type rpcClient interface {
|
||||
CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error
|
||||
}
|
||||
|
||||
func NewClientFromRPC(rpc rpcClient) *APIClient {
|
||||
return &APIClient{rpc: rpc}
|
||||
}
|
||||
|
||||
func (a *APIClient) NewSession(ctx context.Context) (string, error) {
|
||||
var id string
|
||||
err := a.rpc.CallContext(ctx, &id, "suavex_newSession")
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (a *APIClient) AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) (*types.SimulateTransactionResult, error) {
|
||||
var receipt *types.SimulateTransactionResult
|
||||
err := a.rpc.CallContext(ctx, &receipt, "suavex_addTransaction", sessionId, tx)
|
||||
return receipt, err
|
||||
}
|
||||
43
suave/builder/api/api_server.go
Normal file
43
suave/builder/api/api_server.go
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// sessionManager is the backend that manages the session state of the builder API.
|
||||
type sessionManager interface {
|
||||
NewSession() (string, error)
|
||||
AddTransaction(sessionId string, tx *types.Transaction) (*types.SimulateTransactionResult, error)
|
||||
}
|
||||
|
||||
func NewServer(s sessionManager) *Server {
|
||||
api := &Server{
|
||||
sessionMngr: s,
|
||||
}
|
||||
return api
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
sessionMngr sessionManager
|
||||
}
|
||||
|
||||
func (s *Server) NewSession(ctx context.Context) (string, error) {
|
||||
return s.sessionMngr.NewSession()
|
||||
}
|
||||
|
||||
func (s *Server) AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) (*types.SimulateTransactionResult, error) {
|
||||
return s.sessionMngr.AddTransaction(sessionId, tx)
|
||||
}
|
||||
|
||||
type MockServer struct {
|
||||
}
|
||||
|
||||
func (s *MockServer) NewSession(ctx context.Context) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (s *MockServer) AddTransaction(ctx context.Context, sessionId string, tx *types.Transaction) (*types.SimulateTransactionResult, error) {
|
||||
return &types.SimulateTransactionResult{}, nil
|
||||
}
|
||||
39
suave/builder/api/api_test.go
Normal file
39
suave/builder/api/api_test.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAPI(t *testing.T) {
|
||||
srv := rpc.NewServer()
|
||||
|
||||
builderAPI := NewServer(&nullSessionManager{})
|
||||
srv.RegisterName("suavex", builderAPI)
|
||||
|
||||
c := NewClientFromRPC(rpc.DialInProc(srv))
|
||||
|
||||
res0, err := c.NewSession(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, res0, "1")
|
||||
|
||||
txn := types.NewTransaction(0, common.Address{}, big.NewInt(1), 1, big.NewInt(1), []byte{})
|
||||
_, err = c.AddTransaction(context.Background(), "1", txn)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
type nullSessionManager struct{}
|
||||
|
||||
func (n *nullSessionManager) NewSession() (string, error) {
|
||||
return "1", nil
|
||||
}
|
||||
|
||||
func (n *nullSessionManager) AddTransaction(sessionId string, tx *types.Transaction) (*types.SimulateTransactionResult, error) {
|
||||
return &types.SimulateTransactionResult{Logs: []*types.SimulatedLog{}}, nil
|
||||
}
|
||||
77
suave/builder/builder.go
Normal file
77
suave/builder/builder.go
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
package builder
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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/params"
|
||||
)
|
||||
|
||||
type builder struct {
|
||||
config *builderConfig
|
||||
txns []*types.Transaction
|
||||
receipts []*types.Receipt
|
||||
state *state.StateDB
|
||||
gasPool *core.GasPool
|
||||
gasUsed *uint64
|
||||
}
|
||||
|
||||
type builderConfig struct {
|
||||
preState *state.StateDB
|
||||
header *types.Header
|
||||
config *params.ChainConfig
|
||||
context core.ChainContext
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
122
suave/builder/builder_test.go
Normal file
122
suave/builder/builder_test.go
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
138
suave/builder/session_manager.go
Normal file
138
suave/builder/session_manager.go
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
package builder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||
"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/params"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// blockchain is the minimum interface to the blockchain
|
||||
// required to build a block
|
||||
type blockchain interface {
|
||||
core.ChainContext
|
||||
|
||||
// Header returns the current tip of the chain
|
||||
CurrentHeader() *types.Header
|
||||
|
||||
// StateAt returns the state at the given root
|
||||
StateAt(root common.Hash) (*state.StateDB, error)
|
||||
|
||||
// Config returns the chain config
|
||||
Config() *params.ChainConfig
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
GasCeil uint64
|
||||
SessionIdleTimeout time.Duration
|
||||
}
|
||||
|
||||
type SessionManager struct {
|
||||
sessions map[string]*builder
|
||||
sessionTimers map[string]*time.Timer
|
||||
sessionsLock sync.RWMutex
|
||||
blockchain blockchain
|
||||
config *Config
|
||||
}
|
||||
|
||||
func NewSessionManager(blockchain blockchain, config *Config) *SessionManager {
|
||||
if config.GasCeil == 0 {
|
||||
config.GasCeil = 1000000000000000000
|
||||
}
|
||||
if config.SessionIdleTimeout == 0 {
|
||||
config.SessionIdleTimeout = 5 * time.Second
|
||||
}
|
||||
|
||||
s := &SessionManager{
|
||||
sessions: make(map[string]*builder),
|
||||
sessionTimers: make(map[string]*time.Timer),
|
||||
blockchain: blockchain,
|
||||
config: config,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// NewSession creates a new builder session and returns the session id
|
||||
func (s *SessionManager) NewSession() (string, error) {
|
||||
s.sessionsLock.Lock()
|
||||
defer s.sessionsLock.Unlock()
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
// Set baseFee and GasLimit if we are on an EIP-1559 chain
|
||||
if chainConfig.IsLondon(header.Number) {
|
||||
header.BaseFee = misc.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]
|
||||
s.sessions[id] = newBuilder(cfg)
|
||||
|
||||
// start session timer
|
||||
s.sessionTimers[id] = time.AfterFunc(s.config.SessionIdleTimeout, func() {
|
||||
s.sessionsLock.Lock()
|
||||
defer s.sessionsLock.Unlock()
|
||||
|
||||
delete(s.sessions, id)
|
||||
delete(s.sessionTimers, id)
|
||||
})
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (s *SessionManager) getSession(sessionId string) (*builder, error) {
|
||||
s.sessionsLock.RLock()
|
||||
defer s.sessionsLock.RUnlock()
|
||||
|
||||
session, ok := s.sessions[sessionId]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("session %s not found", sessionId)
|
||||
}
|
||||
|
||||
// reset session timer
|
||||
s.sessionTimers[sessionId].Reset(s.config.SessionIdleTimeout)
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (s *SessionManager) AddTransaction(sessionId string, tx *types.Transaction) (*types.SimulateTransactionResult, error) {
|
||||
builder, err := s.getSession(sessionId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return builder.AddTransaction(tx)
|
||||
}
|
||||
183
suave/builder/session_manager_test.go
Normal file
183
suave/builder/session_manager_test.go
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
package builder
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSessionManager_SessionTimeout(t *testing.T) {
|
||||
mngr, _ := newSessionManager(t, &Config{
|
||||
SessionIdleTimeout: 500 * time.Millisecond,
|
||||
})
|
||||
|
||||
id, err := mngr.NewSession()
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
_, err = mngr.getSession(id)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSessionManager_SessionRefresh(t *testing.T) {
|
||||
mngr, _ := newSessionManager(t, &Config{
|
||||
SessionIdleTimeout: 500 * time.Millisecond,
|
||||
})
|
||||
|
||||
id, err := mngr.NewSession()
|
||||
require.NoError(t, err)
|
||||
|
||||
// if we query the session under the idle timeout,
|
||||
// we should be able to refresh it
|
||||
for i := 0; i < 5; i++ {
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
|
||||
_, err = mngr.getSession(id)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// if we query the session after the idle timeout,
|
||||
// we should get an error
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
_, err = mngr.getSession(id)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSessionManager_StartSession(t *testing.T) {
|
||||
// test that the session starts and it can simulate transactions
|
||||
mngr, bMock := newSessionManager(t, &Config{})
|
||||
|
||||
id, err := mngr.NewSession()
|
||||
require.NoError(t, err)
|
||||
|
||||
txn := bMock.state.newTransfer(t, common.Address{}, big.NewInt(1))
|
||||
receipt, err := mngr.AddTransaction(id, txn)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, receipt)
|
||||
}
|
||||
|
||||
func newSessionManager(t *testing.T, cfg *Config) (*SessionManager, *blockchainMock) {
|
||||
if cfg == nil {
|
||||
cfg = &Config{}
|
||||
}
|
||||
|
||||
state := newMockState(t)
|
||||
|
||||
bMock := &blockchainMock{
|
||||
state: state,
|
||||
}
|
||||
return NewSessionManager(bMock, cfg), bMock
|
||||
}
|
||||
|
||||
type blockchainMock struct {
|
||||
state *mockState
|
||||
}
|
||||
|
||||
func (b *blockchainMock) Engine() consensus.Engine {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (b *blockchainMock) GetHeader(common.Hash, uint64) *types.Header {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (b *blockchainMock) Config() *params.ChainConfig {
|
||||
return b.state.chainConfig
|
||||
}
|
||||
|
||||
func (b *blockchainMock) CurrentHeader() *types.Header {
|
||||
return &types.Header{
|
||||
Number: big.NewInt(1),
|
||||
Difficulty: big.NewInt(1),
|
||||
Root: b.state.stateRoot,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *blockchainMock) StateAt(root common.Hash) (*state.StateDB, error) {
|
||||
return b.state.stateAt(root)
|
||||
}
|
||||
|
||||
type mockState struct {
|
||||
stateRoot common.Hash
|
||||
statedb state.Database
|
||||
|
||||
premineKey *ecdsa.PrivateKey
|
||||
premineKeyAdd common.Address
|
||||
|
||||
nextNonce uint64 // figure out a better way
|
||||
signer types.Signer
|
||||
|
||||
chainConfig *params.ChainConfig
|
||||
}
|
||||
|
||||
func newMockState(t *testing.T) *mockState {
|
||||
premineKey, _ := crypto.GenerateKey() // TODO: it would be nice to have it deterministic
|
||||
premineKeyAddr := crypto.PubkeyToAddress(premineKey.PublicKey)
|
||||
|
||||
// create a state reference with at least one premined account
|
||||
// In order to test the statedb in isolation, we are going
|
||||
// to commit this pre-state to a memory database
|
||||
db := state.NewDatabase(rawdb.NewMemoryDatabase())
|
||||
preState, err := state.New(types.EmptyRootHash, db, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
preState.AddBalance(premineKeyAddr, big.NewInt(1000000000000000000))
|
||||
|
||||
root, err := preState.Commit(true)
|
||||
require.NoError(t, err)
|
||||
|
||||
// for the sake of this test, we only need all the forks enabled
|
||||
chainConfig := params.SuaveChainConfig
|
||||
|
||||
// Disable london so that we do not check gasFeeCap (TODO: Fix)
|
||||
chainConfig.LondonBlock = big.NewInt(100)
|
||||
|
||||
return &mockState{
|
||||
statedb: db,
|
||||
stateRoot: root,
|
||||
premineKey: premineKey,
|
||||
premineKeyAdd: premineKeyAddr,
|
||||
signer: types.NewEIP155Signer(chainConfig.ChainID),
|
||||
chainConfig: chainConfig,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mockState) stateAt(root common.Hash) (*state.StateDB, error) {
|
||||
return state.New(root, m.statedb, nil)
|
||||
}
|
||||
|
||||
func (m *mockState) getNonce() uint64 {
|
||||
next := m.nextNonce
|
||||
m.nextNonce++
|
||||
return next
|
||||
}
|
||||
|
||||
func (m *mockState) newTransfer(t *testing.T, to common.Address, amount *big.Int) *types.Transaction {
|
||||
tx := types.NewTransaction(m.getNonce(), to, amount, 1000000, big.NewInt(1), nil)
|
||||
return m.newTxn(t, tx)
|
||||
}
|
||||
|
||||
func (m *mockState) newTxn(t *testing.T, tx *types.Transaction) *types.Transaction {
|
||||
// sign the transaction
|
||||
signature, err := crypto.Sign(m.signer.Hash(tx).Bytes(), m.premineKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
// include the signature in the transaction
|
||||
tx, err = tx.WithSignature(m.signer, signature)
|
||||
require.NoError(t, err)
|
||||
|
||||
return tx
|
||||
}
|
||||
9
suave/config.go
Normal file
9
suave/config.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package suave
|
||||
|
||||
type Config struct {
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
var DefaultConfig = Config{
|
||||
Enabled: false,
|
||||
}
|
||||
Loading…
Reference in a new issue