Skip to content

Commit

Permalink
fix: (0.54) set exchange rates in triggered txn receipts (#15398)
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Tinker <[email protected]>
Signed-off-by: Matt Hess <[email protected]>
Co-authored-by: Matt Hess <[email protected]>
  • Loading branch information
tinker-michaelj and mhess-swl authored Sep 12, 2024
1 parent 5aad759 commit 1c8d441
Show file tree
Hide file tree
Showing 12 changed files with 36 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ default StreamBuilder stateChanges(@NonNull List<StateChange> stateChanges) {
* @param exchangeRate the exchange rate
* @return this builder
*/
StreamBuilder exchangeRate(@NonNull ExchangeRateSet exchangeRate);
StreamBuilder exchangeRate(@Nullable ExchangeRateSet exchangeRate);

/**
* Returns the number of automatic token associations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -563,8 +563,7 @@ public BlockStreamBuilder contractID(@Nullable final ContractID contractID) {

@NonNull
@Override
public BlockStreamBuilder exchangeRate(@NonNull final ExchangeRateSet exchangeRate) {
requireNonNull(exchangeRate);
public BlockStreamBuilder exchangeRate(@Nullable final ExchangeRateSet exchangeRate) {
transactionResultBuilder.exchangeRate(exchangeRate);
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ public StreamBuilder transactionBytes(@NonNull final Bytes transactionBytes) {
}

@Override
public StreamBuilder exchangeRate(@NonNull ExchangeRateSet exchangeRate) {
public StreamBuilder exchangeRate(@Nullable final ExchangeRateSet exchangeRate) {
recordStreamBuilder.exchangeRate(exchangeRate);
blockStreamBuilder.exchangeRate(exchangeRate);
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,8 @@ private HandleOutput execute(@NonNull final UserTxn userTxn) {
dispatchProcessor.processDispatch(dispatch);
updateWorkflowMetrics(userTxn);
}
final var handleOutput = userTxn.stack().buildHandleOutput(userTxn.consensusNow());
final var handleOutput =
userTxn.stack().buildHandleOutput(userTxn.consensusNow(), exchangeRateManager.exchangeRates());
// Note that we don't yet support producing ONLY blocks, because we haven't integrated
// translators from block items to records for answering queries
if (blockStreamConfig.streamRecords()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -852,8 +852,7 @@ public ExchangeRateSet exchangeRate() {
/**{@inheritDoc}*/
@NonNull
@Override
public RecordStreamBuilder exchangeRate(@NonNull final ExchangeRateSet exchangeRate) {
requireNonNull(exchangeRate, "exchangeRate must not be null");
public RecordStreamBuilder exchangeRate(@Nullable final ExchangeRateSet exchangeRate) {
this.exchangeRate = exchangeRate;
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

import com.hedera.hapi.block.stream.BlockItem;
import com.hedera.hapi.node.base.TransactionID;
import com.hedera.hapi.node.transaction.ExchangeRateSet;
import com.hedera.node.app.blocks.impl.BoundaryStateChangeListener;
import com.hedera.node.app.blocks.impl.KVStateChangeListener;
import com.hedera.node.app.blocks.impl.PairedStreamBuilder;
Expand Down Expand Up @@ -59,16 +60,13 @@
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
* A stack of savepoints scoped to a dispatch. Each savepoint captures the state of the {@link State} at the time
* the savepoint was created and all the changes made to the state from the time savepoint was created, along with all
* the stream builders created in the savepoint.
*/
public class SavepointStackImpl implements HandleContext.SavepointStack, State {
private static final Logger log = LogManager.getLogger(SavepointStackImpl.class);
private final State state;
private final Deque<Savepoint> stack = new ArrayDeque<>();
private final Map<String, WritableStatesStack> writableStatesMap = new HashMap<>();
Expand Down Expand Up @@ -444,9 +442,11 @@ Savepoint peek() {
* Builds all the records for the user transaction.
*
* @param consensusTime consensus time of the transaction
* @param exchangeRates the active exchange rates
* @return the stream of records
*/
public HandleOutput buildHandleOutput(@NonNull final Instant consensusTime) {
public HandleOutput buildHandleOutput(
@NonNull final Instant consensusTime, @NonNull final ExchangeRateSet exchangeRates) {
final List<BlockItem> blockItems;
Instant lastAssignedConsenusTime = consensusTime;
if (streamMode == RECORDS) {
Expand Down Expand Up @@ -481,8 +481,16 @@ public HandleOutput buildHandleOutput(@NonNull final Instant consensusTime) {
final var consensusNow = consensusTime.plusNanos((long) i - indexOfUserRecord);
lastAssignedConsenusTime = consensusNow;
builder.consensusTimestamp(consensusNow);
if (i > indexOfUserRecord && builder.category() != SCHEDULED) {
builder.parentConsensus(consensusTime);
if (i > indexOfUserRecord) {
if (builder.category() != SCHEDULED) {
// Only set exchange rates on transactions preceding the user transaction, since
// no subsequent child can change the exchange rate
builder.parentConsensus(consensusTime).exchangeRate(null);
} else {
// But for backward compatibility keep setting rates on scheduled receipts, c.f.
// https://github.com/hashgraph/hedera-services/issues/15393
builder.exchangeRate(exchangeRates);
}
}
switch (streamMode) {
case RECORDS -> records.add(((RecordStreamBuilder) builder).build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.hedera.node.app.authorization.AuthorizerInjectionModule;
import com.hedera.node.app.config.BootstrapConfigProviderImpl;
import com.hedera.node.app.config.ConfigProviderImpl;
import com.hedera.node.app.fees.ExchangeRateManager;
import com.hedera.node.app.service.contract.impl.ContractServiceImpl;
import com.hedera.node.app.service.file.impl.FileServiceImpl;
import com.hedera.node.app.services.ServicesInjectionModule;
Expand Down Expand Up @@ -81,5 +82,7 @@ interface Builder {

StateNetworkInfo stateNetworkInfo();

ExchangeRateManager exchangeRateManager();

StandaloneDispatchFactory standaloneDispatchFactory();
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,14 @@ public TransactionExecutor newExecutor(@NonNull final State state, @NonNull fina
final var executor = newExecutorComponent(properties);
executor.initializer().accept(state);
executor.stateNetworkInfo().initFrom(state);
final var exchangeRateManager = executor.exchangeRateManager();
return (transactionBody, consensusNow, operationTracers) -> {
final var dispatch = executor.standaloneDispatchFactory().newDispatch(state, transactionBody, consensusNow);
OPERATION_TRACERS.set(List.of(operationTracers));
executor.dispatchProcessor().processDispatch(dispatch);
return dispatch.stack().buildHandleOutput(consensusNow).recordsOrThrow();
return dispatch.stack()
.buildHandleOutput(consensusNow, exchangeRateManager.exchangeRates())
.recordsOrThrow();
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public StreamBuilder transactionBytes(@NonNull Bytes transactionBytes) {
}

@Override
public StreamBuilder exchangeRate(@NonNull ExchangeRateSet exchangeRate) {
public StreamBuilder exchangeRate(@Nullable ExchangeRateSet exchangeRate) {
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ public StreamBuilder transactionBytes(@NonNull Bytes transactionBytes) {
}

@Override
public StreamBuilder exchangeRate(@NonNull ExchangeRateSet exchangeRate) {
public StreamBuilder exchangeRate(@Nullable ExchangeRateSet exchangeRate) {
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,9 @@ public SingleTransactionRecord recordFrom(@NonNull final BlockTransactionParts p
if (followsUserRecord) {
recordBuilder.parentConsensusTimestamp(asTimestamp(userTimestamp));
}
if (!followsUserRecord || AUTO_CREATION_MEMOS.contains(parts.memo())) {
if (!followsUserRecord
|| AUTO_CREATION_MEMOS.contains(parts.memo())
|| parts.transactionIdOrThrow().scheduled()) {
// Only preceding and user transactions get exchange rates in their receipts; note that
// auto-account creations are always preceding dispatches and so get exchange rates
receiptBuilder.exchangeRate(activeRates);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import static java.util.Objects.requireNonNull;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.esaulpaugh.headlong.abi.Function;
import com.hedera.node.app.spi.workflows.HandleContext.TransactionCategory;
Expand Down Expand Up @@ -455,6 +456,9 @@ private static void assertParentChildStructure(
withNonce(userTransactionID, nextExpectedNonce++ - postTriggeredOffset), following.txnId());
assertEquals(userConsensusTime, following.parentConsensusTimestamp());
}
if (following.txnId().getScheduled()) {
assertTrue(following.txnRecord().getReceipt().hasExchangeRate());
}
}
}

Expand Down

0 comments on commit 1c8d441

Please sign in to comment.