Skip to content

Commit

Permalink
Fix records for CryptoTransferSuite (#9771)
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Tinker <michael.tinker@swirldslabs.com>
Signed-off-by: Neeharika-Sompalli <neeharika.sompalli@swirldslabs.com>
Co-authored-by: Michael Tinker <michael.tinker@swirldslabs.com>
  • Loading branch information
Neeharika-Sompalli and tinker-michaelj authored Nov 14, 2023
1 parent 9bf77c0 commit 56449f0
Show file tree
Hide file tree
Showing 30 changed files with 378 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ default Transaction simpleCryptoTransfer() {
return simpleCryptoTransfer(TransactionID.newBuilder().build());
}

default Transaction simpleCryptoTransferWithNonce(final TransactionID txnId, final int nonce) {
return simpleCryptoTransfer(TransactionID.newBuilder()
.accountID(txnId.accountID())
.transactionValidStart(txnId.transactionValidStart())
.nonce(nonce)
.build());
}

default Transaction simpleCryptoTransfer(@NonNull final TransactionID transactionID) {
final var cryptoTransferTx = CryptoTransferTransactionBody.newBuilder().build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,9 @@ private SystemPrivilege checkFileChange(@NonNull final AccountID accountID, fina

private SystemPrivilege checkCryptoUpdate(
@NonNull final AccountID payerId, @NonNull final CryptoUpdateTransactionBody op) {
final var targetId = op.accountIDToUpdateOrThrow();
final long targetNum = targetId.accountNumOrThrow();
// while dispatching hollow account finalization transaction body, the accountId is set to DEFAULT
final var targetId = op.accountIDToUpdateOrElse(AccountID.DEFAULT);
final long targetNum = targetId.accountNumOrElse(0L);
final var treasury = accountsConfig.treasury();
final var payerNum = payerId.accountNumOrThrow();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,15 @@ private static <T> T castRecordBuilder(
.nanos(consensusNow().getNano())))
.build();
}
try {
// If the payer is authorized to waive fees, then we can skip the fee calculation.
if (authorizer.hasWaivedFees(syntheticPayerId, functionOf(txBody), bodyToDispatch)) {
return Fees.FREE;
}
} catch (UnknownHederaFunctionality ex) {
throw new HandleException(ResponseCodeEnum.INVALID_TRANSACTION_BODY);
}

return dispatcher.dispatchComputeFees(
new ChildFeeContextImpl(feeManager, this, bodyToDispatch, syntheticPayerId));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ private void handleUserTransaction(
platformEvent.getCreatorId().id());

networkUtilizationManager.resetFrom(stack);
final var hasWaivedFees = authorizer.hasWaivedFees(payer, transactionInfo.functionality(), txBody);

if (validationResult.status() != SO_FAR_SO_GOOD) {
final var sigVerificationFailed = validationResult.responseCodeEnum() == INVALID_SIGNATURE;
Expand All @@ -399,17 +400,19 @@ private void handleUserTransaction(
networkUtilizationManager.trackFeePayments(payer, consensusNow, stack);
}
recordBuilder.status(validationResult.responseCodeEnum());

try {
if (validationResult.status() == NODE_DUE_DILIGENCE_FAILURE) {
feeAccumulator.chargeNetworkFee(creator.accountId(), fees.networkFee());
} else if (validationResult.status() == PAYER_UNWILLING_OR_UNABLE_TO_PAY_SERVICE_FEE) {
// We do not charge partial service fees; if the payer is unwilling or unable to cover
// the entire service fee, then we only charge network and node fees (prioritizing
// the network fee in case of a very low payer balance)
feeAccumulator.chargeFees(payer, creator.accountId(), fees.withoutServiceComponent());
} else {
feeAccumulator.chargeFees(payer, creator.accountId(), fees);
// If the payer is authorized to waive fees, then we don't charge them
if (!hasWaivedFees) {
if (validationResult.status() == NODE_DUE_DILIGENCE_FAILURE) {
feeAccumulator.chargeNetworkFee(creator.accountId(), fees.networkFee());
} else if (validationResult.status() == PAYER_UNWILLING_OR_UNABLE_TO_PAY_SERVICE_FEE) {
// We do not charge partial service fees; if the payer is unwilling or unable to cover
// the entire service fee, then we only charge network and node fees (prioritizing
// the network fee in case of a very low payer balance)
feeAccumulator.chargeFees(payer, creator.accountId(), fees.withoutServiceComponent());
} else {
feeAccumulator.chargeFees(payer, creator.accountId(), fees);
}
}
} catch (final HandleException ex) {
final var identifier = validationResult.status == NODE_DUE_DILIGENCE_FAILURE
Expand All @@ -429,7 +432,8 @@ private void handleUserTransaction(
finalizeHollowAccounts(context, configuration, preHandleResult.hollowAccounts(), verifier);

networkUtilizationManager.trackTxn(transactionInfo, consensusNow, stack);
if (!authorizer.hasWaivedFees(payer, transactionInfo.functionality(), txBody)) {
// If the payer is authorized to waive fees, then we don't charge them
if (!hasWaivedFees) {
// privileged transactions are not charged fees
feeAccumulator.chargeFees(payer, creator.accountId(), fees);
}
Expand All @@ -450,7 +454,8 @@ private void handleUserTransaction(
final var childFees = recordListBuilder.precedingRecordBuilders().stream()
.mapToLong(SingleTransactionRecordBuilderImpl::transactionFee)
.sum();
if (!feeAccumulator.chargeNetworkFee(payer, childFees)) {
// If the payer is authorized to waive fees, then we don't charge them
if (!hasWaivedFees && !feeAccumulator.chargeNetworkFee(payer, childFees)) {
throw new HandleException(INSUFFICIENT_PAYER_BALANCE);
}
}
Expand Down Expand Up @@ -478,7 +483,9 @@ private void handleUserTransaction(

} catch (final HandleException e) {
rollback(e.getStatus(), stack, recordListBuilder);
feeAccumulator.chargeFees(payer, creator.accountId(), fees);
if (!hasWaivedFees) {
feeAccumulator.chargeFees(payer, creator.accountId(), fees);
}
}
}
} catch (final Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,10 @@ public SingleTransactionRecordBuilderImpl doAddPreceding(
// user transaction. The second item is T-2, and so on.
final var parentConsensusTimestamp = userTxnRecordBuilder.consensusNow();
final var consensusNow = parentConsensusTimestamp.minusNanos(precedingCount + 1L);
final var recordBuilder = new SingleTransactionRecordBuilderImpl(consensusNow, reversingBehavior)
.exchangeRate(userTxnRecordBuilder.exchangeRate());
// FUTURE : For some reason, we do not set the exchange rate for preceding transactions in mono-service.
// Should be corrected after differential testing.
final var recordBuilder = new SingleTransactionRecordBuilderImpl(consensusNow, reversingBehavior);
// .exchangeRate(userTxnRecordBuilder.exchangeRate());
precedingTxnRecordBuilders.add(recordBuilder);
return recordBuilder;
}
Expand Down Expand Up @@ -375,8 +377,10 @@ public Result build() {
int count = precedingTxnRecordBuilders == null ? 0 : precedingTxnRecordBuilders.size();
for (int i = count - 1; i >= 0; i--) {
final var recordBuilder = precedingTxnRecordBuilders.get(i);
records.add(
recordBuilder.transactionID(idBuilder.nonce(i + 1).build()).build());
records.add(recordBuilder
.transactionID(idBuilder.nonce(i + 1).build())
.syncBodyIdFromRecordId()
.build());
}

records.add(userTxnRecord);
Expand All @@ -387,6 +391,7 @@ public Result build() {
final var recordBuilder = childRecordBuilders.get(i);
records.add(recordBuilder
.transactionID(idBuilder.nonce(nextNonce++).build())
.syncBodyIdFromRecordId()
.build());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
import com.hedera.hapi.node.contract.ContractFunctionResult;
import com.hedera.hapi.node.transaction.AssessedCustomFee;
import com.hedera.hapi.node.transaction.ExchangeRateSet;
import com.hedera.hapi.node.transaction.SignedTransaction;
import com.hedera.hapi.node.transaction.TransactionBody;
import com.hedera.hapi.node.transaction.TransactionReceipt;
import com.hedera.hapi.node.transaction.TransactionRecord;
import com.hedera.hapi.streams.ContractActions;
Expand All @@ -59,6 +61,7 @@
import com.hedera.node.app.service.token.records.CryptoTransferRecordBuilder;
import com.hedera.node.app.service.token.records.GenesisAccountRecordBuilder;
import com.hedera.node.app.service.token.records.NodeStakeUpdateRecordBuilder;
import com.hedera.node.app.service.token.records.TokenAccountWipeRecordBuilder;
import com.hedera.node.app.service.token.records.TokenBurnRecordBuilder;
import com.hedera.node.app.service.token.records.TokenCreateRecordBuilder;
import com.hedera.node.app.service.token.records.TokenMintRecordBuilder;
Expand All @@ -78,6 +81,7 @@
import java.time.Instant;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
Expand Down Expand Up @@ -119,8 +123,11 @@ public class SingleTransactionRecordBuilderImpl
FeeRecordBuilder,
ContractDeleteRecordBuilder,
GenesisAccountRecordBuilder,
GasFeeRecordBuilder {

GasFeeRecordBuilder,
TokenAccountWipeRecordBuilder {
private static final Comparator<TokenAssociation> TOKEN_ASSOCIATION_COMPARATOR =
Comparator.<TokenAssociation>comparingLong(a -> a.tokenId().tokenNum())
.thenComparingLong(a -> a.accountIdOrThrow().accountNum());
// base transaction data
private Transaction transaction;
private Bytes transactionBytes = Bytes.EMPTY;
Expand Down Expand Up @@ -228,11 +235,16 @@ public SingleTransactionRecordBuilderImpl(
* @return the transaction record
*/
public SingleTransactionRecord build() {
transaction = customizer.apply(transaction);
final var transactionReceipt = transactionReceiptBuilder
.exchangeRate(exchangeRate)
.serialNumbers(serialNumbers)
.build();
if (customizer != null) {
transaction = customizer.apply(transaction);
}
final var builder = transactionReceiptBuilder.serialNumbers(serialNumbers);
// FUTURE : In mono-service exchange rate is not set in preceding child records.
// This should be changed after differential testing
if (exchangeRate != null && exchangeRate.hasCurrentRate() && exchangeRate.hasNextRate()) {
builder.exchangeRate(exchangeRate);
}
final var transactionReceipt = builder.build();

final Bytes transactionHash;
try {
Expand All @@ -246,6 +258,12 @@ public SingleTransactionRecord build() {
final Timestamp parentConsensusTimestamp =
parentConsensus != null ? HapiUtils.asTimestamp(parentConsensus) : null;

// sort the automatic associations to match the order of mono-service records
final var newAutomaticTokenAssociations = new ArrayList<>(automaticTokenAssociations);
if (!automaticTokenAssociations.isEmpty()) {
newAutomaticTokenAssociations.sort(TOKEN_ASSOCIATION_COMPARATOR);
}

final var transactionRecord = transactionRecordBuilder
.transactionID(transactionID)
.receipt(transactionReceipt)
Expand All @@ -255,7 +273,7 @@ public SingleTransactionRecord build() {
.transferList(transferList)
.tokenTransferLists(tokenTransferLists)
.assessedCustomFees(assessedCustomFees)
.automaticTokenAssociations(automaticTokenAssociations)
.automaticTokenAssociations(newAutomaticTokenAssociations)
.paidStakingRewards(paidStakingRewards)
.build();

Expand Down Expand Up @@ -344,6 +362,35 @@ public SingleTransactionRecordBuilderImpl transactionID(@NonNull final Transacti
return this;
}

/**
* When we update nonce on the record, we need to update the body as well with the same transactionID.
* @return the builder
*/
@NonNull
public SingleTransactionRecordBuilderImpl syncBodyIdFromRecordId() {
final var newTransactionID = transactionID;
try {
final var signedTransaction = SignedTransaction.PROTOBUF.parseStrict(
transaction.signedTransactionBytes().toReadableSequentialData());
final var existingTransactionBody =
TransactionBody.PROTOBUF.parse(signedTransaction.bodyBytes().toReadableSequentialData());
final var body = existingTransactionBody
.copyBuilder()
.transactionID(newTransactionID)
.build();
final var newBodyBytes = TransactionBody.PROTOBUF.toBytes(body);
final var newSignedTransaction =
SignedTransaction.newBuilder().bodyBytes(newBodyBytes).build();
final var signedTransactionBytes = SignedTransaction.PROTOBUF.toBytes(newSignedTransaction);
this.transaction = Transaction.newBuilder()
.signedTransactionBytes(signedTransactionBytes)
.build();
return this;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* Sets the memo.
*
Expand Down
Loading

0 comments on commit 56449f0

Please sign in to comment.