Skip to content

Commit 5a9670a

Browse files
xin-hederabilyana-gospodinova
authored andcommitted
HIP-1056 Link scheduled transactions and its trigger (#12123)
- Link short-term scheduled transactions and its triggering transaction - Delegate to the tigger's StateChangeContext when available Signed-off-by: Xin Li <[email protected]>
1 parent adf5384 commit 5a9670a

File tree

6 files changed

+342
-108
lines changed

6 files changed

+342
-108
lines changed

common/src/main/java/org/hiero/mirror/common/domain/transaction/BlockTransaction.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ public class BlockTransaction implements StreamItem {
7777
private final SignedTransaction signedTransaction;
7878
private final byte[] signedTransactionBytes;
7979
private final TopicID topicId; // for consensus submit message transaction
80+
private final BlockTransaction trigger;
8081

8182
@Getter(AccessLevel.NONE)
8283
private final AtomicReference<TopicMessage> topicMessage = new AtomicReference<>();
@@ -106,7 +107,8 @@ public BlockTransaction(
106107
List<TraceData> traceData,
107108
TransactionBody transactionBody,
108109
TransactionResult transactionResult,
109-
Map<TransactionCase, TransactionOutput> transactionOutputs) {
110+
Map<TransactionCase, TransactionOutput> transactionOutputs,
111+
BlockTransaction trigger) {
110112
this.previous = previous;
111113
this.signedTransaction = signedTransaction;
112114
this.signedTransactionBytes = signedTransactionBytes;
@@ -115,6 +117,7 @@ public BlockTransaction(
115117
this.transactionBody = transactionBody;
116118
this.transactionResult = transactionResult;
117119
this.transactionOutputs = transactionOutputs;
120+
this.trigger = trigger;
118121

119122
consensusTimestamp = DomainUtils.timestampInNanosMax(transactionResult.getConsensusTimestamp());
120123
parentConsensusTimestamp = transactionResult.hasParentConsensusTimestamp()
@@ -209,6 +212,10 @@ private StateChangeContext createStateChangeContext() {
209212
return parent.getStateChangeContext();
210213
}
211214

215+
if (trigger != null) {
216+
return trigger.getStateChangeContext();
217+
}
218+
212219
return !CollectionUtils.isEmpty(stateChanges)
213220
? new StateChangeContext(stateChanges)
214221
: StateChangeContext.EMPTY_CONTEXT;

importer/src/main/java/org/hiero/mirror/importer/reader/block/BlockStreamReaderImpl.java

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,27 @@
1414
import static com.hedera.hapi.block.stream.protoc.BlockItem.ItemCase.TRANSACTION_RESULT;
1515

1616
import com.google.protobuf.InvalidProtocolBufferException;
17+
import com.hedera.hapi.block.stream.output.protoc.CreateScheduleOutput;
18+
import com.hedera.hapi.block.stream.output.protoc.SignScheduleOutput;
1719
import com.hedera.hapi.block.stream.output.protoc.StateChanges;
1820
import com.hedera.hapi.block.stream.output.protoc.TransactionOutput;
21+
import com.hedera.hapi.block.stream.output.protoc.TransactionOutput.TransactionCase;
1922
import com.hedera.hapi.block.stream.protoc.BlockItem;
2023
import com.hedera.hapi.block.stream.trace.protoc.TraceData;
2124
import com.hederahashgraph.api.proto.java.AtomicBatchTransactionBody;
2225
import com.hederahashgraph.api.proto.java.BlockHashAlgorithm;
2326
import com.hederahashgraph.api.proto.java.SignedTransaction;
2427
import com.hederahashgraph.api.proto.java.TransactionBody;
28+
import com.hederahashgraph.api.proto.java.TransactionID;
2529
import jakarta.annotation.Nonnull;
2630
import jakarta.inject.Named;
2731
import java.util.ArrayList;
2832
import java.util.Collections;
2933
import java.util.EnumMap;
34+
import java.util.HashMap;
3035
import java.util.List;
36+
import java.util.Map;
3137
import java.util.Objects;
32-
import lombok.CustomLog;
3338
import lombok.Setter;
3439
import lombok.Value;
3540
import lombok.experimental.NonFinal;
@@ -39,7 +44,6 @@
3944
import org.hiero.mirror.common.util.DomainUtils;
4045
import org.hiero.mirror.importer.exception.InvalidStreamFileException;
4146

42-
@CustomLog
4347
@Named
4448
public final class BlockStreamReaderImpl implements BlockStreamReader {
4549

@@ -137,8 +141,7 @@ private void readSignedTransactions(ReaderContext context) {
137141
"Missing transaction result in block " + context.getFilename());
138142
}
139143

140-
var transactionOutputs = new EnumMap<TransactionOutput.TransactionCase, TransactionOutput>(
141-
TransactionOutput.TransactionCase.class);
144+
var transactionOutputs = new EnumMap<TransactionCase, TransactionOutput>(TransactionCase.class);
142145
while ((protoBlockItem = context.readBlockItemFor(TRANSACTION_OUTPUT)) != null) {
143146
var transactionOutput = protoBlockItem.getTransactionOutput();
144147
transactionOutputs.put(transactionOutput.getTransactionCase(), transactionOutput);
@@ -165,13 +168,14 @@ private void readSignedTransactions(ReaderContext context) {
165168

166169
var blockTransaction = BlockTransaction.builder()
167170
.previous(context.getLastBlockTransaction())
171+
.signedTransaction(signedTransaction)
172+
.signedTransactionBytes(signedTransactionInfo.signedTransaction())
168173
.stateChanges(Collections.unmodifiableList(stateChangesList))
169174
.traceData(Collections.unmodifiableList(traceDataList))
170175
.transactionBody(transactionBody)
171176
.transactionResult(transactionResult)
172177
.transactionOutputs(Collections.unmodifiableMap(transactionOutputs))
173-
.signedTransaction(signedTransaction)
174-
.signedTransactionBytes(signedTransactionInfo.signedTransaction())
178+
.trigger(context.getScheduledTransactionTriggers().get(transactionBody.getTransactionID()))
175179
.build();
176180
context.getBlockFile().item(blockTransaction);
177181
context.setLastBlockTransaction(blockTransaction, signedTransactionInfo.userTransactionInBatch());
@@ -211,6 +215,7 @@ private void readNonTransactionStateChanges(ReaderContext context) {
211215

212216
@Value
213217
private static class ReaderContext {
218+
214219
private BlockFile.BlockFileBuilder blockFile;
215220
private List<BlockItem> blockItems;
216221
private BlockRootHashDigest blockRootHashDigest;
@@ -238,6 +243,8 @@ private static class ReaderContext {
238243
@Setter
239244
private Long lastMetaTimestamp; // The last consensus timestamp from metadata
240245

246+
private Map<TransactionID, BlockTransaction> scheduledTransactionTriggers = new HashMap<>();
247+
241248
ReaderContext(@Nonnull List<BlockItem> blockItems, @Nonnull String filename) {
242249
this.blockFile = BlockFile.builder();
243250
this.blockItems = blockItems;
@@ -280,7 +287,7 @@ BlockItem readBlockItemFor(BlockItem.ItemCase itemCase) {
280287
return blockItem;
281288
}
282289

283-
void setLastBlockTransaction(BlockTransaction lastBlockTransaction, boolean userTransactionInBatch) {
290+
void setLastBlockTransaction(@Nonnull BlockTransaction lastBlockTransaction, boolean userTransactionInBatch) {
284291
if (userTransactionInBatch) {
285292
if (lastUserTransactionInBatch != null
286293
&& batchBody != null
@@ -296,12 +303,27 @@ void setLastBlockTransaction(BlockTransaction lastBlockTransaction, boolean user
296303
}
297304

298305
this.lastBlockTransaction = lastBlockTransaction;
299-
if (lastBlockTransaction != null
300-
&& lastBlockTransaction.getTransactionBody().hasAtomicBatch()) {
306+
if (lastBlockTransaction.getTransactionBody().hasAtomicBatch()) {
301307
this.batchIndex = 0;
302308
this.batchBody = lastBlockTransaction.getTransactionBody().getAtomicBatch();
303309
this.lastUserTransactionInBatch = null;
304310
}
311+
312+
// A short-term scheduled transaction and its triggering transaction (either a schedule create or a schedule
313+
// sign) belong to one transactional unit, thus the statechanges are attached to the triggering transaction.
314+
// Build the map to link a short-term scheduled transaction to its trigger
315+
lastBlockTransaction
316+
.getTransactionOutput(TransactionCase.CREATE_SCHEDULE)
317+
.map(TransactionOutput::getCreateSchedule)
318+
.filter(CreateScheduleOutput::hasScheduledTransactionId)
319+
.map(CreateScheduleOutput::getScheduledTransactionId)
320+
.or(() -> lastBlockTransaction
321+
.getTransactionOutput(TransactionCase.SIGN_SCHEDULE)
322+
.map(TransactionOutput::getSignSchedule)
323+
.filter(SignScheduleOutput::hasScheduledTransactionId)
324+
.map(SignScheduleOutput::getScheduledTransactionId))
325+
.ifPresent(scheduledTransactionId ->
326+
scheduledTransactionTriggers.put(scheduledTransactionId, lastBlockTransaction));
305327
}
306328
}
307329

importer/src/test/java/org/hiero/mirror/importer/downloader/block/transformer/ConsensusTransformerTest.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
import com.hedera.hapi.block.stream.trace.protoc.TraceData;
1515
import com.hederahashgraph.api.proto.java.ResponseCodeEnum;
1616
import com.hederahashgraph.api.proto.java.Topic;
17+
import com.hederahashgraph.api.proto.java.TransactionID;
1718
import com.hederahashgraph.api.proto.java.TransactionReceipt;
1819
import java.util.List;
20+
import org.hiero.mirror.common.domain.transaction.RecordItem;
1921
import org.junit.jupiter.api.Test;
2022
import org.junit.jupiter.params.ParameterizedTest;
2123
import org.junit.jupiter.params.provider.ValueSource;
@@ -213,4 +215,67 @@ void consensusSubmitMessageTransformUnsuccessful() {
213215
// then
214216
assertRecordFile(recordFile, blockFile, items -> assertThat(items).containsExactly(expectedRecordItem));
215217
}
218+
219+
@Test
220+
void scheduledConsensusSubmitMessageTransform() {
221+
// given
222+
var scheduledTransactionId = TransactionID.newBuilder()
223+
.setAccountID(recordItemBuilder.accountId())
224+
.setScheduled(true)
225+
.setTransactionValidStart(recordItemBuilder.timestamp())
226+
.build();
227+
var topicId = recordItemBuilder.topicId();
228+
var runningHash = recordItemBuilder.bytes(48);
229+
var scheduleSignRecordItem = recordItemBuilder
230+
.scheduleCreate()
231+
.receipt(r -> r.setScheduledTransactionID(scheduledTransactionId))
232+
.customize(this::finalize)
233+
.build();
234+
var scheduleSignBlockTransaction = blockTransactionBuilder
235+
.scheduleCreate(scheduleSignRecordItem)
236+
.stateChanges(s -> {
237+
var stateChanges = s.getFirst().toBuilder()
238+
.addStateChanges(StateChange.newBuilder()
239+
.setStateId(StateIdentifier.STATE_ID_TOPICS_VALUE)
240+
.setMapUpdate(MapUpdateChange.newBuilder()
241+
.setKey(MapChangeKey.newBuilder().setTopicIdKey(topicId))
242+
.setValue(MapChangeValue.newBuilder()
243+
.setTopicValue(Topic.newBuilder()
244+
.setTopicId(topicId)
245+
.setRunningHash(runningHash)
246+
.setSequenceNumber(10)))))
247+
.build();
248+
s.clear();
249+
s.add(stateChanges);
250+
})
251+
.build();
252+
var consensusSubmitMessageRecordItem = recordItemBuilder
253+
.consensusSubmitMessage()
254+
.clearIncrementer()
255+
.receipt(r -> r.setTopicRunningHash(runningHash).setTopicSequenceNumber(10))
256+
.transactionBody(b -> b.setTopicID(topicId))
257+
.transactionBodyWrapper(w -> w.setTransactionID(scheduledTransactionId))
258+
.recordItem(r -> r.transactionIndex(1))
259+
.customize(this::finalize)
260+
.build();
261+
var consensusSubmitMessageBlockTransaction = blockTransactionBuilder
262+
.consensusSubmitMessage(consensusSubmitMessageRecordItem)
263+
.previous(scheduleSignBlockTransaction)
264+
.stateChanges(List::clear)
265+
.trigger(scheduleSignBlockTransaction)
266+
.build();
267+
var blockFile = blockFileBuilder
268+
.items(List.of(scheduleSignBlockTransaction, consensusSubmitMessageBlockTransaction))
269+
.build();
270+
271+
// when
272+
var recordFile = blockFileTransformer.transform(blockFile);
273+
274+
// then
275+
var expected = List.of(scheduleSignRecordItem, consensusSubmitMessageRecordItem);
276+
assertRecordFile(recordFile, blockFile, items -> {
277+
assertRecordItems(items, expected);
278+
assertThat(items).map(RecordItem::getParent).containsOnlyNulls();
279+
});
280+
}
216281
}

importer/src/test/java/org/hiero/mirror/importer/parser/domain/BlockTransactionBuilder.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -894,13 +894,15 @@ private static EvmTransactionResult fromContractResult(ContractFunctionResult co
894894
}
895895

896896
public static class Builder {
897+
898+
private BlockTransaction previous;
897899
private final SignedTransaction signedTransaction;
898900
private final byte[] signedTransactionBytes;
899-
private final Map<TransactionCase, TransactionOutput> transactionOutputs;
900-
private final TransactionResult.Builder transactionResultBuilder;
901901
private final List<StateChanges> stateChanges;
902902
private final List<TraceData> traceDataList;
903-
private BlockTransaction previous;
903+
private final Map<TransactionCase, TransactionOutput> transactionOutputs;
904+
private final TransactionResult.Builder transactionResultBuilder;
905+
private BlockTransaction trigger;
904906

905907
@SneakyThrows
906908
@SuppressWarnings({"java:S1640", "deprecation"})
@@ -941,6 +943,7 @@ public BlockTransaction build() {
941943
.transactionBody(TransactionBody.parseFrom(signedTransaction.getBodyBytes()))
942944
.transactionResult(transactionResultBuilder.build())
943945
.transactionOutputs(transactionOutputs)
946+
.trigger(trigger)
944947
.build();
945948
}
946949

@@ -968,6 +971,11 @@ public Builder transactionResult(Consumer<TransactionResult.Builder> consumer) {
968971
consumer.accept(transactionResultBuilder);
969972
return this;
970973
}
974+
975+
public Builder trigger(BlockTransaction trigger) {
976+
this.trigger = trigger;
977+
return this;
978+
}
971979
}
972980

973981
private record EvmTraceDataBuilder(

importer/src/test/java/org/hiero/mirror/importer/parser/domain/RecordItemBuilder.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1472,7 +1472,6 @@ public class Builder<T extends GeneratedMessage.Builder<T>> {
14721472

14731473
private static final BiConsumer<TransactionBody.Builder, TransactionRecord.Builder> NOOP_INCREMENTER =
14741474
(b, r) -> {};
1475-
14761475
private final TransactionType type;
14771476
private final T transactionBody;
14781477
private final SignatureMap.Builder signatureMap;
@@ -1552,6 +1551,11 @@ public RecordItem build() {
15521551
.build();
15531552
}
15541553

1554+
public Builder<T> clearIncrementer() {
1555+
this.incrementer = NOOP_INCREMENTER;
1556+
return this;
1557+
}
1558+
15551559
public Builder<T> customize(Consumer<Builder<T>> consumer) {
15561560
consumer.accept(this);
15571561
return this;
@@ -1562,11 +1566,6 @@ public Builder<T> entityTransactionPredicate(Predicate<EntityId> entityTransacti
15621566
return this;
15631567
}
15641568

1565-
public Builder<T> clearIncrementer() {
1566-
this.incrementer = NOOP_INCREMENTER;
1567-
return this;
1568-
}
1569-
15701569
public Builder<T> contractTransactionPredicate(Predicate<EntityId> contractTransactionPredicate) {
15711570
this.contractTransactionPredicate = contractTransactionPredicate;
15721571
return this;

0 commit comments

Comments
 (0)