diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java index 1926fa8a7d3..e3fde4c32be 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java @@ -1092,6 +1092,10 @@ public Transaction detachedCopy() { blobsWithCommitments.map( withCommitments -> blobsWithCommitmentsDetachedCopy(withCommitments, detachedVersionedHashes.get())); + final Optional> detachedCodeDelegationList = + maybeCodeDelegationList.map( + codeDelegations -> + codeDelegations.stream().map(this::codeDelegationDetachedCopy).toList()); final var copiedTx = new Transaction( @@ -1112,7 +1116,7 @@ public Transaction detachedCopy() { chainId, detachedVersionedHashes, detachedBlobsWithCommitments, - maybeCodeDelegationList); + detachedCodeDelegationList); // copy also the computed fields, to avoid to recompute them copiedTx.sender = this.sender; @@ -1129,6 +1133,15 @@ private AccessListEntry accessListDetachedCopy(final AccessListEntry accessListE return new AccessListEntry(detachedAddress, detachedStorage); } + private CodeDelegation codeDelegationDetachedCopy(final CodeDelegation codeDelegation) { + final Address detachedAddress = Address.wrap(codeDelegation.address().copy()); + return new org.hyperledger.besu.ethereum.core.CodeDelegation( + codeDelegation.chainId(), + detachedAddress, + codeDelegation.nonce(), + codeDelegation.signature()); + } + private BlobsWithCommitments blobsWithCommitmentsDetachedCopy( final BlobsWithCommitments blobsWithCommitments, final List versionedHashes) { final var detachedCommitments = diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java index 602116749b6..5cef826ed68 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.core; import org.hyperledger.besu.crypto.KeyPair; +import org.hyperledger.besu.crypto.SECPSignature; import org.hyperledger.besu.datatypes.AccessListEntry; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.BlobsWithCommitments; @@ -29,7 +30,8 @@ import org.apache.tuweni.bytes.Bytes; public class TransactionTestFixture { - + private final SECPSignature signature = + new SECPSignature(BigInteger.ONE, BigInteger.ONE, (byte) 0); private TransactionType transactionType = TransactionType.FRONTIER; private long nonce = 0; @@ -56,6 +58,8 @@ public class TransactionTestFixture { private Optional blobs = Optional.empty(); private Optional v = Optional.empty(); + private Optional> codeDelegations = + Optional.empty(); public Transaction createTransaction(final KeyPair keys) { final Transaction.Builder builder = Transaction.builder(); @@ -93,6 +97,12 @@ public Transaction createTransaction(final KeyPair keys) { } break; case DELEGATE_CODE: + 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)))); break; } @@ -183,4 +193,10 @@ public TransactionTestFixture blobsWithCommitments(final Optional codeDelegations) { + this.codeDelegations = Optional.of(codeDelegations); + return this; + } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java index 91b4efd7b21..cf6bafa8320 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java @@ -38,7 +38,9 @@ public abstract class PendingTransaction static final int PAYLOAD_BASE_MEMORY_SIZE = 32; static final int ACCESS_LIST_STORAGE_KEY_MEMORY_SIZE = 32; static final int ACCESS_LIST_ENTRY_BASE_MEMORY_SIZE = 248; - static final int OPTIONAL_ACCESS_LIST_MEMORY_SIZE = 24; + static final int OPTIONAL_ACCESS_LIST_MEMORY_SIZE = 40; + static final int OPTIONAL_CODE_DELEGATION_LIST_MEMORY_SIZE = 40; + static final int CODE_DELEGATION_ENTRY_MEMORY_SIZE = 432; static final int VERSIONED_HASH_SIZE = 96; static final int BASE_LIST_SIZE = 48; static final int BASE_OPTIONAL_SIZE = 16; @@ -147,7 +149,7 @@ private int computeMemorySize() { case ACCESS_LIST -> computeAccessListMemorySize(); case EIP1559 -> computeEIP1559MemorySize(); case BLOB -> computeBlobMemorySize(); - case DELEGATE_CODE -> computeSetCodeMemorySize(); + case DELEGATE_CODE -> computeDelegateCodeMemorySize(); } + PENDING_TRANSACTION_MEMORY_SIZE; } @@ -181,8 +183,8 @@ private int computeBlobMemorySize() { + computeBlobWithCommitmentsMemorySize(); } - private int computeSetCodeMemorySize() { - return 0; + private int computeDelegateCodeMemorySize() { + return computeEIP1559MemorySize() + computeCodeDelegationListMemorySize(); } private int computeBlobWithCommitmentsMemorySize() { @@ -197,7 +199,7 @@ private int computeBlobWithCommitmentsMemorySize() { } private int computePayloadMemorySize() { - return transaction.getPayload().size() > 0 + return !transaction.getPayload().isEmpty() ? PAYLOAD_BASE_MEMORY_SIZE + transaction.getPayload().size() : 0; } @@ -231,6 +233,18 @@ private int computeAccessListEntriesMemorySize() { .orElse(0); } + private int computeCodeDelegationListMemorySize() { + return transaction + .getCodeDelegationList() + .map( + cd -> { + int totalSize = OPTIONAL_CODE_DELEGATION_LIST_MEMORY_SIZE; + totalSize += cd.size() * CODE_DELEGATION_ENTRY_MEMORY_SIZE; + return totalSize; + }) + .orElse(0); + } + public static List toTransactionList( final Collection transactionsInfo) { return transactionsInfo.stream().map(PendingTransaction::getTransaction).toList(); @@ -252,7 +266,7 @@ public boolean equals(final Object o) { @Override public int hashCode() { - return 31 * (int) (sequence ^ (sequence >>> 32)); + return 31 * Long.hashCode(sequence); } @Override diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionEstimatedMemorySizeTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionEstimatedMemorySizeTest.java index af54f668fe8..c80f5ca1ad4 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionEstimatedMemorySizeTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionEstimatedMemorySizeTest.java @@ -20,6 +20,7 @@ import org.hyperledger.besu.datatypes.AccessListEntry; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.BlobsWithCommitments; +import org.hyperledger.besu.datatypes.CodeDelegation; import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.Transaction; @@ -31,6 +32,8 @@ import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import java.io.IOException; +import java.lang.management.ManagementFactory; import java.math.BigInteger; import java.util.Arrays; import java.util.HashSet; @@ -41,8 +44,10 @@ import java.util.TreeSet; import java.util.concurrent.atomic.LongAdder; import java.util.function.Function; +import javax.management.MBeanServer; import com.google.common.collect.Sets; +import com.sun.management.HotSpotDiagnosticMXBean; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.Test; @@ -64,7 +69,14 @@ public class PendingTransactionEstimatedMemorySizeTest extends BaseTransactionPo private static final Set FRONTIER_ACCESS_LIST_CONSTANT_FIELD_PATHS = Sets.union(COMMON_CONSTANT_FIELD_PATHS, Set.of(".maxFeePerGas", ".maxPriorityFeePerGas")); private static final Set VARIABLE_SIZE_PATHS = - Set.of(".chainId", ".to", ".payload", ".maybeAccessList"); + Set.of( + ".chainId", + ".to", + ".payload", + ".maybeAccessList", + ".versionedHashes", + ".blobsWithCommitments", + ".maybeCodeDelegationList"); @Test public void toSize() { @@ -100,6 +112,7 @@ public void toSize() { + ", " + gpr.klass().toString() + "]"); + System.out.println(ClassLayout.parseClass(gpr.klass()).toPrintable()); }; GraphWalker gw = new GraphWalker(gv); @@ -319,9 +332,10 @@ public void accessListSize() { final ClassLayout cl2 = ClassLayout.parseInstance(optAL.get()); System.out.println(cl2.toPrintable()); - System.out.println("Optional + list size: " + cl2.instanceSize()); + System.out.println("Optional + list size: " + (cl1.instanceSize() + cl2.instanceSize())); - assertThat(cl2.instanceSize()).isEqualTo(PendingTransaction.OPTIONAL_ACCESS_LIST_MEMORY_SIZE); + assertThat(cl1.instanceSize() + cl2.instanceSize()) + .isEqualTo(PendingTransaction.OPTIONAL_ACCESS_LIST_MEMORY_SIZE); final AccessListEntry ale = optAL.get().get(0); @@ -341,80 +355,87 @@ public void accessListSize() { } @Test - public void baseEIP1559AndEIP4844TransactionMemorySize() { + public void codeDelegationListSize() { System.setProperty("jol.magicFieldOffset", "true"); - Transaction txEip1559 = createEIP1559Transaction(1, KEYS1, 10); + + TransactionTestFixture preparedTx = + prepareTransaction(TransactionType.DELEGATE_CODE, 0, Wei.of(500), Wei.ZERO, 0, 0); + Transaction txDelegateCode = preparedTx.createTransaction(KEYS1); BytesValueRLPOutput rlpOut = new BytesValueRLPOutput(); - txEip1559.writeTo(rlpOut); + txDelegateCode.writeTo(rlpOut); - txEip1559 = + txDelegateCode = Transaction.readFrom(new BytesValueRLPInput(rlpOut.encoded(), false)).detachedCopy(); - System.out.println(txEip1559.getSender()); - System.out.println(txEip1559.getHash()); - System.out.println(txEip1559.getSize()); + System.out.println(txDelegateCode.getSender()); + System.out.println(txDelegateCode.getHash()); + System.out.println(txDelegateCode.getSize()); - final ClassLayout cl = ClassLayout.parseInstance(txEip1559); - System.out.println(cl.toPrintable()); - LongAdder eip1559size = new LongAdder(); - eip1559size.add(cl.instanceSize()); - System.out.println(eip1559size); + final var optCD = txDelegateCode.getCodeDelegationList(); - final SortedSet fieldSizes = new TreeSet<>(); - GraphWalker gw = getGraphWalker(EIP1559_EIP4844_CONSTANT_FIELD_PATHS, fieldSizes); + final ClassLayout cl1 = ClassLayout.parseInstance(optCD); + System.out.println(cl1.toPrintable()); + System.out.println("Optional size: " + cl1.instanceSize()); - gw.walk(txEip1559); + final ClassLayout cl2 = ClassLayout.parseInstance(optCD.get()); + System.out.println(cl2.toPrintable()); + System.out.println("Optional + list size: " + (cl1.instanceSize() + cl2.instanceSize())); - fieldSizes.forEach( - fieldSize -> { - eip1559size.add(fieldSize.size()); - System.out.println( - "(" - + eip1559size - + ")[" - + fieldSize.size() - + ", " - + fieldSize.path() - + ", " - + fieldSize - + "]"); - }); + assertThat(cl1.instanceSize() + cl2.instanceSize()) + .isEqualTo(PendingTransaction.OPTIONAL_CODE_DELEGATION_LIST_MEMORY_SIZE); + + final CodeDelegation codeDelegation = optCD.get().get(0); + + long cdSize = sizeOfField(codeDelegation, "storageKeys.elementData["); - System.out.println("Base EIP1559 size: " + eip1559size); - assertThat(eip1559size.sum()) + System.out.println("CodeDelegation container size: " + cdSize); + + assertThat(cdSize).isEqualTo(PendingTransaction.CODE_DELEGATION_ENTRY_MEMORY_SIZE); + } + + @Test + public void baseEIP1559AndEIP4844TransactionMemorySize() { + Transaction txEip1559 = createEIP1559Transaction(1, KEYS1, 10); + assertThat(baseTransactionMemorySize(txEip1559, EIP1559_EIP4844_CONSTANT_FIELD_PATHS)) .isEqualTo(PendingTransaction.EIP1559_AND_EIP4844_SHALLOW_MEMORY_SIZE); } @Test public void baseFrontierAndAccessListTransactionMemorySize() { + final Transaction txFrontier = + createTransaction(TransactionType.FRONTIER, 1, Wei.of(500), 0, KEYS1); + assertThat(baseTransactionMemorySize(txFrontier, FRONTIER_ACCESS_LIST_CONSTANT_FIELD_PATHS)) + .isEqualTo(PendingTransaction.FRONTIER_AND_ACCESS_LIST_SHALLOW_MEMORY_SIZE); + } + + private long baseTransactionMemorySize(final Transaction tx, final Set constantFields) { System.setProperty("jol.magicFieldOffset", "true"); - Transaction txFrontier = createTransaction(TransactionType.FRONTIER, 1, Wei.of(500), 0, KEYS1); BytesValueRLPOutput rlpOut = new BytesValueRLPOutput(); - txFrontier.writeTo(rlpOut); + tx.writeTo(rlpOut); - txFrontier = + final var baseTx = Transaction.readFrom(new BytesValueRLPInput(rlpOut.encoded(), false)).detachedCopy(); - System.out.println(txFrontier.getSender()); - System.out.println(txFrontier.getHash()); - System.out.println(txFrontier.getSize()); + System.out.println(baseTx.getSender()); + System.out.println(baseTx.getHash()); + System.out.println(baseTx.getSize()); - final ClassLayout cl = ClassLayout.parseInstance(txFrontier); + final ClassLayout cl = ClassLayout.parseInstance(baseTx); System.out.println(cl.toPrintable()); - LongAdder frontierSize = new LongAdder(); - frontierSize.add(cl.instanceSize()); - System.out.println(frontierSize); + LongAdder baseTxSize = new LongAdder(); + baseTxSize.add(cl.instanceSize()); + System.out.println(baseTxSize); final SortedSet fieldSizes = new TreeSet<>(); - GraphWalker gw = getGraphWalker(FRONTIER_ACCESS_LIST_CONSTANT_FIELD_PATHS, fieldSizes); + GraphWalker gw = getGraphWalker(constantFields, fieldSizes); - gw.walk(txFrontier); + gw.walk(baseTx); fieldSizes.forEach( fieldSize -> { - frontierSize.add(fieldSize.size()); + baseTxSize.add(fieldSize.size()); System.out.println( "(" - + frontierSize + + baseTxSize + ")[" + fieldSize.size() + ", " @@ -423,10 +444,8 @@ public void baseFrontierAndAccessListTransactionMemorySize() { + fieldSize + "]"); }); - - System.out.println("Base Frontier size: " + frontierSize); - assertThat(frontierSize.sum()) - .isEqualTo(PendingTransaction.FRONTIER_AND_ACCESS_LIST_SHALLOW_MEMORY_SIZE); + System.out.println("Base tx size: " + baseTxSize); + return baseTxSize.sum(); } private GraphWalker getGraphWalker( @@ -478,6 +497,7 @@ private long sizeOfField(final Object container, final String... excludePaths) { + ", " + gpr.klass().toString() + "]"); + System.out.println(ClassLayout.parseClass(gpr.klass()).toPrintable()); } }; @@ -489,6 +509,15 @@ private long sizeOfField(final Object container, final String... excludePaths) { return size.sum(); } + @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); + } + record FieldSize(String path, Class clazz, long size) implements Comparable { @Override