From 3cac28491c5830cb52344a9bc4a778e0f20b486f Mon Sep 17 00:00:00 2001 From: healthykim Date: Tue, 27 Jan 2026 20:15:59 +0900 Subject: [PATCH] blobpool ticket implementation --- core/TicketAllocator.sol | 138 ++++++++++++++++++++++++++ core/blockchain.go | 41 ++++++++ core/blockchain_reader.go | 30 ++++++ core/blockchain_test.go | 124 +++++++++++++++++++++++ core/chain_makers.go | 16 +++ core/state_processor.go | 79 +++++++++++++++ core/txpool/blobpool/blobpool.go | 54 ++++++---- core/txpool/blobpool/blobpool_test.go | 48 +++++++-- core/txpool/blobpool/interface.go | 2 + core/txpool/validation.go | 10 +- core/types.go | 2 + eth/catalyst/api_test.go | 52 +++++++++- eth/protocols/eth/handler_test.go | 15 ++- ethclient/simulated/rollback_test.go | 50 +++++++++- miner/worker.go | 14 +++ params/protocol_params.go | 7 ++ 16 files changed, 638 insertions(+), 44 deletions(-) create mode 100644 core/TicketAllocator.sol diff --git a/core/TicketAllocator.sol b/core/TicketAllocator.sol new file mode 100644 index 0000000000..da202ae891 --- /dev/null +++ b/core/TicketAllocator.sol @@ -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); + } +} diff --git a/core/blockchain.go b/core/blockchain.go index 8741b8b937..a86e987af9 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -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()) diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index ee15c152c4..9534c10533 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -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() diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 73ffce93fb..51ecad3382 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -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. diff --git a/core/chain_makers.go b/core/chain_makers.go index 7ce86b14e9..439e4408de 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -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 { diff --git a/core/state_processor.go b/core/state_processor.go index b4b22e4318..0ca6d14b39 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -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) diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 27441ac2e2..7eb0a141b2 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -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. diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index 4bb3567b69..e1dd5dc14f 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -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) diff --git a/core/txpool/blobpool/interface.go b/core/txpool/blobpool/interface.go index 6f296a54bd..b79f0951f3 100644 --- a/core/txpool/blobpool/interface.go +++ b/core/txpool/blobpool/interface.go @@ -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 } diff --git a/core/txpool/validation.go b/core/txpool/validation.go index e0a333dfa5..613e0bd69e 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -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) + } } } } diff --git a/core/types.go b/core/types.go index bed20802ab..08289b480c 100644 --- a/core/types.go +++ b/core/types.go @@ -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 } diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 4d7246d4ed..5a87ec1f25 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -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 diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index 65c491f815..6dc96ad71e 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -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, diff --git a/ethclient/simulated/rollback_test.go b/ethclient/simulated/rollback_test.go index 093467d291..fba878e282 100644 --- a/ethclient/simulated/rollback_test.go +++ b/ethclient/simulated/rollback_test.go @@ -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() diff --git a/miner/worker.go b/miner/worker.go index 45d7073ed7..e6c5e9efea 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -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 } diff --git a/params/protocol_params.go b/params/protocol_params.go index bb506af015..6654e88caa 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -219,4 +219,11 @@ var ( // EIP-7251 - Increase the MAX_EFFECTIVE_BALANCE ConsolidationQueueAddress = common.HexToAddress("0x0000BBdDc7CE488642fb579F8B00f3a590007251") ConsolidationQueueCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd") + + // Blob tickets (random address for test) + BlobTicketAllocationAddress = common.HexToAddress("0x8fd501b55bf41f51460815f0a1f00b541fc161") + BlobTicketAllocationCode = common.FromHex("608060405234801561000f575f5ffd5b506004361061008a575f3560e01c8063afbd178211610059578063afbd1782146109dd578063bda4fec5146109fb578063e3d670d714610a2b578063f56f48f214610a5b5761008b565b80630309cc7b14610930578063442871131461094c5780635f5152261461097d5780639977c78a146109ad5761008b565b5b5f36606073fffffffffffffffffffffffffffffffffffffffe73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610108576040517fdd169cfb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6060805f85859050111561012e5784848101906101259190611092565b80925081935050505b5f5f90505b600380549050811015610702575f6003828154811061015557610154611108565b5b905f5260205f20015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505f5f5f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2090505f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205490505f5f90505f5f90505b87518161ffff16101561029f578473ffffffffffffffffffffffffffffffffffffffff16888261ffff168151811061024357610242611108565b5b602002602001015173ffffffffffffffffffffffffffffffffffffffff160361028c57868161ffff168151811061027d5761027c611108565b5b6020026020010151915061029f565b808061029790611162565b915050610208565b505b8280549050821015610560575f8383815481106102c1576102c0611108565b5b905f5260205f20906002020190505f815f015f9054906101000a900461ffff1661ffff16036102fe5782806102f590611194565b935050506102a1565b43600261ffff16826001015461031491906111db565b101561042357805f015f9054906101000a900461ffff1660025f8773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282829054906101000a900461ffff16610383919061120e565b92506101000a81548161ffff021916908361ffff1602179055505f8261ffff1611156103f3575f815f015f9054906101000a900461ffff1661ffff168361ffff16116103cf57826103e1565b815f015f9054906101000a900461ffff165b905080836103ef919061120e565b9250505b5f815f015f6101000a81548161ffff021916908361ffff160217905550828061041b90611194565b93505061055a565b5f8261ffff161115610553575f815f015f9054906101000a900461ffff1661ffff168361ffff16116104555782610467565b815f015f9054906101000a900461ffff165b905080825f015f8282829054906101000a900461ffff16610488919061120e565b92506101000a81548161ffff021916908361ffff1602179055508060025f8873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282829054906101000a900461ffff166104fb919061120e565b92506101000a81548161ffff021916908361ffff1602179055508083610521919061120e565b92505f825f015f9054906101000a900461ffff1661ffff160361054d57838061054990611194565b9450505b50610559565b50610560565b5b506102a1565b8160015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055505f60025f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900461ffff1661ffff16036106ea576003600160038054905061060b9190611243565b8154811061061c5761061b611108565b5b905f5260205f20015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff166003868154811061065857610657611108565b5b905f5260205f20015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060038054806106af576106ae611276565b5b600190038181905f5260205f20015f6101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690559055505050506106fd565b84806106f590611194565b955050505050505b610133565b505f60038054905067ffffffffffffffff81111561072357610722610e05565b5b6040519080825280602002602001820160405280156107515781602001602082028036833780820191505090505b5090505f60038054905067ffffffffffffffff81111561077457610773610e05565b5b6040519080825280602002602001820160405280156107a25781602001602082028036833780820191505090505b5090505f5f90505b6003805490508110156108fc57600381815481106107cb576107ca611108565b5b905f5260205f20015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1683828151811061080657610805611108565b5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff168152505060025f6003838154811061085757610856611108565b5b905f5260205f20015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900461ffff168282815181106108d9576108d8611108565b5b602002602001019061ffff16908161ffff168152505080806001019150506107aa565b508181604051602001610910929190611411565b604051602081830303815290604052945050505050915050805190602001f35b61094a60048036038101906109459190611446565b610a79565b005b610966600480360381019061096191906114ae565b610ccb565b60405161097492919061150a565b60405180910390f35b61099760048036038101906109929190611531565b610d11565b6040516109a4919061155c565b60405180910390f35b6109c760048036038101906109c29190611575565b610d68565b6040516109d491906115af565b60405180910390f35b6109e5610da3565b6040516109f291906115c8565b60405180910390f35b610a156004803603810190610a109190611531565b610da8565b604051610a22919061155c565b60405180910390f35b610a456004803603810190610a409190611531565b610dbd565b604051610a5291906115c8565b60405180910390f35b610a63610ddb565b604051610a7091906115c8565b60405180910390f35b5f4390506005548114610a9a57601561ffff16600481905550806005819055505b6004548261ffff161115610ada576040517fa8d8f34900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f60025f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900461ffff1661ffff1603610b9057600383908060018154018082558091505060019003905f5260205f20015f9091909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b5f5f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2060405180604001604052808461ffff16815260200183815250908060018154018082558091505060019003905f5260205f2090600202015f909190919091505f820151815f015f6101000a81548161ffff021916908361ffff1602179055506020820151816001015550508160025f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282829054906101000a900461ffff16610c9091906115e1565b92506101000a81548161ffff021916908361ffff1602179055508161ffff1660045f828254610cbf9190611243565b92505081905550505050565b5f602052815f5260405f208181548110610ce3575f80fd5b905f5260205f2090600202015f9150915050805f015f9054906101000a900461ffff16908060010154905082565b5f60025f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900461ffff1661ffff169050919050565b60038181548110610d77575f80fd5b905f5260205f20015f915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b601581565b6001602052805f5260405f205f915090505481565b6002602052805f5260405f205f915054906101000a900461ffff1681565b600281565b5f604051905090565b5f5ffd5b5f5ffd5b5f5ffd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b610e3b82610df5565b810181811067ffffffffffffffff82111715610e5a57610e59610e05565b5b80604052505050565b5f610e6c610de0565b9050610e788282610e32565b919050565b5f67ffffffffffffffff821115610e9757610e96610e05565b5b602082029050602081019050919050565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610ed582610eac565b9050919050565b610ee581610ecb565b8114610eef575f5ffd5b50565b5f81359050610f0081610edc565b92915050565b5f610f18610f1384610e7d565b610e63565b90508083825260208201905060208402830185811115610f3b57610f3a610ea8565b5b835b81811015610f645780610f508882610ef2565b845260208401935050602081019050610f3d565b5050509392505050565b5f82601f830112610f8257610f81610df1565b5b8135610f92848260208601610f06565b91505092915050565b5f67ffffffffffffffff821115610fb557610fb4610e05565b5b602082029050602081019050919050565b5f61ffff82169050919050565b610fdc81610fc6565b8114610fe6575f5ffd5b50565b5f81359050610ff781610fd3565b92915050565b5f61100f61100a84610f9b565b610e63565b9050808382526020820190506020840283018581111561103257611031610ea8565b5b835b8181101561105b57806110478882610fe9565b845260208401935050602081019050611034565b5050509392505050565b5f82601f83011261107957611078610df1565b5b8135611089848260208601610ffd565b91505092915050565b5f5f604083850312156110a8576110a7610de9565b5b5f83013567ffffffffffffffff8111156110c5576110c4610ded565b5b6110d185828601610f6e565b925050602083013567ffffffffffffffff8111156110f2576110f1610ded565b5b6110fe85828601611065565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61116c82610fc6565b915061ffff82036111805761117f611135565b5b600182019050919050565b5f819050919050565b5f61119e8261118b565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036111d0576111cf611135565b5b600182019050919050565b5f6111e58261118b565b91506111f08361118b565b925082820190508082111561120857611207611135565b5b92915050565b5f61121882610fc6565b915061122383610fc6565b9250828203905061ffff81111561123d5761123c611135565b5b92915050565b5f61124d8261118b565b91506112588361118b565b92508282039050818111156112705761126f611135565b5b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603160045260245ffd5b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b6112d581610ecb565b82525050565b5f6112e683836112cc565b60208301905092915050565b5f602082019050919050565b5f611308826112a3565b61131281856112ad565b935061131d836112bd565b805f5b8381101561134d57815161133488826112db565b975061133f836112f2565b925050600181019050611320565b5085935050505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b61138c81610fc6565b82525050565b5f61139d8383611383565b60208301905092915050565b5f602082019050919050565b5f6113bf8261135a565b6113c98185611364565b93506113d483611374565b805f5b838110156114045781516113eb8882611392565b97506113f6836113a9565b9250506001810190506113d7565b5085935050505092915050565b5f6040820190508181035f83015261142981856112fe565b9050818103602083015261143d81846113b5565b90509392505050565b5f5f6040838503121561145c5761145b610de9565b5b5f61146985828601610ef2565b925050602061147a85828601610fe9565b9150509250929050565b61148d8161118b565b8114611497575f5ffd5b50565b5f813590506114a881611484565b92915050565b5f5f604083850312156114c4576114c3610de9565b5b5f6114d185828601610ef2565b92505060206114e28582860161149a565b9150509250929050565b6114f581610fc6565b82525050565b6115048161118b565b82525050565b5f60408201905061151d5f8301856114ec565b61152a60208301846114fb565b9392505050565b5f6020828403121561154657611545610de9565b5b5f61155384828501610ef2565b91505092915050565b5f60208201905061156f5f8301846114fb565b92915050565b5f6020828403121561158a57611589610de9565b5b5f6115978482850161149a565b91505092915050565b6115a981610ecb565b82525050565b5f6020820190506115c25f8301846115a0565b92915050565b5f6020820190506115db5f8301846114ec565b92915050565b5f6115eb82610fc6565b91506115f683610fc6565b9250828201905061ffff8111156116105761160f611135565b5b9291505056fea2646970667358221220025e215254a4422cde6449804ceea203d39ea9932929ad229bc6b740ae44d67964736f6c63430008210033") + + BlobTicketSenderSlot = big.NewInt(3) + BlobTicketBalanceSlot = big.NewInt(2) )