Skip to content

Commit

Permalink
Estimate the memory size of EIP-7702 transactions
Browse files Browse the repository at this point in the history
Signed-off-by: Fabio Di Fabio <[email protected]>
  • Loading branch information
fab-10 committed Dec 11, 2024
1 parent e2bd137 commit e544e2e
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,10 @@ public Transaction detachedCopy() {
blobsWithCommitments.map(
withCommitments ->
blobsWithCommitmentsDetachedCopy(withCommitments, detachedVersionedHashes.get()));
final Optional<List<CodeDelegation>> detachedCodeDelegationList =
maybeCodeDelegationList.map(
codeDelegations ->
codeDelegations.stream().map(this::codeDelegationDetachedCopy).toList());

final var copiedTx =
new Transaction(
Expand All @@ -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;
Expand All @@ -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<VersionedHash> versionedHashes) {
final var detachedCommitments =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -56,6 +58,8 @@ public class TransactionTestFixture {

private Optional<BlobsWithCommitments> blobs = Optional.empty();
private Optional<BigInteger> v = Optional.empty();
private Optional<List<org.hyperledger.besu.datatypes.CodeDelegation>> codeDelegations =
Optional.empty();

public Transaction createTransaction(final KeyPair keys) {
final Transaction.Builder builder = Transaction.builder();
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -183,4 +193,10 @@ public TransactionTestFixture blobsWithCommitments(final Optional<BlobsWithCommi
this.blobs = blobs;
return this;
}

public TransactionTestFixture codeDelegations(
final List<org.hyperledger.besu.datatypes.CodeDelegation> codeDelegations) {
this.codeDelegations = Optional.of(codeDelegations);
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -181,8 +183,8 @@ private int computeBlobMemorySize() {
+ computeBlobWithCommitmentsMemorySize();
}

private int computeSetCodeMemorySize() {
return 0;
private int computeDelegateCodeMemorySize() {
return computeEIP1559MemorySize() + computeCodeDelegationListMemorySize();
}

private int computeBlobWithCommitmentsMemorySize() {
Expand All @@ -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;
}
Expand Down Expand Up @@ -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<Transaction> toTransactionList(
final Collection<PendingTransaction> transactionsInfo) {
return transactionsInfo.stream().map(PendingTransaction::getTransaction).toList();
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -64,7 +69,14 @@ public class PendingTransactionEstimatedMemorySizeTest extends BaseTransactionPo
private static final Set<String> FRONTIER_ACCESS_LIST_CONSTANT_FIELD_PATHS =
Sets.union(COMMON_CONSTANT_FIELD_PATHS, Set.of(".maxFeePerGas", ".maxPriorityFeePerGas"));
private static final Set<String> VARIABLE_SIZE_PATHS =
Set.of(".chainId", ".to", ".payload", ".maybeAccessList");
Set.of(
".chainId",
".to",
".payload",
".maybeAccessList",
".versionedHashes",
".blobsWithCommitments",
".maybeCodeDelegationList");

@Test
public void toSize() {
Expand Down Expand Up @@ -100,6 +112,7 @@ public void toSize() {
+ ", "
+ gpr.klass().toString()
+ "]");
System.out.println(ClassLayout.parseClass(gpr.klass()).toPrintable());
};

GraphWalker gw = new GraphWalker(gv);
Expand Down Expand Up @@ -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);

Expand All @@ -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<FieldSize> 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<String> 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<FieldSize> 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()
+ ", "
Expand All @@ -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(
Expand Down Expand Up @@ -478,6 +497,7 @@ private long sizeOfField(final Object container, final String... excludePaths) {
+ ", "
+ gpr.klass().toString()
+ "]");
System.out.println(ClassLayout.parseClass(gpr.klass()).toPrintable());
}
};

Expand All @@ -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<FieldSize> {

@Override
Expand Down

0 comments on commit e544e2e

Please sign in to comment.