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 cf6bafa8320..ee5c40a3f4f 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 @@ -14,6 +14,24 @@ */ package org.hyperledger.besu.ethereum.eth.transactions; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.ACCESS_LIST_ENTRY_SHALLOW_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.ACCESS_LIST_STORAGE_KEY_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.BLOBS_WITH_COMMITMENTS_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.BLOB_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.CODE_DELEGATION_ENTRY_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.EIP1559_AND_EIP4844_SHALLOW_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.FRONTIER_AND_ACCESS_LIST_SHALLOW_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.KZG_COMMITMENT_OR_PROOF_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.LIST_SHALLOW_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.OPTIONAL_ACCESS_LIST_SHALLOW_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.OPTIONAL_CHAIN_ID_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.OPTIONAL_CODE_DELEGATION_LIST_SHALLOW_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.OPTIONAL_SHALLOW_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.OPTIONAL_TO_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.PAYLOAD_SHALLOW_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.PENDING_TRANSACTION_SHALLOW_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.VERSIONED_HASH_SIZE; + import org.hyperledger.besu.datatypes.AccessListEntry; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; @@ -31,23 +49,6 @@ public abstract class PendingTransaction implements org.hyperledger.besu.datatypes.PendingTransaction { static final int NOT_INITIALIZED = -1; - static final int FRONTIER_AND_ACCESS_LIST_SHALLOW_MEMORY_SIZE = 904; - static final int EIP1559_AND_EIP4844_SHALLOW_MEMORY_SIZE = 1016; - static final int OPTIONAL_TO_MEMORY_SIZE = 112; - static final int OPTIONAL_CHAIN_ID_MEMORY_SIZE = 80; - 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 = 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; - static final int KZG_COMMITMENT_OR_PROOF_SIZE = 112; - static final int BLOB_SIZE = 131136; - static final int BLOBS_WITH_COMMITMENTS_SIZE = 40; - static final int PENDING_TRANSACTION_MEMORY_SIZE = 40; private static final AtomicLong TRANSACTIONS_ADDED = new AtomicLong(); private final Transaction transaction; private final long addedAt; @@ -151,18 +152,18 @@ private int computeMemorySize() { case BLOB -> computeBlobMemorySize(); case DELEGATE_CODE -> computeDelegateCodeMemorySize(); } - + PENDING_TRANSACTION_MEMORY_SIZE; + + PENDING_TRANSACTION_SHALLOW_SIZE; } private int computeFrontierMemorySize() { - return FRONTIER_AND_ACCESS_LIST_SHALLOW_MEMORY_SIZE + return FRONTIER_AND_ACCESS_LIST_SHALLOW_SIZE + computePayloadMemorySize() + computeToMemorySize() + computeChainIdMemorySize(); } private int computeAccessListMemorySize() { - return FRONTIER_AND_ACCESS_LIST_SHALLOW_MEMORY_SIZE + return FRONTIER_AND_ACCESS_LIST_SHALLOW_SIZE + computePayloadMemorySize() + computeToMemorySize() + computeChainIdMemorySize() @@ -170,7 +171,7 @@ private int computeAccessListMemorySize() { } private int computeEIP1559MemorySize() { - return EIP1559_AND_EIP4844_SHALLOW_MEMORY_SIZE + return EIP1559_AND_EIP4844_SHALLOW_SIZE + computePayloadMemorySize() + computeToMemorySize() + computeChainIdMemorySize() @@ -179,7 +180,7 @@ private int computeEIP1559MemorySize() { private int computeBlobMemorySize() { return computeEIP1559MemorySize() - + BASE_OPTIONAL_SIZE // for the versionedHashes field + + OPTIONAL_SHALLOW_SIZE // for the versionedHashes field + computeBlobWithCommitmentsMemorySize(); } @@ -190,9 +191,9 @@ private int computeDelegateCodeMemorySize() { private int computeBlobWithCommitmentsMemorySize() { final int blobCount = transaction.getBlobCount(); - return BASE_OPTIONAL_SIZE + return OPTIONAL_SHALLOW_SIZE + BLOBS_WITH_COMMITMENTS_SIZE - + (BASE_LIST_SIZE * 4) + + (LIST_SHALLOW_SIZE * 4) + (KZG_COMMITMENT_OR_PROOF_SIZE * blobCount * 2) + (VERSIONED_HASH_SIZE * blobCount) + (BLOB_SIZE * blobCount); @@ -200,20 +201,20 @@ private int computeBlobWithCommitmentsMemorySize() { private int computePayloadMemorySize() { return !transaction.getPayload().isEmpty() - ? PAYLOAD_BASE_MEMORY_SIZE + transaction.getPayload().size() + ? PAYLOAD_SHALLOW_SIZE + transaction.getPayload().size() : 0; } private int computeToMemorySize() { if (transaction.getTo().isPresent()) { - return OPTIONAL_TO_MEMORY_SIZE; + return OPTIONAL_TO_SIZE; } return 0; } private int computeChainIdMemorySize() { if (transaction.getChainId().isPresent()) { - return OPTIONAL_CHAIN_ID_MEMORY_SIZE; + return OPTIONAL_CHAIN_ID_SIZE; } return 0; } @@ -223,11 +224,11 @@ private int computeAccessListEntriesMemorySize() { .getAccessList() .map( al -> { - int totalSize = OPTIONAL_ACCESS_LIST_MEMORY_SIZE; - totalSize += al.size() * ACCESS_LIST_ENTRY_BASE_MEMORY_SIZE; + int totalSize = OPTIONAL_ACCESS_LIST_SHALLOW_SIZE; + totalSize += al.size() * ACCESS_LIST_ENTRY_SHALLOW_SIZE; totalSize += al.stream().map(AccessListEntry::storageKeys).mapToInt(List::size).sum() - * ACCESS_LIST_STORAGE_KEY_MEMORY_SIZE; + * ACCESS_LIST_STORAGE_KEY_SIZE; return totalSize; }) .orElse(0); @@ -238,8 +239,8 @@ private int computeCodeDelegationListMemorySize() { .getCodeDelegationList() .map( cd -> { - int totalSize = OPTIONAL_CODE_DELEGATION_LIST_MEMORY_SIZE; - totalSize += cd.size() * CODE_DELEGATION_ENTRY_MEMORY_SIZE; + int totalSize = OPTIONAL_CODE_DELEGATION_LIST_SHALLOW_SIZE; + totalSize += cd.size() * CODE_DELEGATION_ENTRY_SIZE; return totalSize; }) .orElse(0); @@ -413,4 +414,29 @@ public boolean hasPriority() { } } } + + /** + * The memory size of an object is calculated using the PendingTransactionEstimatedMemorySizeTest + * look there for the details of the calculation and to adapt the code when any of the related + * class changes its structure. + */ + public interface MemorySize { + int FRONTIER_AND_ACCESS_LIST_SHALLOW_SIZE = 904; + int EIP1559_AND_EIP4844_SHALLOW_SIZE = 1016; + int OPTIONAL_TO_SIZE = 112; + int OPTIONAL_CHAIN_ID_SIZE = 80; + int PAYLOAD_SHALLOW_SIZE = 32; + int ACCESS_LIST_STORAGE_KEY_SIZE = 32; + 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 = 432; + int VERSIONED_HASH_SIZE = 96; + int LIST_SHALLOW_SIZE = 48; + int OPTIONAL_SHALLOW_SIZE = 16; + int KZG_COMMITMENT_OR_PROOF_SIZE = 112; + int BLOB_SIZE = 131136; + int BLOBS_WITH_COMMITMENTS_SIZE = 40; + int PENDING_TRANSACTION_SHALLOW_SIZE = 40; + } } 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 c80f5ca1ad4..7c2722937e3 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 @@ -15,6 +15,22 @@ package org.hyperledger.besu.ethereum.eth.transactions; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.ACCESS_LIST_ENTRY_SHALLOW_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.ACCESS_LIST_STORAGE_KEY_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.BLOBS_WITH_COMMITMENTS_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.BLOB_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.CODE_DELEGATION_ENTRY_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.EIP1559_AND_EIP4844_SHALLOW_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.FRONTIER_AND_ACCESS_LIST_SHALLOW_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.KZG_COMMITMENT_OR_PROOF_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.LIST_SHALLOW_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.OPTIONAL_ACCESS_LIST_SHALLOW_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.OPTIONAL_CHAIN_ID_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.OPTIONAL_CODE_DELEGATION_LIST_SHALLOW_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.OPTIONAL_TO_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.PAYLOAD_SHALLOW_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.PENDING_TRANSACTION_SHALLOW_SIZE; +import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.MemorySize.VERSIONED_HASH_SIZE; import org.hyperledger.besu.crypto.SignatureAlgorithm; import org.hyperledger.besu.datatypes.AccessListEntry; @@ -58,16 +74,53 @@ import org.openjdk.jol.info.GraphVisitor; import org.openjdk.jol.info.GraphWalker; -@EnabledOnOs(OS.LINUX) +/** + * This test class has a double utility, first it is used to verify that the current memory size of + * a pending transaction object correspond to the calculated values, so if any of the tests in this + * file fails, it probably means that one of the related classes has changed its format, and a new + * calculation needs to be done. + * + *
The second utility is to help with the calculation of the memory size of a class, using the JOL Tool, to navigate the class layout and collect the + * reported memory sizes. + * + *
For a correct calculation there are some things to consider, first exclude from the + * calculation any reference to a constant object, for example if a field is always a + * reference to {@code Optional.empty()} then just count the reference size, but not the size of the + * Optional since it is always the same instance for every pending transaction. + * + *
To study the layout of a class it is usually useful to create a test method with one or more
+ * instance of it, then programmatically save a heap dump using the {@link #dumpHeap(String,
+ * boolean)} method, then analyze the heap dump with a tool to identify which are the dynamic and
+ * constant fields, then use the JOL Tool to print the class layout and walk in the object tree,
+ * then complete the writing of the test that will verify the current amount of memory used by that
+ * class.
+ */
+@EnabledOnOs({OS.LINUX, OS.MAC})
public class PendingTransactionEstimatedMemorySizeTest extends BaseTransactionPoolTest {
+ /**
+ * Classes that represent constant instances, across all pending transaction types, and are ignored during the calculation
+ */
private static final Set