Skip to content

fix: Fix signer nonce translation for batch ethCalls #20315

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

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
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 @@ -393,10 +393,7 @@ public void addSignerNonce(
@NonNull final ContractFunctionResult.Builder derivedBuilder,
@NonNull final List<StateChange> remainingStateChanges) {
if (senderId != null) {
findAccount(senderId, remainingStateChanges)
.ifPresentOrElse(
senderAccount -> derivedBuilder.signerNonce(senderAccount.ethereumNonce()),
() -> derivedBuilder.signerNonce(nonces.get(senderId.accountNumOrElse(Long.MIN_VALUE))));
derivedBuilder.signerNonce(getSignerNonce(senderId, remainingStateChanges));
}
}

Expand Down Expand Up @@ -984,4 +981,24 @@ private boolean allMintsAreFound() {
}
return true;
}

public boolean isNonceIncremented(
final AccountID accountID, final Long nonce, List<StateChange> remainingStateChanges) {
final var currentNonce = getSignerNonce(accountID, remainingStateChanges);
return currentNonce != null && currentNonce > nonce;
}

/**
* Retrieves the Ethereum nonce for a given account.
*
* @param senderId the account ID to get the nonce for
* @param remainingStateChanges the state changes to search for the account
* @return the Ethereum nonce for the account
*/
public Long getSignerNonce(
@NonNull final AccountID senderId, @NonNull final List<StateChange> remainingStateChanges) {
return findAccount(senderId, remainingStateChanges)
.map(Account::ethereumNonce)
.orElseGet(() -> nonces.get(senderId.accountNumOrElse(Long.MIN_VALUE)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.hedera.hapi.block.stream.output.StateChange;
import com.hedera.hapi.block.stream.output.TransactionOutput;
import com.hedera.hapi.block.stream.trace.TraceData;
import com.hedera.hapi.node.base.AccountID;
import com.hedera.hapi.node.contract.ContractFunctionResult;
import com.hedera.node.app.hapi.utils.ethereum.EthTxData;
import com.hedera.node.app.state.SingleTransactionRecord;
Expand Down Expand Up @@ -81,10 +82,19 @@ public SingleTransactionRecord translate(
derivedBuilder, remainingStateChanges);
}
if (!PRE_NONCE_ERROR_MESSAGE.equals(txCallResult.errorMessage())) {
baseTranslator.addSignerNonce(
txCallResult.senderId(),
derivedBuilder,
remainingStateChanges);
if (parts.isBatchScoped() && ethTxData != null) {
handleBatchScopedNonce(
txCallResult.senderId(),
ethTxData.nonce(),
derivedBuilder,
baseTranslator,
remainingStateChanges);
} else {
baseTranslator.addSignerNonce(
txCallResult.senderId(),
derivedBuilder,
remainingStateChanges);
}
}
final var fnResult = derivedBuilder.build();
recordBuilder.contractCallResult(fnResult);
Expand Down Expand Up @@ -151,4 +161,19 @@ public SingleTransactionRecord translate(
remainingStateChanges,
followingUnitTraces);
}

private void handleBatchScopedNonce(
AccountID senderId,
long currentNonce,
ContractFunctionResult.Builder builder,
BaseTranslator baseTranslator,
List<StateChange> remainingStateChanges) {
if (baseTranslator.isNonceIncremented(senderId, currentNonce, remainingStateChanges)) {
// If we have multiple ethereum transactions in a batch, and they increment the same nonce,
// we have to derive the nonce from the input for the transactions that are in the middle of the batch.
builder.signerNonce(currentNonce + 1L);
} else {
baseTranslator.addSignerNonce(senderId, builder, remainingStateChanges);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
* @param outputs the output of processing the transaction
* @param isTopLevel whether the transaction is a top-level transaction in its unit
* @param hasEnrichedLegacyRecord whether the transaction has an enriched legacy record
* @param isBatchScoped where is part of atomic batch (inner transactions and their children)
* @param isBatchScoped whether is part of atomic batch (inner transactions and their children)
*/
public record BlockTransactionParts(
@Nullable TransactionParts transactionParts,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1026,7 +1026,6 @@ final Stream<DynamicTest> transferTokensViaEip2930TxSuccessfully() {

@HapiTest
final Stream<DynamicTest> accountDeletionResetsTheAliasNonce() {

final AtomicReference<AccountID> partyId = new AtomicReference<>();
final AtomicReference<byte[]> partyAlias = new AtomicReference<>();
final AtomicReference<byte[]> counterAlias = new AtomicReference<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
import org.hiero.base.utility.CommonUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Order;

Expand Down Expand Up @@ -720,8 +721,8 @@ final Stream<DynamicTest> temporarySStoreRefundTest() {
}));
}

// @LeakyHapiTest(overrides = {"contracts.evm.version"}) This will be fixed in
// https://github.com/hiero-ledger/hiero-consensus-node/issues/19993
@Disabled
@LeakyHapiTest(overrides = {"contracts.evm.version"})
final Stream<DynamicTest> evmLazyCreateViaSolidityCall() {
final var LAZY_CREATE_CONTRACT = "NestedLazyCreateContract";
final var ECDSA_KEY = "ECDSAKey";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
import org.apache.tuweni.bytes.Bytes32;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Tag;
Expand All @@ -65,7 +64,6 @@
@Tag(SMART_CONTRACT)
@HapiTestLifecycle
@DisplayName("Records Suite")
@Disabled
public class AtomicRecordsSuite {

public static final String LOG_NOW = "logNow";
Expand Down Expand Up @@ -197,15 +195,15 @@ final Stream<DynamicTest> blck003ReturnsTimestampOfTheBlock() {
final var firstCallLogs =
firstCallRecord.getContractCallResult().getLogInfoList();
final var firstCallTimeLogData =
firstCallLogs.get(0).getData().toByteArray();
firstCallLogs.getFirst().getData().toByteArray();
final var firstCallTimestamp =
Longs.fromByteArray(Arrays.copyOfRange(firstCallTimeLogData, 24, 32));

final var secondCallRecord = recordOp.getResponseRecord();
final var secondCallLogs =
secondCallRecord.getContractCallResult().getLogInfoList();
final var secondCallTimeLogData =
secondCallLogs.get(0).getData().toByteArray();
secondCallLogs.getFirst().getData().toByteArray();
final var secondCallTimestamp =
Longs.fromByteArray(Arrays.copyOfRange(secondCallTimeLogData, 24, 32));

Expand Down
Loading