mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 05:06:43 +00:00
beacon/engine, eth/catalyst, miner: add GetInclusionListV1
This commit is contained in:
parent
1dab670156
commit
7ce64f1d65
5 changed files with 307 additions and 7 deletions
|
|
@ -46,3 +46,23 @@ func (inclusionList *InclusionList) UnmarshalJSON(input []byte) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func InclusionListToTransactions(inclusionList InclusionList) []*Transaction {
|
||||||
|
var txs []*Transaction
|
||||||
|
for _, encTx := range inclusionList {
|
||||||
|
var tx Transaction
|
||||||
|
if err := tx.UnmarshalBinary(encTx); err != nil {
|
||||||
|
continue // Skip invalid transactions
|
||||||
|
}
|
||||||
|
txs = append(txs, &tx)
|
||||||
|
}
|
||||||
|
return txs
|
||||||
|
}
|
||||||
|
|
||||||
|
func TransactionsToInclusionList(txs []*Transaction) InclusionList {
|
||||||
|
var enc = make([][]byte, len(txs))
|
||||||
|
for i, tx := range txs {
|
||||||
|
enc[i], _ = tx.MarshalBinary()
|
||||||
|
}
|
||||||
|
return enc
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,42 @@ const (
|
||||||
beaconUpdateWarnFrequency = 5 * time.Minute
|
beaconUpdateWarnFrequency = 5 * time.Minute
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// All methods provided over the engine endpoint.
|
||||||
|
var caps = []string{
|
||||||
|
"engine_forkchoiceUpdatedV1",
|
||||||
|
"engine_forkchoiceUpdatedV2",
|
||||||
|
"engine_forkchoiceUpdatedV3",
|
||||||
|
"engine_forkchoiceUpdatedWithWitnessV1",
|
||||||
|
"engine_forkchoiceUpdatedWithWitnessV2",
|
||||||
|
"engine_forkchoiceUpdatedWithWitnessV3",
|
||||||
|
"engine_exchangeTransitionConfigurationV1",
|
||||||
|
"engine_getPayloadV1",
|
||||||
|
"engine_getPayloadV2",
|
||||||
|
"engine_getPayloadV3",
|
||||||
|
"engine_getPayloadV4",
|
||||||
|
"engine_getPayloadV5",
|
||||||
|
"engine_getBlobsV1",
|
||||||
|
"engine_getBlobsV2",
|
||||||
|
"engine_getInclusionListV1",
|
||||||
|
"engine_newPayloadV1",
|
||||||
|
"engine_newPayloadV2",
|
||||||
|
"engine_newPayloadV3",
|
||||||
|
"engine_newPayloadV4",
|
||||||
|
"engine_newPayloadWithWitnessV1",
|
||||||
|
"engine_newPayloadWithWitnessV2",
|
||||||
|
"engine_newPayloadWithWitnessV3",
|
||||||
|
"engine_newPayloadWithWitnessV4",
|
||||||
|
"engine_executeStatelessPayloadV1",
|
||||||
|
"engine_executeStatelessPayloadV2",
|
||||||
|
"engine_executeStatelessPayloadV3",
|
||||||
|
"engine_executeStatelessPayloadV4",
|
||||||
|
"engine_getPayloadBodiesByHashV1",
|
||||||
|
"engine_getPayloadBodiesByHashV2",
|
||||||
|
"engine_getPayloadBodiesByRangeV1",
|
||||||
|
"engine_getPayloadBodiesByRangeV2",
|
||||||
|
"engine_getClientVersionV1",
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Number of blobs requested via getBlobsV2
|
// Number of blobs requested via getBlobsV2
|
||||||
getBlobsRequestedCounter = metrics.NewRegisteredCounter("engine/getblobs/requested", nil)
|
getBlobsRequestedCounter = metrics.NewRegisteredCounter("engine/getblobs/requested", nil)
|
||||||
|
|
@ -98,8 +134,9 @@ var (
|
||||||
type ConsensusAPI struct {
|
type ConsensusAPI struct {
|
||||||
eth *eth.Ethereum
|
eth *eth.Ethereum
|
||||||
|
|
||||||
remoteBlocks *headerQueue // Cache of remote payloads received
|
remoteBlocks *headerQueue // Cache of remote payloads received
|
||||||
localBlocks *payloadQueue // Cache of local payloads generated
|
localBlocks *payloadQueue // Cache of local payloads generated
|
||||||
|
localInclusionLists *inclusionListQueue // Cache of inclusion list generated
|
||||||
|
|
||||||
// The forkchoice update and new payload method require us to return the
|
// The forkchoice update and new payload method require us to return the
|
||||||
// latest valid hash in an invalid chain. To support that return, we need
|
// latest valid hash in an invalid chain. To support that return, we need
|
||||||
|
|
@ -149,11 +186,12 @@ func newConsensusAPIWithoutHeartbeat(eth *eth.Ethereum) *ConsensusAPI {
|
||||||
log.Warn("Engine API started but chain not configured for merge yet")
|
log.Warn("Engine API started but chain not configured for merge yet")
|
||||||
}
|
}
|
||||||
api := &ConsensusAPI{
|
api := &ConsensusAPI{
|
||||||
eth: eth,
|
eth: eth,
|
||||||
remoteBlocks: newHeaderQueue(),
|
remoteBlocks: newHeaderQueue(),
|
||||||
localBlocks: newPayloadQueue(),
|
localBlocks: newPayloadQueue(),
|
||||||
invalidBlocksHits: make(map[common.Hash]int),
|
localInclusionLists: newInclusionListQueue(),
|
||||||
invalidTipsets: make(map[common.Hash]*types.Header),
|
invalidBlocksHits: make(map[common.Hash]int),
|
||||||
|
invalidTipsets: make(map[common.Hash]*types.Header),
|
||||||
}
|
}
|
||||||
eth.Downloader().SetBadBlockCallback(api.setInvalidAncestor)
|
eth.Downloader().SetBadBlockCallback(api.setInvalidAncestor)
|
||||||
return api
|
return api
|
||||||
|
|
@ -589,6 +627,25 @@ func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProo
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *ConsensusAPI) GetInclusionListV1(parentHash common.Hash) (types.InclusionList, error) {
|
||||||
|
if inclusionList := api.localInclusionLists.get(parentHash); inclusionList != nil {
|
||||||
|
return inclusionList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
args := &miner.BuildInclusionListArgs{
|
||||||
|
Parent: parentHash,
|
||||||
|
}
|
||||||
|
inclusionList, err := api.eth.Miner().BuildInclusionList(args)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to build inclusion list", "err", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
api.localInclusionLists.put(parentHash, inclusionList)
|
||||||
|
|
||||||
|
return inclusionList, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Helper for NewPayload* methods.
|
// Helper for NewPayload* methods.
|
||||||
var invalidStatus = engine.PayloadStatusV1{Status: engine.INVALID}
|
var invalidStatus = engine.PayloadStatusV1{Status: engine.INVALID}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,10 @@ const maxTrackedPayloads = 10
|
||||||
// limit.
|
// limit.
|
||||||
const maxTrackedHeaders = 96
|
const maxTrackedHeaders = 96
|
||||||
|
|
||||||
|
// maxTrackedInclusionLists is the maximum number of inclusion lists the execution
|
||||||
|
// engine tracks before evicting old ones.
|
||||||
|
const maxTrackedInclusionLists = 8
|
||||||
|
|
||||||
// payloadQueueItem represents an id->payload tuple to store until it's retrieved
|
// payloadQueueItem represents an id->payload tuple to store until it's retrieved
|
||||||
// or evicted.
|
// or evicted.
|
||||||
type payloadQueueItem struct {
|
type payloadQueueItem struct {
|
||||||
|
|
@ -156,3 +160,54 @@ func (q *headerQueue) get(hash common.Hash) *types.Header {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inclusionListQueueItem represents an hash->inclusionList tuple to store until it's retrieved
|
||||||
|
// or evicted.
|
||||||
|
type inclusionListQueueItem struct {
|
||||||
|
parentHash common.Hash
|
||||||
|
inclusionList types.InclusionList
|
||||||
|
}
|
||||||
|
|
||||||
|
// inclusionListQueue tracks the latest handful of constructed inclusion lists to be retrieved
|
||||||
|
// by the beacon chain if inclusion list production is requested.
|
||||||
|
type inclusionListQueue struct {
|
||||||
|
inclusionLists []*inclusionListQueueItem
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// newinclusionListQueue creates a pre-initialized queue with a fixed number of slots
|
||||||
|
// all containing empty items.
|
||||||
|
func newInclusionListQueue() *inclusionListQueue {
|
||||||
|
return &inclusionListQueue{
|
||||||
|
inclusionLists: make([]*inclusionListQueueItem, maxTrackedInclusionLists),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// put inserts a new inclusion list into the queue at the given parent hash that
|
||||||
|
// the inclusion list is built upon.
|
||||||
|
func (q *inclusionListQueue) put(parentHash common.Hash, inclusionList types.InclusionList) {
|
||||||
|
q.lock.Lock()
|
||||||
|
defer q.lock.Unlock()
|
||||||
|
|
||||||
|
copy(q.inclusionLists[1:], q.inclusionLists)
|
||||||
|
q.inclusionLists[0] = &inclusionListQueueItem{
|
||||||
|
parentHash,
|
||||||
|
inclusionList,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get retrieves a previously stored inclusion list item or nil if it does not exist.
|
||||||
|
func (q *inclusionListQueue) get(parentHash common.Hash) types.InclusionList {
|
||||||
|
q.lock.RLock()
|
||||||
|
defer q.lock.RUnlock()
|
||||||
|
|
||||||
|
for _, item := range q.inclusionLists {
|
||||||
|
if item == nil {
|
||||||
|
return nil // no more items
|
||||||
|
}
|
||||||
|
if item.parentHash == parentHash {
|
||||||
|
return item.inclusionList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
49
miner/inclusion_list_building.go
Normal file
49
miner/inclusion_list_building.go
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
package miner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BuildInclusionListArgs contains the provided parameters for building inclusion list.
|
||||||
|
type BuildInclusionListArgs struct {
|
||||||
|
Parent common.Hash // The hash of the parent block to build the inclusion list upon
|
||||||
|
}
|
||||||
|
|
||||||
|
func (miner *Miner) BuildInclusionList(args *BuildInclusionListArgs) (types.InclusionList, error) {
|
||||||
|
genParams := &generateParams{
|
||||||
|
timestamp: uint64(time.Now().Unix()),
|
||||||
|
forceTime: false,
|
||||||
|
parentHash: args.Parent,
|
||||||
|
coinbase: miner.config.PendingFeeRecipient,
|
||||||
|
random: common.Hash{},
|
||||||
|
withdrawals: []*types.Withdrawal{},
|
||||||
|
beaconRoot: nil,
|
||||||
|
noTxs: false,
|
||||||
|
}
|
||||||
|
env, err := miner.prepareWork(genParams, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := miner.fillTransactions(nil, env); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
inclusionListTxs := make([]*types.Transaction, 0)
|
||||||
|
inclusionListSize := uint64(0)
|
||||||
|
|
||||||
|
for _, tx := range env.txs {
|
||||||
|
if inclusionListSize+tx.Size() > params.MaxBytesPerInclusionList {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
inclusionListTxs = append(inclusionListTxs, tx)
|
||||||
|
inclusionListSize += tx.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.TransactionsToInclusionList(inclusionListTxs), nil
|
||||||
|
}
|
||||||
119
miner/inclusion_list_building_test.go
Normal file
119
miner/inclusion_list_building_test.go
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
package miner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool"
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// testMinerBackend implements miner's Backend interface.
|
||||||
|
type testMinerBackend struct {
|
||||||
|
txPool *txpool.TxPool
|
||||||
|
chain *core.BlockChain
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *testMinerBackend) BlockChain() *core.BlockChain { return b.chain }
|
||||||
|
func (b *testMinerBackend) TxPool() *txpool.TxPool { return b.txPool }
|
||||||
|
|
||||||
|
func newTestMinerBackend(t *testing.T, genesis *core.Genesis, engine consensus.Engine) *testMinerBackend {
|
||||||
|
chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), genesis, engine, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("core.NewBlockChain failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testTxPoolConfig := legacypool.DefaultConfig
|
||||||
|
pool := legacypool.New(testTxPoolConfig, chain)
|
||||||
|
txpool, err := txpool.New(testTxPoolConfig.PriceLimit, chain, []txpool.SubPool{pool})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("txpool.New failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &testMinerBackend{
|
||||||
|
chain: chain,
|
||||||
|
txPool: txpool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestMiner(eth *testMinerBackend) *Miner {
|
||||||
|
return New(eth, DefaultConfig, eth.chain.Engine())
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPendingTransactions(fromPrivateKey *ecdsa.PrivateKey, toAddress common.Address) []*types.Transaction {
|
||||||
|
signer := types.LatestSigner(params.TestChainConfig)
|
||||||
|
|
||||||
|
tx1 := types.MustSignNewTx(fromPrivateKey, signer, &types.AccessListTx{
|
||||||
|
ChainID: params.TestChainConfig.ChainID,
|
||||||
|
Nonce: 0,
|
||||||
|
To: &toAddress,
|
||||||
|
Value: big.NewInt(1000),
|
||||||
|
Gas: params.TxGas,
|
||||||
|
GasPrice: big.NewInt(params.InitialBaseFee),
|
||||||
|
})
|
||||||
|
pendingTxs = append(pendingTxs, tx1)
|
||||||
|
|
||||||
|
tx2 := types.MustSignNewTx(fromPrivateKey, signer, &types.LegacyTx{
|
||||||
|
Nonce: 1,
|
||||||
|
To: &toAddress,
|
||||||
|
Value: big.NewInt(1000),
|
||||||
|
Gas: params.TxGas,
|
||||||
|
GasPrice: big.NewInt(params.InitialBaseFee),
|
||||||
|
})
|
||||||
|
pendingTxs = append(pendingTxs, tx2)
|
||||||
|
|
||||||
|
return pendingTxs
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildInclusionList(t *testing.T) {
|
||||||
|
var (
|
||||||
|
// Test accounts
|
||||||
|
testBankKey, _ = crypto.GenerateKey()
|
||||||
|
testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
|
||||||
|
testBankFunds = big.NewInt(1000000000000000000)
|
||||||
|
|
||||||
|
testUserKey, _ = crypto.GenerateKey()
|
||||||
|
testUserAddress = crypto.PubkeyToAddress(testUserKey.PublicKey)
|
||||||
|
|
||||||
|
// Test genesis and consensus engine
|
||||||
|
testGenesis = &core.Genesis{
|
||||||
|
Config: params.TestChainConfig,
|
||||||
|
Alloc: types.GenesisAlloc{testBankAddress: {Balance: testBankFunds}},
|
||||||
|
}
|
||||||
|
testEngine = ethash.NewFaker()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initialize miner and backend.
|
||||||
|
eth := newTestMinerBackend(t, testGenesis, testEngine)
|
||||||
|
miner := newTestMiner(eth)
|
||||||
|
|
||||||
|
// Add pending transactions to the pool.
|
||||||
|
pendingTxs := newPendingTransactions(testBankKey, testUserAddress)
|
||||||
|
eth.txPool.Add(pendingTxs, true)
|
||||||
|
|
||||||
|
// Build inclusion list.
|
||||||
|
args := &BuildInclusionListArgs{
|
||||||
|
Parent: eth.chain.CurrentBlock().Hash(),
|
||||||
|
}
|
||||||
|
inclusionList, err := miner.BuildInclusionList(args)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to build inclusion list %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify inclusion list size.
|
||||||
|
inclusionListSize := uint64(0)
|
||||||
|
for _, tx := range inclusionList {
|
||||||
|
inclusionListSize += uint64(len(tx))
|
||||||
|
}
|
||||||
|
require.LessOrEqual(t, inclusionListSize, params.MaxBytesPerInclusionList)
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue