mirror of
https://github.com/vacp2p/linea-besu.git
synced 2026-01-09 15:37:54 -05:00
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:
@@ -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)
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user