Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

7702 bugfixes for devnet-1 #7394

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading