diff --git a/EIPS/eip-000.md b/EIPS/eip-000.md index c387ec6793a1dd..d481145d31e46f 100644 --- a/EIPS/eip-000.md +++ b/EIPS/eip-000.md @@ -149,6 +149,16 @@ transactions using one, it is reasonable for the protocol to only burn the gas c As all transactions in the same block pay exactly the same `baseFeePerGas`, the single cost of accessing a cold item is divided evenly among all transactions containing such access and the rest of the burned base fee is refunded. +### Setting an absolute minimal cost of cold state access + +If a large number of transactions all access the same addresses or slots, the cost of each cold access may get +way too low which may represent a potential DoS attack vector. + +In order to prevent that, the gas cost of including an entity in the access list cannot be lower +than `MIN_ACCESS_LIST_ENTRY_COST`, which is set to `32 gas`. + +This value is equivalent to the calldata cost of including two bytes long identifier of entries in `block_access_list`. + ### Calculating a refund of the charged priority fee Each transaction pays an individual `priorityFeePerGas` value and redistributing this part of the cold access cost @@ -171,6 +181,35 @@ The most expensive transaction contributes a different value because it determin A single transaction accessing an address or a slot that is not shared by other transactions does not trigger a refund, and therefore has a zero marginal contribution. +### Efficiently storing the access lists in the block history + +The contents of the `accessList` parameter are part of the Ethereum history and the potential cost of keeping this +data in the blockchain must be accounted for when implementing this change. +There is currently no additional charge applied to the `accessList` parameter, due to the cost of including +an address or a storage slot in the `accessList` being a constant value that is significantly higher than the +potential cost of storing the `accessList` at the cost of a +dynamically sized `calldata` field. + +With the block-level warming there is a change that makes it possible for a transaction sender to construct +transactions with a large `accessList` that cost very little to be included, and this can be used to bloat the +blockchain size. + +This potential bloating of the block size also presents a challenge for the propagation of the block in a P2P network. + +In order to minimize the cost of permanently storing access lists, we propose the following changes to +the `execution_payload` structure: + +1. Add a new `block_access_list` field. \ + The execution clients create a combined block-level "access list" that contains all unique entries from all + transactions in the block. +2. All individual transaction `accessList` fields replace the full entries with a + compact 2 bytes long reference to the `block_access_list` in the same order they appeared originally. + +With this approach shared entries in the access lists cannot cause sufficient bloating of the block size. + +There is no need to introduce any observable changes to the RPC API as this "compression" can be unwrapped by the +clients in real time. + ### Pseudocode implementation of the refund calculation algorithm ```typescript @@ -201,7 +240,8 @@ function calculateItemColdAccessRefund ( for (let i = 0; i < sortedAccessDetails.length; i++) { const accessor = sortedAccessDetails[i] const refund = refunds.get(accessor.sender) ?? { refundFromBurn: 0n, refundFromCoinbase: 0n } - refund.refundFromBurn += BigInt(Math.floor(parseInt(accessGasCost) * parseInt(baseFeePerGas) * refundPercent)) + const adjustedAccessGasCost = Math.max(MIN_ACCESS_LIST_ENTRY_COST, parseInt(accessGasCost) * refundPercent) + refund.refundFromBurn += BigInt(adjustedAccessGasCost * parseInt(baseFeePerGas)) refund.refundFromCoinbase += BigInt(refundsFromCoinbase[i]) refunds.set(accessor.sender, refund) } @@ -234,7 +274,10 @@ export function calculatePriorityFeeRefunds (sortedAccesses: AccessDetails[], ac const refunds = [Math.floor(totalRefund * topTransactionContribution / totalContributions)] for (let i = 1; i < sortedAccesses.length; i++) { const charge = parseInt(sortedAccesses[i].priorityFeePerGas) * parseInt(accessGasCost) - refunds.push(Math.floor(totalRefund * charge / totalContributions)) + const calldataCharge = parseInt(sortedAccesses[i].priorityFeePerGas) * MIN_ACCESS_LIST_ENTRY_COST + const refundToCalldata = charge - calldataCharge + const refundToContribution = Math.floor(totalRefund * charge / totalContributions) + refunds.push(Math.min(refundToCalldata, refundToContribution)) } return refunds }