Skip to content

Commit

Permalink
process the authority list before increasing the sender nonce, make s…
Browse files Browse the repository at this point in the history
…ure that the updated balance of the sender is calculated correctly if they sign an authorization as well

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>
  • Loading branch information
daniellehrner committed Jul 29, 2024
1 parent 94b497e commit e3feff8
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
Expand All @@ -51,10 +52,10 @@ public class SetCodeTransactionAcceptanceTest extends AcceptanceTestBase {
public static final Bytes AUTHORIZER_PRIVATE_KEY =
Bytes.fromHexString("11f2e7b6a734ab03fa682450e0d4681d18a944f8b83c99bf7b9b4de6c0f35ea1");

private final Account transactionSponsor =
private final Account otherAccount =
accounts.createAccount(
Address.fromHexStringStrict("a05b21E5186Ce93d2a226722b85D6e550Ac7D6E3"));
public static final Bytes TRANSACTION_SPONSOR_PRIVATE_KEY =
public static final Bytes OTHER_ACCOUNT_PRIVATE_KEY =
Bytes.fromHexString("3a4ff6d22d7502ef2452368165422861c01a0f72f851793b372b87888dc3c453");

private BesuNode besuNode;
Expand All @@ -68,12 +69,18 @@ void setUp() throws IOException {
testHelper = new PragueAcceptanceTestHelper(besuNode, ethTransactions);
}

@AfterEach
void tearDown() {
besuNode.close();
cluster.close();
}

/**
* At the beginning of the test both the authorizer and the transaction sponsor have a balance of
* 90000 ETH. The authorizer creates an authorization for a contract that send all its ETH to any
* given address. The transaction sponsor created a 7702 transaction with it and sends all the ETH
* from the authorizer to itself. The authorizer balance should be 0 and the transaction sponsor
* balance should be 180000 ETH minus the transaction costs.
* At the beginning of the test both the authorizer and the other account have a balance of 90000
* ETH. The authorizer creates an authorization for a contract that send all its ETH to any given
* address. The other account sponsors the 7702 transaction and sends all the ETH from the
* authorizer to itself. The authorizer balance should be 0 and the transaction sponsor balance
* should be 180000 ETH minus the transaction costs.
*/
@Test
public void shouldTransferAllEthOfAuthorizerToSponsor() throws IOException {
Expand All @@ -97,13 +104,12 @@ public void shouldTransferAllEthOfAuthorizerToSponsor() throws IOException {
.gasLimit(1000000)
.to(Address.fromHexStringStrict(authorizer.getAddress()))
.value(Wei.ZERO)
.payload(Bytes32.leftPad(Bytes.fromHexString(transactionSponsor.getAddress())))
.payload(Bytes32.leftPad(Bytes.fromHexString(otherAccount.getAddress())))
.accessList(List.of())
.setCodeTransactionPayloads(List.of(authorization))
.signAndBuild(
secp256k1.createKeyPair(
secp256k1.createPrivateKey(
TRANSACTION_SPONSOR_PRIVATE_KEY.toUnsignedBigInteger())));
secp256k1.createPrivateKey(OTHER_ACCOUNT_PRIVATE_KEY.toUnsignedBigInteger())));

final String txHash =
besuNode.execute(ethTransactions.sendRawTransaction(tx.encoded().toHexString()));
Expand All @@ -120,6 +126,65 @@ public void shouldTransferAllEthOfAuthorizerToSponsor() throws IOException {
final BigInteger txCost =
maybeTransactionReceipt.get().getGasUsed().multiply(new BigInteger(gasPriceWithout0x, 16));
BigInteger expectedSponsorBalance = new BigInteger("180000000000000000000000").subtract(txCost);
cluster.verify(transactionSponsor.balanceEquals(Amount.wei(expectedSponsorBalance)));
cluster.verify(otherAccount.balanceEquals(Amount.wei(expectedSponsorBalance)));
}

/**
* The authorizer creates an authorization for a contract that send all its ETH to any given
* address. But the nonce is 1 and the authorization list is processed before the nonce increase
* of the sender. Therefore, the authorization should be invalid and will be ignored. No balance
* change, except for a decrease for paying the transaction cost should occur.
*/
@Test
public void shouldCheckNonceBeforeNonceIncreaseOfSender() throws IOException {

cluster.verify(authorizer.balanceEquals(Amount.ether(90000)));

final org.hyperledger.besu.datatypes.SetCodeAuthorization authorization =
SetCodeAuthorization.builder()
.chainId(BigInteger.valueOf(20211))
.nonces(
Optional.of(
1L)) // nonce is 1, but because it is validated before the nonce increase, it
// should be 0
.address(SEND_ALL_ETH_CONTRACT_ADDRESS)
.signAndBuild(
secp256k1.createKeyPair(
secp256k1.createPrivateKey(AUTHORIZER_PRIVATE_KEY.toUnsignedBigInteger())));

final Transaction tx =
Transaction.builder()
.type(TransactionType.SET_CODE)
.chainId(BigInteger.valueOf(20211))
.nonce(0)
.maxPriorityFeePerGas(Wei.of(1000000000))
.maxFeePerGas(Wei.fromHexString("0x02540BE400"))
.gasLimit(1000000)
.to(Address.fromHexStringStrict(authorizer.getAddress()))
.value(Wei.ZERO)
.payload(Bytes32.leftPad(Bytes.fromHexString(otherAccount.getAddress())))
.accessList(List.of())
.setCodeTransactionPayloads(List.of(authorization))
.signAndBuild(
secp256k1.createKeyPair(
secp256k1.createPrivateKey(AUTHORIZER_PRIVATE_KEY.toUnsignedBigInteger())));

final String txHash =
besuNode.execute(ethTransactions.sendRawTransaction(tx.encoded().toHexString()));
testHelper.buildNewBlock();

Optional<TransactionReceipt> maybeTransactionReceipt =
besuNode.execute(ethTransactions.getTransactionReceipt(txHash));
assertThat(maybeTransactionReceipt).isPresent();

// verify that the balance of the other account has not changed
cluster.verify(otherAccount.balanceEquals(Amount.ether(90000)));

final String gasPriceWithout0x =
maybeTransactionReceipt.get().getEffectiveGasPrice().substring(2);
final BigInteger txCost =
maybeTransactionReceipt.get().getGasUsed().multiply(new BigInteger(gasPriceWithout0x, 16));
BigInteger expectedSponsorBalance = new BigInteger("90000000000000000000000").subtract(txCost);
cluster.verify(authorizer.balanceEquals(Amount.wei(expectedSponsorBalance)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public AuthorityProcessor(final Optional<BigInteger> maybeChainId) {
}

public void addContractToAuthority(
final WorldUpdater worldUpdater,
final WorldUpdater worldState,
final AuthorizedCodeService authorizedCodeService,
final Transaction transaction) {

Expand All @@ -60,7 +60,7 @@ public void addContractToAuthority(
}

final Optional<MutableAccount> maybeAccount =
Optional.ofNullable(worldUpdater.getAccount(authorityAddress));
Optional.ofNullable(worldState.getAccount(authorityAddress));
final long accountNonce =
maybeAccount.map(AccountState::getNonce).orElse(0L);

Expand All @@ -74,7 +74,7 @@ public void addContractToAuthority(
}

Optional<Account> codeAccount =
Optional.ofNullable(worldUpdater.get(payload.address()));
Optional.ofNullable(worldState.get(payload.address()));
final Bytes code;
if (codeAccount.isPresent()) {
code = codeAccount.get().getCode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,6 @@ public TransactionProcessingResult processTransaction(
}

final Address senderAddress = transaction.getSender();

final MutableAccount sender = worldState.getOrCreateSenderAccount(senderAddress);

validationResult =
Expand All @@ -318,6 +317,19 @@ public TransactionProcessingResult processTransaction(

operationTracer.tracePrepareTransaction(worldState, transaction);

final Set<Address> addressList = new BytesTrieSet<>(Address.SIZE);

if (transaction.getAuthorizationList().isPresent()) {
if (maybeAuthorityProcessor.isEmpty()) {
throw new RuntimeException("Authority processor is required for 7702 transactions");
}

maybeAuthorityProcessor
.get()
.addContractToAuthority(worldState, authorizedCodeService, transaction);
addressList.addAll(authorizedCodeService.getAuthorities());
}

final long previousNonce = sender.incrementNonce();
LOG.trace(
"Incremented sender {} nonce ({} -> {})",
Expand All @@ -343,7 +355,6 @@ public TransactionProcessingResult processTransaction(
final List<AccessListEntry> accessListEntries = transaction.getAccessList().orElse(List.of());
// we need to keep a separate hash set of addresses in case they specify no storage.
// No-storage is a common pattern, especially for Externally Owned Accounts
final Set<Address> addressList = new BytesTrieSet<>(Address.SIZE);
final Multimap<Address, Bytes32> storageList = HashMultimap.create();
int accessListStorageCount = 0;
for (final var entry : accessListEntries) {
Expand Down Expand Up @@ -408,15 +419,6 @@ public TransactionProcessingResult processTransaction(
if (transaction.getVersionedHashes().isPresent()) {
commonMessageFrameBuilder.versionedHashes(
Optional.of(transaction.getVersionedHashes().get().stream().toList()));
} else if (transaction.getAuthorizationList().isPresent()) {
if (maybeAuthorityProcessor.isEmpty()) {
throw new RuntimeException("Authority processor is required for 7702 transactions");
}

maybeAuthorityProcessor
.get()
.addContractToAuthority(worldUpdater, authorizedCodeService, transaction);
addressList.addAll(authorizedCodeService.getAuthorities());
} else {
commonMessageFrameBuilder.versionedHashes(Optional.empty());
}
Expand Down

0 comments on commit e3feff8

Please sign in to comment.