Support code delegations when purging confirmed blocks in the layered txpool (#8018)

Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
This commit is contained in:
Fabio Di Fabio
2025-01-10 11:22:09 +01:00
committed by GitHub
parent 2aadbfcb0a
commit 0698734678
8 changed files with 315 additions and 68 deletions

View File

@@ -19,7 +19,7 @@
- Retrieve all transaction receipts for a block in one request [#6646](https://github.com/hyperledger/besu/pull/6646)
- Implement EIP-7840: Add blob schedule to config files [#8042](https://github.com/hyperledger/besu/pull/8042)
- Allow gasPrice (legacy) and 1559 gasPrice params to be specified simultaneously for `eth_call`, `eth_createAccessList`, and `eth_estimateGas` [#8059](https://github.com/hyperledger/besu/pull/8059)
- Add support for EIP-7702 transaction in the txpool [#8018](https://github.com/hyperledger/besu/pull/8018) [#7984](https://github.com/hyperledger/besu/pull/7984)
### Bug fixes
- Fix serialization of state overrides when `movePrecompileToAddress` is present [#8204](https://github.com/hyperledger/besu/pull/8024)

View File

@@ -14,24 +14,35 @@
*/
package org.hyperledger.besu.ethereum.core;
import org.hyperledger.besu.crypto.CodeDelegationSignature;
import org.hyperledger.besu.crypto.KeyPair;
import org.hyperledger.besu.crypto.SECPSignature;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.AccessListEntry;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.BlobsWithCommitments;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.VersionedHash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.encoding.CodeDelegationTransactionEncoder;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import java.math.BigInteger;
import java.util.List;
import java.util.Optional;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import org.apache.tuweni.bytes.Bytes;
public class TransactionTestFixture {
private final SECPSignature signature =
new SECPSignature(BigInteger.ONE, BigInteger.ONE, (byte) 0);
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);
private static final KeyPair KEY_PAIR = SIGNATURE_ALGORITHM.get().generateKeyPair();
private static final org.hyperledger.besu.datatypes.CodeDelegation CODE_DELEGATION =
createSignedCodeDelegation(BigInteger.ZERO, Address.ZERO, 0, KEY_PAIR);
private TransactionType transactionType = TransactionType.FRONTIER;
private long nonce = 0;
@@ -100,9 +111,7 @@ public class TransactionTestFixture {
builder.maxPriorityFeePerGas(maxPriorityFeePerGas.orElse(Wei.of(500)));
builder.maxFeePerGas(maxFeePerGas.orElse(Wei.of(5000)));
builder.accessList(accessListEntries.orElse(List.of()));
builder.codeDelegations(
codeDelegations.orElse(
List.of(new CodeDelegation(chainId.get(), sender, 0, signature))));
builder.codeDelegations(codeDelegations.orElse(List.of(CODE_DELEGATION)));
break;
}
@@ -196,7 +205,28 @@ public class TransactionTestFixture {
public TransactionTestFixture codeDelegations(
final List<org.hyperledger.besu.datatypes.CodeDelegation> codeDelegations) {
this.codeDelegations = Optional.of(codeDelegations);
this.codeDelegations = Optional.ofNullable(codeDelegations);
return this;
}
public static CodeDelegation createSignedCodeDelegation(
final BigInteger chainId, final Address address, final long nonce, final KeyPair keys) {
BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput();
CodeDelegationTransactionEncoder.encodeSingleCodeDelegationWithoutSignature(
new org.hyperledger.besu.ethereum.core.CodeDelegation(chainId, address, nonce, null),
rlpOutput);
final Hash hash =
Hash.hash(
Bytes.concatenate(
org.hyperledger.besu.ethereum.core.CodeDelegation.MAGIC, rlpOutput.encoded()));
final var signature = SIGNATURE_ALGORITHM.get().sign(hash, keys);
return new org.hyperledger.besu.ethereum.core.CodeDelegation(
chainId,
address,
nonce,
CodeDelegationSignature.create(signature.getR(), signature.getS(), signature.getRecId()));
}
}

View File

@@ -430,7 +430,7 @@ public abstract class PendingTransaction
int ACCESS_LIST_ENTRY_SHALLOW_SIZE = 248;
int OPTIONAL_ACCESS_LIST_SHALLOW_SIZE = 40;
int OPTIONAL_CODE_DELEGATION_LIST_SHALLOW_SIZE = 40;
int CODE_DELEGATION_ENTRY_SIZE = 472;
int CODE_DELEGATION_ENTRY_SIZE = 520;
int VERSIONED_HASH_SIZE = 96;
int LIST_SHALLOW_SIZE = 48;
int OPTIONAL_SHALLOW_SIZE = 16;

View File

@@ -433,7 +433,7 @@ public class LayeredPendingTransactions implements PendingTransactions {
}
@Override
public synchronized void manageBlockAdded(
public void manageBlockAdded(
final BlockHeader blockHeader,
final List<Transaction> confirmedTransactions,
final List<Transaction> reorgTransactions,
@@ -447,19 +447,21 @@ public class LayeredPendingTransactions implements PendingTransactions {
final var reorgNonceRangeBySender = nonceRangeBySender(reorgTransactions);
try {
prioritizedTransactions.blockAdded(feeMarket, blockHeader, maxConfirmedNonceBySender);
} catch (final Throwable throwable) {
LOG.warn(
"Unexpected error {} when managing added block {}, maxNonceBySender {}, reorgNonceRangeBySender {}",
throwable,
blockHeader.toLogString(),
maxConfirmedNonceBySender,
reorgTransactions);
LOG.warn("Stack trace", throwable);
}
synchronized (this) {
try {
prioritizedTransactions.blockAdded(feeMarket, blockHeader, maxConfirmedNonceBySender);
} catch (final Throwable throwable) {
LOG.warn(
"Unexpected error {} when managing added block {}, maxNonceBySender {}, reorgNonceRangeBySender {}",
throwable,
blockHeader.toLogString(),
maxConfirmedNonceBySender,
reorgTransactions);
LOG.warn("Stack trace", throwable);
}
logBlockHeaderForReplay(blockHeader, maxConfirmedNonceBySender, reorgNonceRangeBySender);
logBlockHeaderForReplay(blockHeader, maxConfirmedNonceBySender, reorgNonceRangeBySender);
}
}
private void logBlockHeaderForReplay(
@@ -498,10 +500,25 @@ public class LayeredPendingTransactions implements PendingTransactions {
}
private Map<Address, Long> maxNonceBySender(final List<Transaction> confirmedTransactions) {
record SenderNonce(Address sender, long nonce) {}
return confirmedTransactions.stream()
.<SenderNonce>mapMulti(
(transaction, consumer) -> {
// always consider the sender
consumer.accept(new SenderNonce(transaction.getSender(), transaction.getNonce()));
// and if a code delegation tx also the authorities
if (transaction.getType().supportsDelegateCode()) {
transaction.getCodeDelegationList().get().stream()
.map(cd -> cd.authorizer().map(address -> new SenderNonce(address, cd.nonce())))
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(consumer);
}
})
.collect(
groupingBy(
Transaction::getSender, mapping(Transaction::getNonce, reducing(0L, Math::max))));
groupingBy(SenderNonce::sender, mapping(SenderNonce::nonce, reducing(0L, Math::max))));
}
private Map<Address, LongRange> nonceRangeBySender(

View File

@@ -140,7 +140,7 @@ public class PendingTransactionEstimatedMemorySizeTest extends BaseTransactionPo
@Test
public void toSize() {
TransactionTestFixture preparedTx =
prepareTransaction(TransactionType.ACCESS_LIST, 10, Wei.of(500), Wei.ZERO, 10, 0);
prepareTransaction(TransactionType.ACCESS_LIST, 10, Wei.of(500), Wei.ZERO, 10, 0, null);
Transaction txTo =
preparedTx.to(Optional.of(Address.extract(Bytes32.random()))).createTransaction(KEYS1);
BytesValueRLPOutput rlpOut = new BytesValueRLPOutput();
@@ -187,7 +187,7 @@ public class PendingTransactionEstimatedMemorySizeTest extends BaseTransactionPo
public void payloadSize() {
TransactionTestFixture preparedTx =
prepareTransaction(TransactionType.ACCESS_LIST, 10, Wei.of(500), Wei.ZERO, 10, 0);
prepareTransaction(TransactionType.ACCESS_LIST, 10, Wei.of(500), Wei.ZERO, 10, 0, null);
Transaction txPayload = preparedTx.createTransaction(KEYS1);
BytesValueRLPOutput rlpOut = new BytesValueRLPOutput();
txPayload.writeTo(rlpOut);
@@ -277,7 +277,7 @@ public class PendingTransactionEstimatedMemorySizeTest extends BaseTransactionPo
final long containerSize,
final long itemSize) {
TransactionTestFixture preparedTx =
prepareTransaction(TransactionType.BLOB, 10, Wei.of(500), Wei.of(50), 10, 1);
prepareTransaction(TransactionType.BLOB, 10, Wei.of(500), Wei.of(50), 10, 1, null);
Transaction txBlob = preparedTx.createTransaction(KEYS1);
BytesValueRLPOutput rlpOut = new BytesValueRLPOutput();
TransactionEncoder.encodeRLP(txBlob, rlpOut, EncodingContext.POOLED_TRANSACTION);
@@ -309,7 +309,7 @@ public class PendingTransactionEstimatedMemorySizeTest extends BaseTransactionPo
@Test
public void blobsWithCommitmentsSize() {
TransactionTestFixture preparedTx =
prepareTransaction(TransactionType.BLOB, 10, Wei.of(500), Wei.of(50), 10, 1);
prepareTransaction(TransactionType.BLOB, 10, Wei.of(500), Wei.of(50), 10, 1, null);
Transaction txBlob = preparedTx.createTransaction(KEYS1);
BytesValueRLPOutput rlpOut = new BytesValueRLPOutput();
TransactionEncoder.encodeRLP(txBlob, rlpOut, EncodingContext.POOLED_TRANSACTION);
@@ -337,7 +337,7 @@ public class PendingTransactionEstimatedMemorySizeTest extends BaseTransactionPo
public void pendingTransactionSize() {
TransactionTestFixture preparedTx =
prepareTransaction(TransactionType.ACCESS_LIST, 10, Wei.of(500), Wei.ZERO, 10, 0);
prepareTransaction(TransactionType.ACCESS_LIST, 10, Wei.of(500), Wei.ZERO, 10, 0, null);
Transaction txPayload = preparedTx.createTransaction(KEYS1);
BytesValueRLPOutput rlpOut = new BytesValueRLPOutput();
txPayload.writeTo(rlpOut);
@@ -369,7 +369,7 @@ public class PendingTransactionEstimatedMemorySizeTest extends BaseTransactionPo
final List<AccessListEntry> ales = List.of(ale1);
TransactionTestFixture preparedTx =
prepareTransaction(TransactionType.ACCESS_LIST, 0, Wei.of(500), Wei.ZERO, 0, 0);
prepareTransaction(TransactionType.ACCESS_LIST, 0, Wei.of(500), Wei.ZERO, 0, 0, null);
Transaction txAccessList = preparedTx.accessList(ales).createTransaction(KEYS1);
BytesValueRLPOutput rlpOut = new BytesValueRLPOutput();
txAccessList.writeTo(rlpOut);
@@ -416,7 +416,14 @@ public class PendingTransactionEstimatedMemorySizeTest extends BaseTransactionPo
System.setProperty("jol.magicFieldOffset", "true");
TransactionTestFixture preparedTx =
prepareTransaction(TransactionType.DELEGATE_CODE, 0, Wei.of(500), Wei.ZERO, 0, 0);
prepareTransaction(
TransactionType.DELEGATE_CODE,
0,
Wei.of(500),
Wei.ZERO,
0,
0,
List.of(CODE_DELEGATION_SENDER_1));
Transaction txDelegateCode = preparedTx.createTransaction(KEYS1);
BytesValueRLPOutput rlpOut = new BytesValueRLPOutput();
txDelegateCode.writeTo(rlpOut);
@@ -461,7 +468,7 @@ public class PendingTransactionEstimatedMemorySizeTest extends BaseTransactionPo
@Test
public void baseFrontierAndAccessListTransactionMemorySize() {
final Transaction txFrontier =
createTransaction(TransactionType.FRONTIER, 1, Wei.of(500), 0, KEYS1);
createTransaction(TransactionType.FRONTIER, 1, Wei.of(500), 0, List.of(), KEYS1);
assertThat(baseTransactionMemorySize(txFrontier, FRONTIER_ACCESS_LIST_CONSTANT_FIELD_PATHS))
.isEqualTo(FRONTIER_AND_ACCESS_LIST_SHALLOW_SIZE);
}
@@ -575,15 +582,18 @@ public class PendingTransactionEstimatedMemorySizeTest extends BaseTransactionPo
*
* @param filePath where to save the heap dump
* @param live true to only include live objects
* @throws IOException if any errors happen during the saving
*/
@SuppressWarnings("unused")
private static void dumpHeap(final String filePath, final boolean live) throws IOException {
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
HotSpotDiagnosticMXBean mxBean =
ManagementFactory.newPlatformMXBeanProxy(
server, "com.sun.management:type=HotSpotDiagnostic", HotSpotDiagnosticMXBean.class);
mxBean.dumpHeap(filePath, live);
private static void dumpHeap(final String filePath, final boolean live) {
try {
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
HotSpotDiagnosticMXBean mxBean =
ManagementFactory.newPlatformMXBeanProxy(
server, "com.sun.management:type=HotSpotDiagnostic", HotSpotDiagnosticMXBean.class);
mxBean.dumpHeap(filePath, live);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
record FieldSize(String path, Class<?> clazz, long size) implements Comparable<FieldSize> {

View File

@@ -116,6 +116,7 @@ public class BaseFeePrioritizedTransactionsTest extends AbstractPrioritizedTrans
originalTransaction.getMaxGasPrice().multiply(2).divide(10),
originalTransaction.getPayload().size(),
originalTransaction.getBlobCount(),
originalTransaction.getCodeDelegationList().orElse(null),
keys);
}
@@ -191,7 +192,7 @@ public class BaseFeePrioritizedTransactionsTest extends AbstractPrioritizedTrans
final TransactionType type) {
final PendingTransaction lowGasPriceTx =
createRemotePendingTransaction(
createTransaction(type, 0, DEFAULT_MIN_GAS_PRICE, Wei.ONE, 0, 1, KEYS1));
createTransaction(type, 0, DEFAULT_MIN_GAS_PRICE, Wei.ONE, 0, 1, null, KEYS1));
assertThat(prioritizeTransaction(lowGasPriceTx)).isEqualTo(DROPPED);
assertEvicted(lowGasPriceTx);
assertTransactionNotPrioritized(lowGasPriceTx);
@@ -217,6 +218,7 @@ public class BaseFeePrioritizedTransactionsTest extends AbstractPrioritizedTrans
0,
DEFAULT_MIN_GAS_PRICE.add(1).multiply(20),
0,
null,
SIGNATURE_ALGORITHM.get().generateKeyPair())))
.collect(Collectors.toUnmodifiableList());
@@ -238,6 +240,7 @@ public class BaseFeePrioritizedTransactionsTest extends AbstractPrioritizedTrans
DEFAULT_MIN_GAS_PRICE.divide(10),
0,
1,
null,
SIGNATURE_ALGORITHM.get().generateKeyPair());
addedTxs.add(tx);
assertThat(prioritizeTransaction(tx)).isEqualTo(ADDED);
@@ -251,6 +254,7 @@ public class BaseFeePrioritizedTransactionsTest extends AbstractPrioritizedTrans
DEFAULT_MIN_GAS_PRICE.divide(10),
0,
1,
null,
SIGNATURE_ALGORITHM.get().generateKeyPair());
assertThat(prioritizeTransaction(overflowTx)).isEqualTo(DROPPED);
@@ -272,6 +276,7 @@ public class BaseFeePrioritizedTransactionsTest extends AbstractPrioritizedTrans
DEFAULT_MIN_GAS_PRICE.divide(10),
0,
1,
null,
KEYS1);
addedTxs.add(tx);
assertThat(prioritizeTransaction(tx)).isEqualTo(ADDED);

View File

@@ -15,6 +15,7 @@
package org.hyperledger.besu.ethereum.eth.transactions.layered;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.core.TransactionTestFixture.createSignedCodeDelegation;
import org.hyperledger.besu.crypto.KeyPair;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
@@ -22,6 +23,7 @@ import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Blob;
import org.hyperledger.besu.datatypes.BlobsWithCommitments;
import org.hyperledger.besu.datatypes.CodeDelegation;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.KZGCommitment;
import org.hyperledger.besu.datatypes.KZGProof;
@@ -39,6 +41,8 @@ import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.metrics.StubMetricsSystem;
import org.hyperledger.besu.testutil.DeterministicEthScheduler;
import java.math.BigInteger;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.stream.IntStream;
@@ -56,6 +60,8 @@ public class BaseTransactionPoolTest {
protected static final KeyPair KEYS2 = SIGNATURE_ALGORITHM.get().generateKeyPair();
protected static final Address SENDER1 = Util.publicKeyToAddress(KEYS1.getPublicKey());
protected static final Address SENDER2 = Util.publicKeyToAddress(KEYS2.getPublicKey());
protected static final CodeDelegation CODE_DELEGATION_SENDER_1 =
createSignedCodeDelegation(BigInteger.ONE, Address.ZERO, 0, KEYS1);
protected static final Wei DEFAULT_MIN_GAS_PRICE = Wei.of(50);
protected static final Wei DEFAULT_MIN_PRIORITY_FEE = Wei.ZERO;
private static final Random randomizeTxType = new Random();
@@ -92,7 +98,7 @@ public class BaseTransactionPoolTest {
protected Transaction createEIP1559Transaction(
final long nonce, final KeyPair keys, final int gasFeeMultiplier) {
return createTransaction(
TransactionType.EIP1559, nonce, Wei.of(5000L).multiply(gasFeeMultiplier), 0, keys);
TransactionType.EIP1559, nonce, Wei.of(5000L).multiply(gasFeeMultiplier), 0, null, keys);
}
protected Transaction createEIP4844Transaction(
@@ -104,6 +110,21 @@ public class BaseTransactionPoolTest {
Wei.of(5000L).multiply(gasFeeMultiplier).divide(10),
0,
blobCount,
null,
keys);
}
protected Transaction createEIP7702Transaction(
final long nonce,
final KeyPair keys,
final int gasFeeMultiplier,
final List<CodeDelegation> codeDelegations) {
return createTransaction(
TransactionType.DELEGATE_CODE,
nonce,
Wei.of(5000L).multiply(gasFeeMultiplier),
0,
codeDelegations,
keys);
}
@@ -115,11 +136,11 @@ public class BaseTransactionPoolTest {
randomizeTxType.nextInt(txSize < blobTransaction0.getSize() ? 3 : 4)];
final Transaction baseTx =
createTransaction(txType, nonce, maxGasPrice, maxGasPrice.divide(10), 0, 1, keys);
createTransaction(txType, nonce, maxGasPrice, maxGasPrice.divide(10), 0, 1, null, keys);
final int payloadSize = txSize - baseTx.getSize();
return createTransaction(
txType, nonce, maxGasPrice, maxGasPrice.divide(10), payloadSize, 1, keys);
txType, nonce, maxGasPrice, maxGasPrice.divide(10), payloadSize, 1, null, keys);
}
protected Transaction createTransaction(
@@ -128,11 +149,14 @@ public class BaseTransactionPoolTest {
final TransactionType txType = TransactionType.values()[randomizeTxType.nextInt(4)];
return switch (txType) {
case FRONTIER, ACCESS_LIST, EIP1559, DELEGATE_CODE ->
createTransaction(txType, nonce, maxGasPrice, payloadSize, keys);
case FRONTIER, ACCESS_LIST, EIP1559 ->
createTransaction(txType, nonce, maxGasPrice, payloadSize, null, keys);
case BLOB ->
createTransaction(
txType, nonce, maxGasPrice, maxGasPrice.divide(10), payloadSize, 1, keys);
txType, nonce, maxGasPrice, maxGasPrice.divide(10), payloadSize, 1, null, keys);
case DELEGATE_CODE ->
createTransaction(
txType, nonce, maxGasPrice, payloadSize, List.of(CODE_DELEGATION_SENDER_1), keys);
};
}
@@ -141,9 +165,10 @@ public class BaseTransactionPoolTest {
final long nonce,
final Wei maxGasPrice,
final int payloadSize,
final List<CodeDelegation> codeDelegations,
final KeyPair keys) {
return createTransaction(
type, nonce, maxGasPrice, maxGasPrice.divide(10), payloadSize, 0, keys);
type, nonce, maxGasPrice, maxGasPrice.divide(10), payloadSize, 0, codeDelegations, keys);
}
protected Transaction createTransaction(
@@ -153,9 +178,10 @@ public class BaseTransactionPoolTest {
final Wei maxPriorityFeePerGas,
final int payloadSize,
final int blobCount,
final List<CodeDelegation> codeDelegations,
final KeyPair keys) {
return prepareTransaction(
type, nonce, maxGasPrice, maxPriorityFeePerGas, payloadSize, blobCount)
type, nonce, maxGasPrice, maxPriorityFeePerGas, payloadSize, blobCount, codeDelegations)
.createTransaction(keys);
}
@@ -165,7 +191,8 @@ public class BaseTransactionPoolTest {
final Wei maxGasPrice,
final Wei maxPriorityFeePerGas,
final int payloadSize,
final int blobCount) {
final int blobCount,
final List<CodeDelegation> codeDelegations) {
var tx =
new TransactionTestFixture()
@@ -198,6 +225,8 @@ public class BaseTransactionPoolTest {
final var blobsWithCommitments =
new BlobsWithCommitments(kgzCommitments, blobs, kzgProofs, versionHashes);
tx.blobsWithCommitments(Optional.of(blobsWithCommitments));
} else if (type.supportsDelegateCode()) {
tx.codeDelegations(codeDelegations);
}
} else {
tx.gasPrice(maxGasPrice);
@@ -214,6 +243,7 @@ public class BaseTransactionPoolTest {
originalTransaction.getMaxGasPrice().multiply(2).divide(10),
0,
1,
originalTransaction.getCodeDelegationList().orElse(null),
keys);
}

View File

@@ -19,9 +19,14 @@ import static org.assertj.core.api.Assertions.fail;
import static org.awaitility.Awaitility.await;
import static org.hyperledger.besu.datatypes.TransactionType.ACCESS_LIST;
import static org.hyperledger.besu.datatypes.TransactionType.BLOB;
import static org.hyperledger.besu.datatypes.TransactionType.DELEGATE_CODE;
import static org.hyperledger.besu.datatypes.TransactionType.EIP1559;
import static org.hyperledger.besu.datatypes.TransactionType.FRONTIER;
import static org.hyperledger.besu.ethereum.core.TransactionTestFixture.createSignedCodeDelegation;
import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayeredRemovalReason.PoolRemovalReason.INVALIDATED;
import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayersTest.AuthorityAndNonce.NO_DELEGATIONS;
import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayersTest.AuthorityAndNonce.delegation;
import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayersTest.AuthorityAndNonce.toCodeDelegations;
import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayersTest.Sender.S1;
import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayersTest.Sender.S2;
import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayersTest.Sender.S3;
@@ -33,6 +38,7 @@ import static org.mockito.Mockito.when;
import org.hyperledger.besu.crypto.KeyPair;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.CodeDelegation;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.BlockHeader;
@@ -50,6 +56,7 @@ import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.testutil.DeterministicEthScheduler;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -62,6 +69,7 @@ import java.util.NavigableMap;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
@@ -174,6 +182,12 @@ public class LayersTest extends BaseTransactionPoolTest {
assertScenario(scenario);
}
@ParameterizedTest
@MethodSource("providerConfirmedEIP7702")
void confirmedEIP7702(final Scenario scenario) {
assertScenario(scenario);
}
private void assertScenario(final Scenario scenario) {
scenario.run();
}
@@ -1309,6 +1323,57 @@ public class LayersTest extends BaseTransactionPoolTest {
.expectedPrioritizedForSenders()));
}
static Stream<Arguments> providerConfirmedEIP7702() {
return Stream.of(
Arguments.of(
new Scenario("code delegation tx only")
.addForSender(S1, delegation(S2, 0), 0)
.expectedPrioritizedForSender(S1, 0)
.confirmedForSenders(S1, 0)
.expectedPrioritizedForSenders()),
Arguments.of(
new Scenario("confirmed delegation over plain tx")
.addForSender(S2, 0)
.addForSender(S1, delegation(S2, 0), 0)
.expectedPrioritizedForSenders(S2, 0, S1, 0)
.confirmedForSenders(S1, 0)
// confirming the code delegation tx updates the nonce for S2, so his conflicting
// plain tx is removed
.expectedPrioritizedForSenders()),
Arguments.of(
new Scenario("confirmed plain tx over delegation")
.addForSender(S2, 0)
.addForSender(S1, delegation(S2, 0), 0)
.expectedPrioritizedForSenders(S2, 0, S1, 0)
.confirmedForSenders(S2, 0)
// verify the code delegation for S2 is still there, of course that delegation will
// fail,
// but is it not possible to remove it from the list
.expectedPrioritizedForSender(S1, 0)),
Arguments.of(
new Scenario("self code delegation")
.addForSender(S1, delegation(S1, 1), 0)
.expectedPrioritizedForSender(S1, 0)
.confirmedForSenders(S1, 0)
.expectedPrioritizedForSenders()),
Arguments.of(
new Scenario("self code delegation and plain tx")
.addForSender(S1, delegation(S1, 1), 0)
.addForSender(S1, 1)
.expectedPrioritizedForSender(S1, 0, 1)
.confirmedForSenders(S1, 0)
.expectedPrioritizedForSenders()),
Arguments.of(
new Scenario("self code delegation and plain tx in sparse")
.addForSender(S1, delegation(S1, 1), 0)
.addForSender(S1, 2)
.expectedPrioritizedForSender(S1, 0)
.expectedSparseForSender(S1, 2)
.confirmedForSenders(S1, 0)
.expectedPrioritizedForSender(S1, 2)
.expectedSparseForSenders()));
}
private static BlockHeader mockBlockHeader() {
final BlockHeader blockHeader = mock(BlockHeader.class);
when(blockHeader.getBaseFee()).thenReturn(Optional.of(BASE_FEE));
@@ -1423,24 +1488,42 @@ public class LayersTest extends BaseTransactionPoolTest {
}
public Scenario addForSender(final Sender sender, final long... nonce) {
return addForSender(sender, EIP1559, nonce);
return addForSender(sender, EIP1559, NO_DELEGATIONS, nonce);
}
public Scenario addForSender(
final Sender sender, final TransactionType type, final long... nonce) {
internalAddForSender(sender, type, nonce);
internalAddForSender(sender, type, NO_DELEGATIONS, nonce);
actions.add(notificationsChecker::assertExpectedNotifications);
return this;
}
public Scenario addForSender(
final Sender sender, final AuthorityAndNonce[] authorityAndNonces, final long... nonce) {
return addForSender(sender, DELEGATE_CODE, authorityAndNonces, nonce);
}
public Scenario addForSender(
final Sender sender,
final TransactionType type,
final AuthorityAndNonce[] authorityAndNonces,
final long... nonce) {
internalAddForSender(sender, type, authorityAndNonces, nonce);
actions.add(notificationsChecker::assertExpectedNotifications);
return this;
}
private void internalAddForSender(
final Sender sender, final TransactionType type, final long... nonce) {
final Sender sender,
final TransactionType type,
final AuthorityAndNonce[] authorityAndNonces,
final long... nonce) {
actions.add(
() -> {
Arrays.stream(nonce)
.forEach(
n -> {
final var pendingTx = create(sender, type, n);
final var pendingTx = create(sender, type, authorityAndNonces, n);
final Account mockSender = mock(Account.class);
when(mockSender.getNonce()).thenReturn(nonceBySender.get(sender));
pending.addTransaction(pendingTx, Optional.of(mockSender));
@@ -1497,7 +1580,7 @@ public class LayersTest extends BaseTransactionPoolTest {
for (int i = 0; i < args.length; i = i + 2) {
final Sender sender = (Sender) args[i];
final long nonce = (int) args[i + 1];
internalAddForSender(sender, EIP1559, nonce);
internalAddForSender(sender, EIP1559, NO_DELEGATIONS, nonce);
}
actions.add(notificationsChecker::assertExpectedNotifications);
return this;
@@ -1549,21 +1632,61 @@ public class LayersTest extends BaseTransactionPoolTest {
public Scenario confirmedForSenders(final Object... args) {
actions.add(
() -> {
final Map<Address, Long> maxConfirmedNonceBySender = new HashMap<>();
final Map<Sender, Long> maxConfirmedNonceBySender = new HashMap<>();
for (int i = 0; i < args.length; i = i + 2) {
final Sender sender = (Sender) args[i];
final long nonce = (int) args[i + 1];
maxConfirmedNonceBySender.put(sender.address, nonce);
maxConfirmedNonceBySender.put(sender, nonce);
nonceBySender.put(sender, nonce + 1);
for (final var pendingTx : getAll(sender)) {
if (pendingTx.getNonce() <= nonce) {
notificationsChecker.addExpectedDropNotification(
liveTxsBySender.get(sender).remove(pendingTx.getNonce()));
}
}
// if the confirmed tx contains delegations then update the confirmed nonce
// accordingly
getMaybe(sender, nonce)
.ifPresent(
confirmedTx ->
confirmedTx
.getTransaction()
.getCodeDelegationList()
.ifPresent(
codeDelegations ->
codeDelegations.forEach(
cd -> {
final var authority =
Sender.getByAddress(cd.authorizer().get());
maxConfirmedNonceBySender.compute(
authority,
(unused, currentMax) ->
currentMax == null
? cd.nonce()
: Math.max(currentMax, cd.nonce()));
nonceBySender.compute(
authority,
(unused, currentNonce) ->
currentNonce == null
? cd.nonce() + 1
: Math.max(currentNonce, cd.nonce()) + 1);
})));
}
prio.blockAdded(FeeMarket.london(0L), mockBlockHeader(), maxConfirmedNonceBySender);
maxConfirmedNonceBySender.entrySet().stream()
.forEach(
san -> {
final var sender = san.getKey();
final var nonce = san.getValue();
for (final var pendingTx : getAll(sender)) {
if (pendingTx.getNonce() <= nonce) {
notificationsChecker.addExpectedDropNotification(
liveTxsBySender.get(sender).remove(pendingTx.getNonce()));
}
}
});
prio.blockAdded(
FeeMarket.london(0L),
mockBlockHeader(),
maxConfirmedNonceBySender.entrySet().stream()
.collect(
Collectors.toMap(entry -> entry.getKey().address, Map.Entry::getValue)));
notificationsChecker.assertExpectedNotifications();
});
return this;
@@ -1588,7 +1711,10 @@ public class LayersTest extends BaseTransactionPoolTest {
}
private PendingTransaction create(
final Sender sender, final TransactionType type, final long nonce) {
final Sender sender,
final TransactionType type,
final AuthorityAndNonce[] authorityAndNonces,
final long nonce) {
if (liveTxsBySender.get(sender).containsKey(nonce)) {
fail(
"Transaction for sender " + sender.name() + " with nonce " + nonce + " already exists");
@@ -1599,7 +1725,8 @@ public class LayersTest extends BaseTransactionPoolTest {
case ACCESS_LIST -> createAccessListPendingTransaction(sender, nonce);
case EIP1559 -> createEIP1559PendingTransaction(sender, nonce);
case BLOB -> createBlobPendingTransaction(sender, nonce);
case DELEGATE_CODE -> throw new UnsupportedOperationException();
case DELEGATE_CODE ->
createEIP7702PendingTransaction(sender, nonce, authorityAndNonces);
};
liveTxsBySender.get(sender).put(nonce, newPendingTx);
return newPendingTx;
@@ -1629,13 +1756,15 @@ public class LayersTest extends BaseTransactionPoolTest {
private PendingTransaction createFrontierPendingTransaction(
final Sender sender, final long nonce) {
return createRemotePendingTransaction(
createTransaction(FRONTIER, nonce, Wei.ONE, 0, sender.key), sender.hasPriority);
createTransaction(FRONTIER, nonce, Wei.ONE, 0, List.of(), sender.key),
sender.hasPriority);
}
private PendingTransaction createAccessListPendingTransaction(
final Sender sender, final long nonce) {
return createRemotePendingTransaction(
createTransaction(ACCESS_LIST, nonce, Wei.ONE, 0, sender.key), sender.hasPriority);
createTransaction(ACCESS_LIST, nonce, Wei.ONE, 0, List.of(), sender.key),
sender.hasPriority);
}
private PendingTransaction createEIP1559PendingTransaction(
@@ -1650,6 +1779,14 @@ public class LayersTest extends BaseTransactionPoolTest {
sender.hasPriority);
}
private PendingTransaction createEIP7702PendingTransaction(
final Sender sender, final long nonce, final AuthorityAndNonce[] authorityAndNonces) {
return createRemotePendingTransaction(
createEIP7702Transaction(
nonce, sender.key, sender.gasFeeMultiplier, toCodeDelegations(authorityAndNonces)),
sender.hasPriority);
}
public Scenario expectedPrioritizedForSender(final Sender sender, final long... nonce) {
actions.add(
() -> {
@@ -1835,7 +1972,8 @@ public class LayersTest extends BaseTransactionPoolTest {
.forEach(
n -> {
final var maybeLiveTx = getMaybe(sender, n);
final var pendingTx = maybeLiveTx.orElseGet(() -> create(sender, EIP1559, n));
final var pendingTx =
maybeLiveTx.orElseGet(() -> create(sender, EIP1559, NO_DELEGATIONS, n));
prio.remove(pendingTx, INVALIDATED);
maybeLiveTx.ifPresent(
liveTx -> {
@@ -1968,6 +2106,23 @@ public class LayersTest extends BaseTransactionPoolTest {
}
}
record AuthorityAndNonce(Sender sender, long nonce) {
static final AuthorityAndNonce[] NO_DELEGATIONS = new AuthorityAndNonce[0];
static AuthorityAndNonce[] delegation(final Sender sender, final long nonce) {
return new AuthorityAndNonce[] {new AuthorityAndNonce(sender, nonce)};
}
static CodeDelegation toCodeDelegation(final AuthorityAndNonce authorityAndNonce) {
return createSignedCodeDelegation(
BigInteger.ZERO, Address.ZERO, authorityAndNonce.nonce, authorityAndNonce.sender.key);
}
static List<CodeDelegation> toCodeDelegations(final AuthorityAndNonce[] authorityAndNonces) {
return Arrays.stream(authorityAndNonces).map(AuthorityAndNonce::toCodeDelegation).toList();
}
}
@Test
void dryRunDetector() {
assertThat(true)