mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 11:20:45 +00:00
blobpool ticket implementation
This commit is contained in:
parent
1022c7637d
commit
3cac28491c
16 changed files with 638 additions and 44 deletions
138
core/TicketAllocator.sol
Normal file
138
core/TicketAllocator.sol
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity 0.8.33;
|
||||
|
||||
contract TicketAllocator {
|
||||
address constant SYSTEM_ADDRESS = 0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE;
|
||||
|
||||
error NotEnoughTickets();
|
||||
error NotSystemContract();
|
||||
|
||||
struct Ticket {
|
||||
uint16 amount;
|
||||
uint256 blockNumber;
|
||||
}
|
||||
|
||||
mapping(address => Ticket[]) public queue;
|
||||
mapping(address => uint256) public head;
|
||||
mapping(address => uint16) public balance;
|
||||
|
||||
address[] public senders; // list of senders with >0 balance
|
||||
|
||||
uint16 public constant TOTAL_TICKETS = 21;
|
||||
uint16 public constant TIMEOUT = 2;
|
||||
|
||||
uint256 private leftTickets;
|
||||
uint256 private lastRequestBlock;
|
||||
|
||||
function requestTickets(address sender, uint16 numTickets) external {
|
||||
uint256 currentBlock = block.number;
|
||||
|
||||
if (currentBlock != lastRequestBlock) {
|
||||
leftTickets = TOTAL_TICKETS;
|
||||
lastRequestBlock = currentBlock;
|
||||
}
|
||||
|
||||
if (numTickets > leftTickets) {
|
||||
revert NotEnoughTickets();
|
||||
}
|
||||
|
||||
if (balance[sender] == 0) {
|
||||
senders.push(sender);
|
||||
}
|
||||
|
||||
queue[sender].push(Ticket({
|
||||
amount: numTickets,
|
||||
blockNumber: currentBlock
|
||||
}));
|
||||
|
||||
balance[sender] += numTickets;
|
||||
leftTickets -= numTickets;
|
||||
}
|
||||
|
||||
function checkBalance(address sender) public view returns(uint) {
|
||||
return balance[sender];
|
||||
}
|
||||
|
||||
// System call:
|
||||
// 1. remove expired and used tickets
|
||||
// - for used tickets, the oldest ticket is removed first
|
||||
// 3. return the overall sender balances
|
||||
fallback(bytes calldata input) external returns (bytes memory) {
|
||||
if (msg.sender != SYSTEM_ADDRESS) revert NotSystemContract();
|
||||
|
||||
address[] memory usedSenders;
|
||||
uint16[] memory usedAmounts;
|
||||
|
||||
if (input.length > 0) {
|
||||
(usedSenders, usedAmounts) = abi.decode(input, (address[], uint16[]));
|
||||
}
|
||||
|
||||
for (uint256 s = 0; s < senders.length; ) {
|
||||
address sender = senders[s];
|
||||
Ticket[] storage senderQueue = queue[sender];
|
||||
uint256 senderHead = head[sender];
|
||||
|
||||
// find the number of used tickets of this sender
|
||||
uint16 used = 0;
|
||||
for (uint16 i = 0; i < usedSenders.length; i++) {
|
||||
if (usedSenders[i] == sender) {
|
||||
used = usedAmounts[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (senderHead < senderQueue.length) {
|
||||
Ticket storage ticket = senderQueue[senderHead];
|
||||
|
||||
if (ticket.amount == 0) {
|
||||
senderHead++;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (ticket.blockNumber + TIMEOUT < block.number) {
|
||||
// remove expired tickets (comsume used tickets if possible)
|
||||
balance[sender] -= ticket.amount;
|
||||
if (used > 0) {
|
||||
// used = max(0, used - t.amount)
|
||||
uint16 deduct = used > ticket.amount ? ticket.amount : used;
|
||||
used -= deduct;
|
||||
}
|
||||
ticket.amount = 0;
|
||||
senderHead++;
|
||||
} else if (used > 0) {
|
||||
// remove used ticekts (for now we don't revert when the ticket amount is less than used tickets)
|
||||
uint16 deduct = used > ticket.amount ? ticket.amount : used;
|
||||
ticket.amount -= uint16(deduct);
|
||||
balance[sender] -= deduct;
|
||||
used -= deduct;
|
||||
|
||||
if (ticket.amount == 0) senderHead++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
head[sender] = senderHead;
|
||||
|
||||
// remove the sender from balance map if its balance became 0
|
||||
if (balance[sender] == 0) {
|
||||
senders[s] = senders[senders.length - 1];
|
||||
senders.pop();
|
||||
continue; // process swapped-in sender at same index (no s++)
|
||||
}
|
||||
s++;
|
||||
}
|
||||
|
||||
// return the active senders and their balances
|
||||
address[] memory activeSenders = new address[](senders.length);
|
||||
uint16[] memory activeBalances = new uint16[](senders.length);
|
||||
|
||||
for (uint256 i = 0; i < senders.length; i++) {
|
||||
activeSenders[i] = senders[i];
|
||||
activeBalances[i] = balance[senders[i]];
|
||||
}
|
||||
|
||||
return abi.encode(activeSenders, activeBalances);
|
||||
}
|
||||
}
|
||||
|
|
@ -350,6 +350,9 @@ type BlockChain struct {
|
|||
txLookupLock sync.RWMutex
|
||||
txLookupCache *lru.Cache[common.Hash, txLookup]
|
||||
|
||||
ticketBlocks map[*big.Int][]common.Hash // `ticketBlocks` is block hashes mapped by its number, whose ticket information is cached.
|
||||
tickets map[common.Hash]map[common.Address]uint16 // `tickets` is a cache of ticket balances processed for each leaf block.
|
||||
|
||||
stopping atomic.Bool // false if chain is running, true when stopped
|
||||
procInterrupt atomic.Bool // interrupt signaler for block processing
|
||||
|
||||
|
|
@ -407,6 +410,8 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
|
|||
receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit),
|
||||
blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit),
|
||||
txLookupCache: lru.NewCache[common.Hash, txLookup](txLookupCacheLimit),
|
||||
ticketBlocks: make(map[*big.Int][]common.Hash),
|
||||
tickets: make(map[common.Hash]map[common.Address]uint16),
|
||||
engine: engine,
|
||||
logger: cfg.VmConfig.Tracer,
|
||||
slowBlockThreshold: cfg.SlowBlockThreshold,
|
||||
|
|
@ -801,9 +806,23 @@ func (bc *BlockChain) SetFinalized(header *types.Header) {
|
|||
if header != nil {
|
||||
rawdb.WriteFinalizedBlockHash(bc.db, header.Hash())
|
||||
headFinalizedBlockGauge.Update(int64(header.Number.Uint64()))
|
||||
|
||||
// remove ticket balances whose block number is smaller than
|
||||
// finalized block
|
||||
for n, hashes := range bc.ticketBlocks {
|
||||
if n.Cmp(header.Number) < 0 {
|
||||
for _, hash := range hashes {
|
||||
delete(bc.tickets, hash)
|
||||
}
|
||||
delete(bc.ticketBlocks, n)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rawdb.WriteFinalizedBlockHash(bc.db, common.Hash{})
|
||||
headFinalizedBlockGauge.Update(0)
|
||||
|
||||
bc.ticketBlocks = make(map[*big.Int][]common.Hash)
|
||||
bc.tickets = make(map[common.Hash]map[common.Address]uint16)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1111,6 +1130,8 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha
|
|||
bc.receiptsCache.Purge()
|
||||
bc.blockCache.Purge()
|
||||
bc.txLookupCache.Purge()
|
||||
bc.tickets = make(map[common.Hash]map[common.Address]uint16)
|
||||
bc.ticketBlocks = make(map[*big.Int][]common.Hash)
|
||||
|
||||
// Clear safe block, finalized block if needed
|
||||
headBlock := bc.CurrentBlock()
|
||||
|
|
@ -2257,6 +2278,26 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add ticket balances of this block and remove the parent block data
|
||||
bc.tickets[block.Hash()] = res.Tickets
|
||||
if _, ok := bc.ticketBlocks[block.Number()]; !ok {
|
||||
bc.ticketBlocks[block.Number()] = make([]common.Hash, 0)
|
||||
}
|
||||
bc.ticketBlocks[block.Number()] = append(bc.ticketBlocks[block.Number()], block.Hash())
|
||||
|
||||
if _, ok := bc.tickets[block.ParentHash()]; ok {
|
||||
delete(bc.tickets, block.ParentHash())
|
||||
if hashes := bc.ticketBlocks[block.Number().Sub(block.Number(), big.NewInt(1))]; len(hashes) != 0 {
|
||||
for i, hash := range hashes {
|
||||
if hash.Cmp(block.ParentHash()) == 0 {
|
||||
hashes[i] = hashes[len(hashes)-1]
|
||||
hashes = hashes[:len(hashes)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report the collected witness statistics
|
||||
if witnessStats != nil {
|
||||
witnessStats.ReportMetrics(block.NumberU64())
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||
"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/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
|
|
@ -353,6 +354,35 @@ func (bc *BlockChain) GetCanonicalTransaction(hash common.Hash) (*rawdb.LegacyTx
|
|||
return lookup, tx
|
||||
}
|
||||
|
||||
func (bc *BlockChain) GetTicketBalance(hash common.Hash, statedb *state.StateDB) map[common.Address]uint16 {
|
||||
if bc.tickets[hash] != nil {
|
||||
return bc.tickets[hash]
|
||||
}
|
||||
|
||||
result := make(map[common.Address]uint16)
|
||||
|
||||
senders := statedb.GetState(params.BlobTicketAllocationAddress, common.BigToHash(params.BlobTicketSenderSlot)).Big().Int64()
|
||||
base := crypto.Keccak256Hash(common.LeftPadBytes(params.BlobTicketSenderSlot.Bytes(), 32)).Big()
|
||||
|
||||
for i := range senders {
|
||||
key := common.BigToHash(base.Add(base, big.NewInt(i)))
|
||||
sender := common.BytesToAddress(statedb.GetState(params.BlobTicketAllocationAddress, key).Bytes())
|
||||
|
||||
key = crypto.Keccak256Hash(
|
||||
append(
|
||||
common.LeftPadBytes(sender.Bytes(), 32),
|
||||
common.LeftPadBytes(params.BlobTicketBalanceSlot.Bytes(), 32)...,
|
||||
),
|
||||
)
|
||||
|
||||
balance := statedb.GetState(params.BlobTicketAllocationAddress, key).Big().Uint64()
|
||||
|
||||
result[sender] = uint16(balance)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// TxIndexDone returns true if the transaction indexer has finished indexing.
|
||||
func (bc *BlockChain) TxIndexDone() bool {
|
||||
progress, err := bc.TxIndexProgress()
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||
|
|
@ -42,6 +43,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/core/vm/program"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb/pebble"
|
||||
|
|
@ -4188,6 +4190,128 @@ func TestEIP7702(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBlobTickets(t *testing.T) {
|
||||
var (
|
||||
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||
config = *params.MergedTestChainConfig
|
||||
signer = types.LatestSigner(&config)
|
||||
engine = beacon.New(ethash.NewFaker())
|
||||
|
||||
ticketAmount = uint16(3)
|
||||
)
|
||||
gspec := &Genesis{
|
||||
Config: &config,
|
||||
Alloc: types.GenesisAlloc{
|
||||
addr1: {Balance: big.NewInt(9999900000000000)},
|
||||
params.BlobTicketAllocationAddress: {Code: params.BlobTicketAllocationCode},
|
||||
},
|
||||
}
|
||||
|
||||
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 4, func(i int, bg *BlockGen) {
|
||||
switch i {
|
||||
case 0: // create ticket request
|
||||
selector := crypto.Keccak256([]byte("requestTickets(address,uint16)"))[:4]
|
||||
uint16Type, _ := abi.NewType("uint16", "", nil)
|
||||
addressType, _ := abi.NewType("address", "", nil)
|
||||
args := abi.Arguments{{Type: addressType}, {Type: uint16Type}}
|
||||
data, _ := args.Pack(addr1, ticketAmount)
|
||||
|
||||
data = append(selector, data...)
|
||||
|
||||
txdata := &types.DynamicFeeTx{
|
||||
ChainID: gspec.Config.ChainID,
|
||||
Nonce: 0,
|
||||
To: ¶ms.BlobTicketAllocationAddress,
|
||||
Gas: 500000,
|
||||
GasFeeCap: big.NewInt(875000000),
|
||||
GasTipCap: big.NewInt(1),
|
||||
Data: data,
|
||||
}
|
||||
tx := types.MustSignNewTx(key1, signer, txdata)
|
||||
|
||||
bg.AddTx(tx)
|
||||
case 1: // create ticket usage
|
||||
var (
|
||||
blobs = make([]kzg4844.Blob, 1)
|
||||
commitments []kzg4844.Commitment
|
||||
proofs []kzg4844.Proof
|
||||
)
|
||||
blobs[0][0] = 0x1
|
||||
c, _ := kzg4844.BlobToCommitment(&blobs[0])
|
||||
p, _ := kzg4844.ComputeBlobProof(&blobs[0], c)
|
||||
commitments = append(commitments, c)
|
||||
proofs = append(proofs, p)
|
||||
sidecar := types.NewBlobTxSidecar(types.BlobSidecarVersion0, blobs, commitments, proofs)
|
||||
|
||||
txdata := &types.BlobTx{
|
||||
ChainID: uint256.MustFromBig(config.ChainID),
|
||||
Nonce: 1,
|
||||
Gas: 500000,
|
||||
GasFeeCap: uint256.NewInt(875000000),
|
||||
GasTipCap: uint256.NewInt(1),
|
||||
BlobFeeCap: uint256.NewInt(1),
|
||||
BlobHashes: sidecar.BlobHashes(),
|
||||
}
|
||||
tx := types.MustSignNewTx(key1, signer, txdata)
|
||||
bg.AddTx(tx)
|
||||
}
|
||||
})
|
||||
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), gspec, engine, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create tester chain: %v", err)
|
||||
}
|
||||
defer chain.Stop()
|
||||
|
||||
// check the balance of requester
|
||||
if n, err := chain.InsertChain(blocks[:1]); err != nil {
|
||||
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
|
||||
}
|
||||
|
||||
balance, _ := chain.tickets[blocks[0].Hash()]
|
||||
amount := balance[addr1]
|
||||
if amount != ticketAmount {
|
||||
t.Fatalf("wrong ticket amount: expected %d, got %d", ticketAmount, amount)
|
||||
}
|
||||
|
||||
// test ticket usage
|
||||
if n, err := chain.InsertChain(blocks[1:2]); err != nil {
|
||||
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
|
||||
}
|
||||
|
||||
balance, _ = chain.tickets[blocks[1].Hash()]
|
||||
amount = balance[addr1]
|
||||
if amount != ticketAmount-1 {
|
||||
t.Fatalf("wrong ticket amount: expected %d, got %d", ticketAmount-1, amount)
|
||||
}
|
||||
|
||||
// test expiry
|
||||
if n, err := chain.InsertChain(blocks[2:]); err != nil {
|
||||
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
|
||||
}
|
||||
|
||||
balance, _ = chain.tickets[blocks[3].Hash()]
|
||||
amount = balance[addr1]
|
||||
if amount != 0 {
|
||||
t.Fatalf("wrong ticket amount: expected %d, got %d", 0, amount)
|
||||
}
|
||||
|
||||
// previous block ticket balance
|
||||
state, _ := chain.StateAt(blocks[0].Root())
|
||||
balance = chain.GetTicketBalance(blocks[0].Hash(), state)
|
||||
amount = balance[addr1]
|
||||
if amount != ticketAmount {
|
||||
t.Fatalf("wrong ticket amount: expected %d, got %d", ticketAmount, amount)
|
||||
}
|
||||
|
||||
state, _ = chain.StateAt(blocks[1].Root())
|
||||
balance = chain.GetTicketBalance(blocks[1].Hash(), state)
|
||||
amount = balance[addr1]
|
||||
if amount != ticketAmount-1 {
|
||||
t.Fatalf("wrong ticket amount: expected %d, got %d", ticketAmount-1, amount)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests the scenario that the synchronization target in snap sync has been changed
|
||||
// with a chain reorg at the tip. In this case the reorg'd segment should be unmarked
|
||||
// with canonical flags.
|
||||
|
|
|
|||
|
|
@ -408,6 +408,22 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
|||
b.header.RequestsHash = &reqHash
|
||||
}
|
||||
|
||||
if config.IsPrague(b.header.Number, b.header.Time) {
|
||||
usedAddress := make([]common.Address, 0)
|
||||
usedAmount := make([]uint16, 0)
|
||||
signer := b.Signer()
|
||||
for i, tx := range b.txs {
|
||||
if tx.Type() == types.BlobTxType && b.receipts[i].Status == types.ReceiptStatusSuccessful {
|
||||
from, _ := types.Sender(signer, tx)
|
||||
usedAddress = append(usedAddress, from)
|
||||
usedAmount = append(usedAmount, uint16(len(tx.BlobHashes())))
|
||||
}
|
||||
}
|
||||
blockContext := NewEVMBlockContext(b.header, cm, &b.header.Coinbase)
|
||||
evm := vm.NewEVM(blockContext, statedb, b.cm.config, vm.Config{})
|
||||
ProcessTickets(usedAddress, usedAmount, evm)
|
||||
}
|
||||
|
||||
body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals}
|
||||
block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, &body, b.receipts)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
|
|
@ -94,6 +95,8 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
ProcessParentBlockHash(block.ParentHash(), evm)
|
||||
}
|
||||
|
||||
var usedAddress []common.Address
|
||||
var usedAmount []uint16
|
||||
// Iterate over and process the individual transactions
|
||||
for i, tx := range block.Transactions() {
|
||||
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
|
||||
|
|
@ -108,9 +111,15 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
}
|
||||
receipts = append(receipts, receipt)
|
||||
allLogs = append(allLogs, receipt.Logs...)
|
||||
|
||||
if tx.Type() == types.BlobTxType && receipt.Status == types.ReceiptStatusSuccessful {
|
||||
usedAddress = append(usedAddress, msg.From)
|
||||
usedAmount = append(usedAmount, uint16(len(msg.BlobHashes)))
|
||||
}
|
||||
}
|
||||
// Read requests if Prague is enabled.
|
||||
var requests [][]byte
|
||||
var tickets map[common.Address]uint16
|
||||
if config.IsPrague(block.Number(), block.Time()) {
|
||||
requests = [][]byte{}
|
||||
// EIP-6110
|
||||
|
|
@ -125,6 +134,9 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
if err := ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
return nil, fmt.Errorf("failed to process consolidation queue: %w", err)
|
||||
}
|
||||
|
||||
// This should be moved to after having fork
|
||||
tickets = ProcessTickets(usedAddress, usedAmount, evm)
|
||||
}
|
||||
|
||||
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
||||
|
|
@ -135,6 +147,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
Requests: requests,
|
||||
Logs: allLogs,
|
||||
GasUsed: *usedGas,
|
||||
Tickets: tickets,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -337,6 +350,72 @@ func ParseDepositLogs(requests *[][]byte, logs []*types.Log, config *params.Chai
|
|||
return nil
|
||||
}
|
||||
|
||||
// ProcessTickets updates ticket balances and return the balances
|
||||
func ProcessTickets(usedAddress []common.Address, usedAmount []uint16, evm *vm.EVM) map[common.Address]uint16 {
|
||||
if tracer := evm.Config.Tracer; tracer != nil {
|
||||
onSystemCallStart(tracer, evm.GetVMContext())
|
||||
if tracer.OnSystemCallEnd != nil {
|
||||
defer tracer.OnSystemCallEnd()
|
||||
}
|
||||
}
|
||||
|
||||
addressType, _ := abi.NewType("address[]", "", nil)
|
||||
uint16Type, _ := abi.NewType("uint16[]", "", nil)
|
||||
|
||||
var data []byte
|
||||
if len(usedAddress) > 0 {
|
||||
// abi.encode(address[], uint16[])
|
||||
arguments := abi.Arguments{
|
||||
{Type: addressType},
|
||||
{Type: uint16Type},
|
||||
}
|
||||
|
||||
data, _ = arguments.Pack(usedAddress, usedAmount)
|
||||
}
|
||||
|
||||
msg := &Message{
|
||||
From: params.SystemAddress,
|
||||
GasLimit: 30_000_000,
|
||||
GasPrice: common.Big0,
|
||||
GasFeeCap: common.Big0,
|
||||
GasTipCap: common.Big0,
|
||||
To: ¶ms.BlobTicketAllocationAddress,
|
||||
Data: data,
|
||||
}
|
||||
evm.SetTxContext(NewEVMTxContext(msg))
|
||||
|
||||
res, _, _ := evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560)
|
||||
|
||||
evm.StateDB.Finalise(true)
|
||||
|
||||
arguments := abi.Arguments{
|
||||
{Type: addressType},
|
||||
{Type: uint16Type},
|
||||
}
|
||||
|
||||
values, err := arguments.Unpack(res)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(values) < 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
senders, ok1 := values[0].([]common.Address)
|
||||
amounts, ok2 := values[1].([]uint16)
|
||||
if !ok1 || !ok2 || len(senders) != len(amounts) {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make(map[common.Address]uint16)
|
||||
for i, sender := range senders {
|
||||
result[sender] = amounts[i]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func onSystemCallStart(tracer *tracing.Hooks, ctx *tracing.VMContext) {
|
||||
if tracer.OnSystemCallStartV2 != nil {
|
||||
tracer.OnSystemCallStartV2(ctx)
|
||||
|
|
|
|||
|
|
@ -75,14 +75,6 @@ const (
|
|||
// Note: if you increase this, validation will fail on txMaxSize.
|
||||
maxBlobsPerTx = params.BlobTxMaxBlobs
|
||||
|
||||
// maxTxsPerAccount is the maximum number of blob transactions admitted from
|
||||
// a single account. The limit is enforced to minimize the DoS potential of
|
||||
// a private tx cancelling publicly propagated blobs.
|
||||
//
|
||||
// Note, transactions resurrected by a reorg are also subject to this limit,
|
||||
// so pushing it down too aggressively might make resurrections non-functional.
|
||||
maxTxsPerAccount = 16
|
||||
|
||||
// pendingTransactionStore is the subfolder containing the currently queued
|
||||
// blob transactions.
|
||||
pendingTransactionStore = "queue"
|
||||
|
|
@ -358,6 +350,8 @@ type BlobPool struct {
|
|||
discoverFeed event.Feed // Event feed to send out new tx events on pool discovery (reorg excluded)
|
||||
insertFeed event.Feed // Event feed to send out new tx events on pool inclusion (reorg included)
|
||||
|
||||
tickets map[common.Address]uint16
|
||||
|
||||
lock sync.RWMutex // Mutex protecting the pool during reorg handling
|
||||
}
|
||||
|
||||
|
|
@ -378,6 +372,7 @@ func New(config Config, chain BlockChain, hasPendingAuth func(common.Address) bo
|
|||
spent: make(map[common.Address]*uint256.Int),
|
||||
gapped: make(map[common.Address][]*types.Transaction),
|
||||
gappedSource: make(map[common.Hash]common.Address),
|
||||
tickets: make(map[common.Address]uint16),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -424,6 +419,8 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser
|
|||
p.head.Store(head)
|
||||
p.state = state
|
||||
|
||||
p.tickets = p.chain.GetTicketBalance(head.Hash(), state)
|
||||
|
||||
// Create new slotter for pre-Osaka blob configuration.
|
||||
slotter := newSlotter(params.BlobTxMaxBlobs)
|
||||
|
||||
|
|
@ -563,7 +560,7 @@ func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) error {
|
|||
}
|
||||
|
||||
// recheck verifies the pool's content for a specific account and drops anything
|
||||
// that does not fit anymore (dangling or filled nonce, overdraft).
|
||||
// that does not fit anymore (dangling or filled nonce, overdraft, ticketless).
|
||||
func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint64) {
|
||||
// Sort the transactions belonging to the account so reinjects can be simpler
|
||||
txs := p.index[addr]
|
||||
|
|
@ -769,16 +766,19 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
|
|||
}
|
||||
}
|
||||
}
|
||||
// Sanity check that no account can have more queued transactions than the
|
||||
// DoS protection threshold.
|
||||
if len(txs) > maxTxsPerAccount {
|
||||
// Evict the highest nonce transactions until the pending set falls under
|
||||
// the account's transaction cap
|
||||
|
||||
// If the number of txs is bigger than the sender's ticket balance,
|
||||
// drop overflown amount from the sender
|
||||
var blobs int
|
||||
for _, tx := range txs {
|
||||
blobs += len(tx.vhashes)
|
||||
}
|
||||
if ticket, ok := p.tickets[addr]; ok && blobs > int(ticket) {
|
||||
var (
|
||||
ids []uint64
|
||||
nonces []uint64
|
||||
)
|
||||
for len(txs) > maxTxsPerAccount {
|
||||
for blobs > int(ticket) {
|
||||
last := txs[len(txs)-1]
|
||||
txs[len(txs)-1] = nil
|
||||
txs = txs[:len(txs)-1]
|
||||
|
|
@ -789,10 +789,12 @@ func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint6
|
|||
p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], last.costCap)
|
||||
p.stored -= uint64(last.storageSize)
|
||||
p.lookup.untrack(last)
|
||||
|
||||
blobs -= len(last.vhashes)
|
||||
}
|
||||
p.index[addr] = txs
|
||||
|
||||
log.Warn("Dropping overcapped blob transactions", "from", addr, "kept", len(txs), "drop", nonces, "ids", ids)
|
||||
log.Trace("Dropping ticketless blob transactions", "from", addr, "dropped", len(ids), "left", len(txs))
|
||||
dropOvercappedMeter.Mark(int64(len(ids)))
|
||||
|
||||
for _, id := range ids {
|
||||
|
|
@ -860,6 +862,8 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) {
|
|||
p.head.Store(newHead)
|
||||
p.state = statedb
|
||||
|
||||
p.tickets = p.chain.GetTicketBalance(newHead.Hash(), p.state)
|
||||
|
||||
// Run the reorg between the old and new head and figure out which accounts
|
||||
// need to be rechecked and which transactions need to be readded
|
||||
if reinject, inclusions := p.reorg(oldHead, newHead); reinject != nil {
|
||||
|
|
@ -913,6 +917,10 @@ func (p *BlobPool) reorg(oldHead, newHead *types.Header) (map[common.Address][]*
|
|||
oldNum := oldHead.Number.Uint64()
|
||||
newNum := newHead.Number.Uint64()
|
||||
|
||||
// todo: This can happen by chain rewind (e.g. setHead)
|
||||
// todo: Why don't we clear the txpool in here, given that
|
||||
// todo: most txs could be gapped ?
|
||||
// todo: Where those txs is being rechecked ?
|
||||
if depth := uint64(math.Abs(float64(oldNum) - float64(newNum))); depth > 64 {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -1222,11 +1230,15 @@ func (p *BlobPool) validateTx(tx *types.Transaction) error {
|
|||
return p.state.GetNonce(addr) + uint64(len(p.index[addr]))
|
||||
},
|
||||
UsedAndLeftSlots: func(addr common.Address) (int, int) {
|
||||
have := len(p.index[addr])
|
||||
if have >= maxTxsPerAccount {
|
||||
have := 0
|
||||
for _, meta := range p.index[addr] {
|
||||
have += len(meta.vhashes)
|
||||
}
|
||||
|
||||
if have >= int(p.tickets[addr]) {
|
||||
return have, 0
|
||||
}
|
||||
return have, maxTxsPerAccount - have
|
||||
return have, int(p.tickets[addr]) - have
|
||||
},
|
||||
ExistingExpenditure: func(addr common.Address) *big.Int {
|
||||
if spent := p.spent[addr]; spent != nil {
|
||||
|
|
@ -1970,7 +1982,9 @@ func (p *BlobPool) gappedAllowance(addr common.Address) int {
|
|||
nonce := p.state.GetNonce(addr)
|
||||
allowance := int(math.Log10(float64(nonce + 1)))
|
||||
// Cap the allowance to the remaining pool space
|
||||
return min(allowance, maxTxsPerAccount-len(p.index[addr])) - len(p.gapped[addr])
|
||||
// We consider the ticket balance here, where one ticket is treated as a tx with a single blob.
|
||||
// The reason is that we cannot know how many tickets filling txs will use.
|
||||
return min(allowance, int(p.tickets[addr])-len(p.index[addr])) - len(p.gapped[addr])
|
||||
}
|
||||
|
||||
// evictGapped removes the old transactions from the gapped reorder buffer.
|
||||
|
|
|
|||
|
|
@ -90,6 +90,8 @@ type testBlockChain struct {
|
|||
blocks map[uint64]*types.Block
|
||||
|
||||
blockTime *uint64
|
||||
|
||||
tickets map[common.Address]uint16
|
||||
}
|
||||
|
||||
func (bc *testBlockChain) Config() *params.ChainConfig {
|
||||
|
|
@ -184,6 +186,10 @@ func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) {
|
|||
return bc.statedb, nil
|
||||
}
|
||||
|
||||
func (bc *testBlockChain) GetTicketBalance(common.Hash, *state.StateDB) map[common.Address]uint16 {
|
||||
return bc.tickets
|
||||
}
|
||||
|
||||
// reserver is a utility struct to sanity check that accounts are
|
||||
// properly reserved by the blobpool (no duplicate reserves or unreserves).
|
||||
type reserver struct {
|
||||
|
|
@ -478,7 +484,7 @@ func verifyBlobRetrievals(t *testing.T, pool *BlobPool) {
|
|||
// - 4. All transactions after an already included nonce must be dropped
|
||||
// - 5. All transactions after an underpriced one (including it) must be dropped
|
||||
// - 6. All transactions after an overdrafting sequence must be dropped
|
||||
// - 7. All transactions exceeding the per-account limit must be dropped
|
||||
// - 7. All transactions exceeding the ticket limit must be dropped
|
||||
//
|
||||
// Furthermore, some strange corner-cases can also occur after a crash, as Billy's
|
||||
// simplicity also allows it to resurrect past deleted entities:
|
||||
|
|
@ -515,6 +521,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
badsig, _ := store.Put(blob)
|
||||
|
||||
tickets := make(map[common.Address]uint16)
|
||||
// Insert a sequence of transactions with a nonce gap in between to verify
|
||||
// that anything gapped will get evicted (case 3).
|
||||
var (
|
||||
|
|
@ -533,6 +540,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
} else {
|
||||
gapped[id] = struct{}{}
|
||||
}
|
||||
tickets[crypto.PubkeyToAddress(gapper.PublicKey)] += 1
|
||||
}
|
||||
// Insert a sequence of transactions with a gapped starting nonce to verify
|
||||
// that the entire set will get dropped (case 3).
|
||||
|
|
@ -546,6 +554,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
|
||||
id, _ := store.Put(blob)
|
||||
dangling[id] = struct{}{}
|
||||
tickets[crypto.PubkeyToAddress(dangler.PublicKey)] += 1
|
||||
}
|
||||
// Insert a sequence of transactions with already passed nonces to veirfy
|
||||
// that the entire set will get dropped (case 4).
|
||||
|
|
@ -559,6 +568,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
|
||||
id, _ := store.Put(blob)
|
||||
filled[id] = struct{}{}
|
||||
tickets[crypto.PubkeyToAddress(filler.PublicKey)] += 1
|
||||
}
|
||||
// Insert a sequence of transactions with partially passed nonces to verify
|
||||
// that the included part of the set will get dropped (case 4).
|
||||
|
|
@ -576,6 +586,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
} else {
|
||||
overlapped[id] = struct{}{}
|
||||
}
|
||||
tickets[crypto.PubkeyToAddress(overlapper.PublicKey)] += 1
|
||||
}
|
||||
// Insert a sequence of transactions with an underpriced first to verify that
|
||||
// the entire set will get dropped (case 5).
|
||||
|
|
@ -594,6 +605,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
|
||||
id, _ := store.Put(blob)
|
||||
underpaid[id] = struct{}{}
|
||||
tickets[crypto.PubkeyToAddress(underpayer.PublicKey)] += 1
|
||||
}
|
||||
|
||||
// Insert a sequence of transactions with an underpriced in between to verify
|
||||
|
|
@ -617,6 +629,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
} else {
|
||||
outpriced[id] = struct{}{}
|
||||
}
|
||||
tickets[crypto.PubkeyToAddress(outpricer.PublicKey)] += 1
|
||||
}
|
||||
// Insert a sequence of transactions fully overdrafted to verify that the
|
||||
// entire set will get invalidated (case 6).
|
||||
|
|
@ -635,6 +648,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
|
||||
id, _ := store.Put(blob)
|
||||
exceeded[id] = struct{}{}
|
||||
tickets[crypto.PubkeyToAddress(exceeder.PublicKey)] += 1
|
||||
}
|
||||
// Insert a sequence of transactions partially overdrafted to verify that part
|
||||
// of the set will get invalidated (case 6).
|
||||
|
|
@ -657,18 +671,24 @@ func TestOpenDrops(t *testing.T) {
|
|||
} else {
|
||||
overdrafted[id] = struct{}{}
|
||||
}
|
||||
tickets[crypto.PubkeyToAddress(overdrafter.PublicKey)] += 1
|
||||
}
|
||||
// Insert a sequence of transactions overflowing the account cap to verify
|
||||
// that part of the set will get invalidated (case 7).
|
||||
var (
|
||||
overcapper, _ = crypto.GenerateKey()
|
||||
overcapped = make(map[uint64]struct{})
|
||||
|
||||
valid = uint16(10)
|
||||
)
|
||||
for nonce := uint64(0); nonce < maxTxsPerAccount+3; nonce++ {
|
||||
// Initialise tickets to be used for validation
|
||||
tickets[crypto.PubkeyToAddress(overcapper.PublicKey)] = valid
|
||||
|
||||
for nonce := uint64(0); nonce < uint64(valid)+3; nonce++ {
|
||||
blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, 1, 1, overcapper))
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
if nonce < maxTxsPerAccount {
|
||||
if nonce < uint64(valid) {
|
||||
valids[id] = struct{}{}
|
||||
} else {
|
||||
overcapped[id] = struct{}{}
|
||||
|
|
@ -691,6 +711,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
duplicated[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
tickets[crypto.PubkeyToAddress(duplicater.PublicKey)] += 1
|
||||
}
|
||||
// Insert a batch of duplicated nonces to verify that only one of each will
|
||||
// remain (case 9).
|
||||
|
|
@ -708,6 +729,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
} else {
|
||||
repeated[id] = struct{}{}
|
||||
}
|
||||
tickets[crypto.PubkeyToAddress(repeater.PublicKey)] += 1
|
||||
}
|
||||
}
|
||||
store.Close()
|
||||
|
|
@ -734,6 +756,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
basefee: uint256.NewInt(params.InitialBaseFee),
|
||||
blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice),
|
||||
statedb: statedb,
|
||||
tickets: tickets,
|
||||
}
|
||||
pool := New(Config{Datadir: storage}, chain, nil)
|
||||
if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil {
|
||||
|
|
@ -1310,6 +1333,7 @@ func TestBlobCountLimit(t *testing.T) {
|
|||
basefee: uint256.NewInt(1050),
|
||||
blobfee: uint256.NewInt(105),
|
||||
statedb: statedb,
|
||||
tickets: map[common.Address]uint16{addr1: 6, addr2: 7},
|
||||
}
|
||||
pool := New(Config{Datadir: t.TempDir()}, chain, nil)
|
||||
if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil {
|
||||
|
|
@ -1325,10 +1349,10 @@ func TestBlobCountLimit(t *testing.T) {
|
|||
|
||||
// Check that first succeeds second fails.
|
||||
if errs[0] != nil {
|
||||
t.Fatalf("expected tx with 7 blobs to succeed, got %v", errs[0])
|
||||
t.Fatalf("expected tx with 6 blobs to succeed, got %v", errs[0])
|
||||
}
|
||||
if !errors.Is(errs[1], txpool.ErrTxBlobLimitExceeded) {
|
||||
t.Fatalf("expected tx with 8 blobs to fail, got: %v", errs[1])
|
||||
t.Fatalf("expected tx with 7 blobs to fail, got: %v", errs[1])
|
||||
}
|
||||
|
||||
verifyPoolInternals(t, pool)
|
||||
|
|
@ -1587,11 +1611,6 @@ func TestAdd(t *testing.T) {
|
|||
tx: makeUnsignedTx(15, 10, 10, 10),
|
||||
err: nil,
|
||||
},
|
||||
{ // New account, 16 pooled tx, 0 slots left: reject nonce 16 with overcap
|
||||
from: "alice",
|
||||
tx: makeUnsignedTx(16, 1, 1, 1),
|
||||
err: txpool.ErrAccountLimitExceeded,
|
||||
},
|
||||
},
|
||||
},
|
||||
// Previously existing transactions should be allowed to be replaced iff
|
||||
|
|
@ -1754,6 +1773,10 @@ func TestAdd(t *testing.T) {
|
|||
basefee: uint256.NewInt(1050),
|
||||
blobfee: uint256.NewInt(105),
|
||||
statedb: statedb,
|
||||
tickets: make(map[common.Address]uint16),
|
||||
}
|
||||
for _, add := range tt.adds {
|
||||
chain.tickets[addrs[add.from]] += uint16(len(add.tx.BlobHashes))
|
||||
}
|
||||
pool := New(Config{Datadir: storage}, chain, nil)
|
||||
if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil {
|
||||
|
|
@ -1851,6 +1874,8 @@ func TestGetBlobs(t *testing.T) {
|
|||
blob1, _ = rlp.EncodeToBytes(tx1)
|
||||
blob2, _ = rlp.EncodeToBytes(tx2)
|
||||
blob3, _ = rlp.EncodeToBytes(tx3)
|
||||
|
||||
tickets = map[common.Address]uint16{addr1: 6, addr2: 6, addr3: 6}
|
||||
)
|
||||
|
||||
// Write the two safely sized txs to store. note: although the store is
|
||||
|
|
@ -1889,6 +1914,7 @@ func TestGetBlobs(t *testing.T) {
|
|||
basefee: uint256.NewInt(1050),
|
||||
blobfee: uint256.NewInt(105),
|
||||
statedb: statedb,
|
||||
tickets: tickets,
|
||||
}
|
||||
pool := New(Config{Datadir: storage}, chain, nil)
|
||||
if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil {
|
||||
|
|
@ -2089,6 +2115,7 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) {
|
|||
basefee: uint256.NewInt(basefee),
|
||||
blobfee: uint256.NewInt(blobfee),
|
||||
statedb: statedb,
|
||||
tickets: make(map[common.Address]uint16),
|
||||
}
|
||||
pool = New(Config{Datadir: ""}, chain, nil)
|
||||
)
|
||||
|
|
@ -2112,6 +2139,7 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) {
|
|||
b.Fatal(err)
|
||||
}
|
||||
statedb.AddBalance(addr, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
|
||||
chain.tickets[addr] += 1
|
||||
pool.add(tx)
|
||||
}
|
||||
statedb.Commit(0, true, false)
|
||||
|
|
|
|||
|
|
@ -41,4 +41,6 @@ type BlockChain interface {
|
|||
|
||||
// StateAt returns a state database for a given root hash (generally the head).
|
||||
StateAt(root common.Hash) (*state.StateDB, error)
|
||||
|
||||
GetTicketBalance(hash common.Hash, statedb *state.StateDB) map[common.Address]uint16
|
||||
}
|
||||
|
|
|
|||
|
|
@ -292,8 +292,14 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op
|
|||
// overflow the number of permitted transactions from a single account
|
||||
// (i.e. max cancellable via out-of-bound transaction).
|
||||
if opts.UsedAndLeftSlots != nil {
|
||||
if used, left := opts.UsedAndLeftSlots(from); left <= 0 {
|
||||
return fmt.Errorf("%w: pooled %d txs", ErrAccountLimitExceeded, used)
|
||||
if tx.Type() == types.BlobTxType {
|
||||
if used, left := opts.UsedAndLeftSlots(from); left < len(tx.BlobHashes()) {
|
||||
return fmt.Errorf("%w: pooled %d blobs", ErrAccountLimitExceeded, used)
|
||||
}
|
||||
} else {
|
||||
if used, left := opts.UsedAndLeftSlots(from); left <= 0 {
|
||||
return fmt.Errorf("%w: pooled %d txs", ErrAccountLimitExceeded, used)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package core
|
|||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
|
|
@ -57,4 +58,5 @@ type ProcessResult struct {
|
|||
Requests [][]byte
|
||||
Logs []*types.Log
|
||||
GasUsed uint64
|
||||
Tickets map[common.Address]uint16
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/beacon/engine"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
|
|
@ -1897,16 +1898,57 @@ func newGetBlobEnv(t testing.TB, version byte) (*node.Node, *ConsensusAPI) {
|
|||
addr1: {Balance: testBalance},
|
||||
addr2: {Balance: testBalance},
|
||||
addr3: {Balance: testBalance},
|
||||
|
||||
params.BlobTicketAllocationAddress: {Code: params.BlobTicketAllocationCode},
|
||||
},
|
||||
Difficulty: common.Big0,
|
||||
}
|
||||
n, ethServ := startEthService(t, gspec, nil)
|
||||
|
||||
// Generate ticket request blocks
|
||||
engine := beacon.New(ethash.NewFaker())
|
||||
_, ticketBlocks, _ := core.GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *core.BlockGen) {
|
||||
signers := []struct {
|
||||
addr common.Address
|
||||
key *ecdsa.PrivateKey
|
||||
}{
|
||||
{addr1, key1},
|
||||
{addr2, key2},
|
||||
{addr3, key3},
|
||||
}
|
||||
for _, signer := range signers {
|
||||
selector := crypto.Keccak256([]byte("requestTickets(address,uint16)"))[:4]
|
||||
uint16Type, _ := abi.NewType("uint16", "", nil)
|
||||
addressType, _ := abi.NewType("address", "", nil)
|
||||
args := abi.Arguments{{Type: addressType}, {Type: uint16Type}}
|
||||
data, _ := args.Pack(signer.addr, uint16(2))
|
||||
data = append(selector, data...)
|
||||
|
||||
txdata := &types.DynamicFeeTx{
|
||||
ChainID: config.ChainID,
|
||||
Nonce: b.TxNonce(signer.addr),
|
||||
To: ¶ms.BlobTicketAllocationAddress,
|
||||
Gas: 500000,
|
||||
GasFeeCap: big.NewInt(params.InitialBaseFee * 2),
|
||||
GasTipCap: big.NewInt(params.GWei),
|
||||
Data: data,
|
||||
}
|
||||
tx := types.MustSignNewTx(signer.key, types.LatestSigner(&config), txdata)
|
||||
b.AddTx(tx)
|
||||
}
|
||||
})
|
||||
|
||||
n, ethServ := startEthService(t, gspec, ticketBlocks)
|
||||
|
||||
// fill blob txs into the pool
|
||||
tx1 := makeMultiBlobTx(&config, 0, 2, 0, key1, version) // blob[0, 2)
|
||||
tx2 := makeMultiBlobTx(&config, 0, 2, 2, key2, version) // blob[2, 4)
|
||||
tx3 := makeMultiBlobTx(&config, 0, 2, 4, key3, version) // blob[4, 6)
|
||||
ethServ.TxPool().Add([]*types.Transaction{tx1, tx2, tx3}, true)
|
||||
tx1 := makeMultiBlobTx(&config, 1, 2, 0, key1, version) // blob[0, 2)
|
||||
tx2 := makeMultiBlobTx(&config, 1, 2, 2, key2, version) // blob[2, 4)
|
||||
tx3 := makeMultiBlobTx(&config, 1, 2, 4, key3, version) // blob[4, 6)
|
||||
errs := ethServ.TxPool().Add([]*types.Transaction{tx1, tx2, tx3}, true)
|
||||
for _, err := range errs {
|
||||
if err != nil {
|
||||
t.Fatal("GetBlob environment generation failed", err)
|
||||
}
|
||||
}
|
||||
|
||||
api := newConsensusAPIWithoutHeartbeat(ethServ)
|
||||
return n, api
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import (
|
|||
"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/state"
|
||||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
|
||||
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
|
||||
|
|
@ -69,6 +70,14 @@ func newTestBackend(blocks int) *testBackend {
|
|||
return newTestBackendWithGenerator(blocks, false, false, nil)
|
||||
}
|
||||
|
||||
type testChain struct {
|
||||
*core.BlockChain
|
||||
}
|
||||
|
||||
func (c *testChain) GetTicketBalance(hash common.Hash, statedb *state.StateDB) map[common.Address]uint16 {
|
||||
return map[common.Address]uint16{testAddr: 1000}
|
||||
}
|
||||
|
||||
// newTestBackendWithGenerator creates a chain with a number of explicitly defined blocks and
|
||||
// wraps it into a mock backend.
|
||||
func newTestBackendWithGenerator(blocks int, shanghai bool, cancun bool, generator func(int, *core.BlockGen)) *testBackend {
|
||||
|
|
@ -134,9 +143,9 @@ func newTestBackendWithGenerator(blocks int, shanghai bool, cancun bool, generat
|
|||
storage, _ := os.MkdirTemp("", "blobpool-")
|
||||
defer os.RemoveAll(storage)
|
||||
|
||||
blobPool := blobpool.New(blobpool.Config{Datadir: storage}, chain, nil)
|
||||
legacyPool := legacypool.New(txconfig, chain)
|
||||
txpool, _ := txpool.New(txconfig.PriceLimit, chain, []txpool.SubPool{legacyPool, blobPool})
|
||||
blobPool := blobpool.New(blobpool.Config{Datadir: storage}, &testChain{chain}, nil)
|
||||
legacyPool := legacypool.New(txconfig, &testChain{chain})
|
||||
txpool, _ := txpool.New(txconfig.PriceLimit, &testChain{chain}, []txpool.SubPool{legacyPool, blobPool})
|
||||
|
||||
return &testBackend{
|
||||
db: db,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,10 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// TestTransactionRollbackBehavior tests that calling Rollback on the simulated backend doesn't prevent subsequent
|
||||
|
|
@ -31,14 +34,16 @@ import (
|
|||
func TestTransactionRollbackBehavior(t *testing.T) {
|
||||
sim := NewBackend(
|
||||
types.GenesisAlloc{
|
||||
testAddr: {Balance: big.NewInt(10000000000000000)},
|
||||
testAddr2: {Balance: big.NewInt(10000000000000000)},
|
||||
testAddr: {Balance: big.NewInt(10000000000000000)},
|
||||
testAddr2: {Balance: big.NewInt(10000000000000000)},
|
||||
params.BlobTicketAllocationAddress: {Code: params.BlobTicketAllocationCode},
|
||||
},
|
||||
)
|
||||
defer sim.Close()
|
||||
client := sim.Client()
|
||||
|
||||
btx0 := testSendSignedTx(t, testKey, sim, true, 0)
|
||||
allocateTickets(t, testKey, sim, 0, 10)
|
||||
btx0 := testSendSignedTx(t, testKey, sim, true, 1)
|
||||
tx0 := testSendSignedTx(t, testKey2, sim, false, 0)
|
||||
tx1 := testSendSignedTx(t, testKey2, sim, false, 1)
|
||||
|
||||
|
|
@ -48,7 +53,7 @@ func TestTransactionRollbackBehavior(t *testing.T) {
|
|||
t.Fatalf("all transactions were not rolled back")
|
||||
}
|
||||
|
||||
btx2 := testSendSignedTx(t, testKey, sim, true, 0)
|
||||
btx2 := testSendSignedTx(t, testKey, sim, true, 1)
|
||||
tx2 := testSendSignedTx(t, testKey2, sim, false, 0)
|
||||
tx3 := testSendSignedTx(t, testKey2, sim, false, 1)
|
||||
|
||||
|
|
@ -86,6 +91,43 @@ func testSendSignedTx(t *testing.T, key *ecdsa.PrivateKey, sim *Backend, isBlobT
|
|||
return signedTx
|
||||
}
|
||||
|
||||
func allocateTickets(t *testing.T, key *ecdsa.PrivateKey, sim *Backend, nonce uint64, amount uint16) {
|
||||
t.Helper()
|
||||
client := sim.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
chainid, _ := client.ChainID(ctx)
|
||||
signer := types.LatestSignerForChainID(chainid)
|
||||
|
||||
selector := crypto.Keccak256([]byte("requestTickets(address,uint16)"))[:4]
|
||||
uint16Type, _ := abi.NewType("uint16", "", nil)
|
||||
addressType, _ := abi.NewType("address", "", nil)
|
||||
args := abi.Arguments{{Type: addressType}, {Type: uint16Type}}
|
||||
data, _ := args.Pack(addr, amount)
|
||||
data = append(selector, data...)
|
||||
|
||||
// Get current head
|
||||
head, _ := client.HeaderByNumber(ctx, nil)
|
||||
|
||||
txdata := &types.DynamicFeeTx{
|
||||
ChainID: chainid,
|
||||
Nonce: nonce,
|
||||
To: ¶ms.BlobTicketAllocationAddress,
|
||||
Gas: 500000,
|
||||
GasFeeCap: new(big.Int).Add(head.BaseFee, big.NewInt(params.GWei)),
|
||||
GasTipCap: big.NewInt(params.GWei),
|
||||
Data: data,
|
||||
}
|
||||
tx := types.MustSignNewTx(key, signer, txdata)
|
||||
|
||||
if err := client.SendTransaction(ctx, tx); err != nil {
|
||||
t.Fatalf("failed to request tickets: %v", err)
|
||||
}
|
||||
|
||||
sim.Commit()
|
||||
}
|
||||
|
||||
// pendingStateHasTx returns true if a given transaction was successfully included as of the latest pending state.
|
||||
func pendingStateHasTx(client Client, tx *types.Transaction) bool {
|
||||
ctx := context.Background()
|
||||
|
|
|
|||
|
|
@ -70,6 +70,9 @@ type environment struct {
|
|||
sidecars []*types.BlobTxSidecar
|
||||
blobs int
|
||||
|
||||
usedAddress []common.Address
|
||||
usedAmount []uint16
|
||||
|
||||
witness *stateless.Witness
|
||||
}
|
||||
|
||||
|
|
@ -167,6 +170,8 @@ func (miner *Miner) generateWork(genParam *generateParams, witness bool) *newPay
|
|||
return &newPayloadResult{err: err}
|
||||
}
|
||||
}
|
||||
core.ProcessTickets(work.usedAddress, work.usedAmount, work.evm)
|
||||
|
||||
if requests != nil {
|
||||
reqHash := types.CalcRequestsHash(requests)
|
||||
work.header.RequestsHash = &reqHash
|
||||
|
|
@ -335,6 +340,15 @@ func (miner *Miner) commitBlobTransaction(env *environment, tx *types.Transactio
|
|||
env.size += txNoBlob.Size()
|
||||
*env.header.BlobGasUsed += receipt.BlobGasUsed
|
||||
env.tcount++
|
||||
|
||||
if receipt.Status == types.ReceiptStatusSuccessful {
|
||||
sender, err := types.Sender(env.signer, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env.usedAddress = append(env.usedAddress, sender)
|
||||
env.usedAmount = append(env.usedAmount, uint16(len(tx.BlobHashes())))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue