Skip to content

Commit 127f00f

Browse files
Adapt importer to handle changes from HIP-1215
Signed-off-by: Ivan Kavaldzhiev <ivankavaldzhiev@gmail.com>
1 parent 1991e40 commit 127f00f

7 files changed

Lines changed: 270 additions & 2 deletions

File tree

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,12 @@ private Version hapiVersion() {
145145

146146
return new Version(hapiVersionMajor, hapiVersionMinor, hapiVersionPatch);
147147
}
148+
149+
public long getGasUsed() {
150+
return gasUsed;
151+
}
152+
153+
public byte[] getLogsBloom() {
154+
return logsBloom;
155+
}
148156
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,11 @@ public boolean isInvalidIdError() {
172172

173173
// Whether this is a top level, user submitted transaction that could possibly trigger other internal transactions.
174174
public boolean isTopLevel() {
175-
return transactionRecord.getTransactionID().getNonce() == 0;
175+
var transactionNonce = transactionRecord.getTransactionID().getNonce();
176+
177+
return transactionNonce == 0
178+
|| (transactionNonce > 0 && transactionRecord.getTransactionID().getScheduled())
179+
|| transactionRecord.getParentConsensusTimestamp().getSeconds() == 0;
176180
}
177181

178182
/**

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import static org.hiero.mirror.importer.domain.StreamFilename.FileType.DATA;
66

7+
import com.hederahashgraph.api.proto.java.Timestamp;
78
import jakarta.inject.Named;
89
import java.time.Instant;
910
import java.util.ArrayDeque;
@@ -118,9 +119,13 @@ public class ItemBuilder {
118119

119120
private int count = 100;
120121
private int entities = 10;
122+
private int nonce = 0;
121123
private boolean entityAutoCreation = false;
124+
private boolean isScheduled = false;
122125
private SubType subType = SubType.STANDARD;
123126
private TransactionType type = TransactionType.UNKNOWN;
127+
private Timestamp parentConsensusTimestamp =
128+
Timestamp.newBuilder().setSeconds(0L).build();
124129
private Supplier<RecordItemBuilder.Builder<?>> template;
125130

126131
@Getter(lazy = true, value = AccessLevel.PRIVATE)
@@ -140,11 +145,22 @@ public ItemBuilder entities(int entities) {
140145
return this;
141146
}
142147

148+
public ItemBuilder nonce(int nonce) {
149+
Assert.isTrue(nonce > 0, "nonce must be positive");
150+
this.nonce = nonce;
151+
return this;
152+
}
153+
143154
public ItemBuilder entityAutoCreation(boolean entityAutoCreation) {
144155
this.entityAutoCreation = entityAutoCreation;
145156
return this;
146157
}
147158

159+
public ItemBuilder isScheduled(boolean isScheduled) {
160+
this.isScheduled = isScheduled;
161+
return this;
162+
}
163+
148164
public ItemBuilder subType(SubType subType) {
149165
Assert.notNull(subType, "subType must not be null");
150166
this.subType = subType;
@@ -164,6 +180,11 @@ public ItemBuilder type(TransactionType type) {
164180
return this;
165181
}
166182

183+
public ItemBuilder parentConsensusTimestamp(Timestamp parentConsensusTimestamp) {
184+
this.parentConsensusTimestamp = parentConsensusTimestamp;
185+
return this;
186+
}
187+
167188
private Supplier<RecordItem> build() {
168189
var recordItemBuilders = getBuilders();
169190
int builderSize = recordItemBuilders.size();
@@ -197,6 +218,8 @@ private Supplier<RecordItemBuilder.Builder<?>> buildTemplate() {
197218
if (subType != SubType.STANDARD) {
198219
return switch (subType) {
199220
case TOKEN_TRANSFER -> () -> recordItemBuilder.cryptoTransfer(TransferType.TOKEN);
221+
case CONTRACT_CALL ->
222+
() -> recordItemBuilder.contractCall(nonce, parentConsensusTimestamp, isScheduled);
200223
default -> throw new IllegalArgumentException("subType not supported: " + subType);
201224
};
202225
}

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,28 @@ public Builder<ContractCallTransactionBody.Builder> contractCall() {
337337
return contractCall(contractId());
338338
}
339339

340+
public Builder<ContractCallTransactionBody.Builder> contractCall(
341+
final int nonce, final Timestamp parentConsensusTimestamp, boolean isScheduled) {
342+
var contractId = contractId();
343+
ContractCallTransactionBody.Builder transactionBody = ContractCallTransactionBody.newBuilder()
344+
.setAmount(5_000L)
345+
.setContractID(contractId)
346+
.setFunctionParameters(nonZeroBytes(64))
347+
.setGas(10_000L);
348+
349+
var functionResult = contractFunctionResult(contractId);
350+
var transactionID = TransactionID.newBuilder()
351+
.setNonce(nonce)
352+
.setAccountID(functionResult.getSenderId())
353+
.setScheduled(isScheduled);
354+
355+
return new Builder<>(TransactionType.CONTRACTCALL, transactionBody)
356+
.receipt(r -> r.setContractID(contractId))
357+
.record(r -> r.setContractCallResult(functionResult)
358+
.setTransactionID(transactionID.build())
359+
.setParentConsensusTimestamp(parentConsensusTimestamp));
360+
}
361+
340362
@SuppressWarnings("deprecation")
341363
public Builder<ContractCallTransactionBody.Builder> contractCall(ContractID contractId) {
342364
ContractCallTransactionBody.Builder transactionBody = ContractCallTransactionBody.newBuilder()

importer/src/test/java/org/hiero/mirror/importer/parser/record/RecordFileParserIntegrationTest.java

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@
44

55
import static org.assertj.core.api.Assertions.assertThat;
66

7+
import com.hederahashgraph.api.proto.java.Timestamp;
78
import java.util.Comparator;
89
import java.util.LinkedList;
910
import java.util.List;
1011
import java.util.concurrent.atomic.AtomicInteger;
1112
import lombok.RequiredArgsConstructor;
13+
import org.hiero.mirror.common.aggregator.LogsBloomAggregator;
1214
import org.hiero.mirror.common.domain.contract.ContractLog;
1315
import org.hiero.mirror.common.domain.entity.EntityId;
1416
import org.hiero.mirror.common.domain.topic.StreamMessage;
1517
import org.hiero.mirror.common.domain.transaction.RecordFile;
1618
import org.hiero.mirror.common.domain.transaction.TransactionType;
19+
import org.hiero.mirror.common.util.DomainUtils;
1720
import org.hiero.mirror.importer.EnabledIfV1;
1821
import org.hiero.mirror.importer.ImporterIntegrationTest;
1922
import org.hiero.mirror.importer.exception.ParserException;
@@ -24,6 +27,7 @@
2427
import org.hiero.mirror.importer.repository.EntityRepository;
2528
import org.hiero.mirror.importer.repository.RecordFileRepository;
2629
import org.hiero.mirror.importer.repository.TransactionRepository;
30+
import org.hiero.mirror.importer.test.performance.PerformanceProperties.SubType;
2731
import org.junit.jupiter.api.Assertions;
2832
import org.junit.jupiter.api.BeforeEach;
2933
import org.junit.jupiter.api.Test;
@@ -113,6 +117,158 @@ void parseSingleThenList() {
113117
assertThat(transactionRepository.count()).isEqualTo(3 * transactions);
114118
}
115119

120+
@Test
121+
void parseSingleFileWithNonceZeroContractCallItems() {
122+
// given
123+
int transactions = 2;
124+
int entities = 2;
125+
LogsBloomAggregator logsBloom = new LogsBloomAggregator();
126+
var recordFileTemplate = recordFileBuilder
127+
.recordFile()
128+
.recordItems(i -> i.count(transactions).entities(entities).subType(SubType.CONTRACT_CALL));
129+
var recordFile = recordFileTemplate.build();
130+
recordFile.getItems().forEach(r -> {
131+
var rec = r.getTransactionRecord();
132+
var result = rec.hasContractCreateResult() ? rec.getContractCreateResult() : rec.getContractCallResult();
133+
logsBloom.aggregate(DomainUtils.toBytes(result.getBloom()));
134+
});
135+
136+
// when
137+
recordFileParser.parse(recordFile);
138+
139+
// then
140+
assertRecordFile(recordFile);
141+
var updatedRecordFileOptional = recordFileRepository.findById(recordFile.getConsensusEnd());
142+
assertThat(updatedRecordFileOptional).isPresent();
143+
var updatedRecordFile = updatedRecordFileOptional.get();
144+
assertThat(updatedRecordFile.getGasUsed()).isEqualTo(transactions * 100000L);
145+
assertThat(updatedRecordFile.getLogsBloom()).isEqualTo(logsBloom.getBloom());
146+
}
147+
148+
@Test
149+
void parseSingleFileWithEmptyParentConsensusTimestampTopLevelContractCallItems() {
150+
// given
151+
int transactions = 2;
152+
int entities = 2;
153+
LogsBloomAggregator logsBloom = new LogsBloomAggregator();
154+
var recordFileTemplate = recordFileBuilder.recordFile().recordItems(i -> i.count(transactions)
155+
.entities(entities)
156+
.subType(SubType.CONTRACT_CALL)
157+
.nonce(5)
158+
.isScheduled(false));
159+
var recordFile = recordFileTemplate.build();
160+
recordFile.getItems().forEach(r -> {
161+
var rec = r.getTransactionRecord();
162+
var result = rec.hasContractCreateResult() ? rec.getContractCreateResult() : rec.getContractCallResult();
163+
logsBloom.aggregate(DomainUtils.toBytes(result.getBloom()));
164+
});
165+
166+
// when
167+
recordFileParser.parse(recordFile);
168+
169+
// then
170+
assertRecordFile(recordFile);
171+
var updatedRecordFileOptional = recordFileRepository.findById(recordFile.getConsensusEnd());
172+
assertThat(updatedRecordFileOptional).isPresent();
173+
var updatedRecordFile = updatedRecordFileOptional.get();
174+
assertThat(updatedRecordFile.getGasUsed()).isEqualTo(transactions * 100000L);
175+
assertThat(updatedRecordFile.getLogsBloom()).isEqualTo(logsBloom.getBloom());
176+
}
177+
178+
@Test
179+
void parseSingleFileWitHavingParentConsensusTimestampNonTopLevelContractCallItems() {
180+
// given
181+
int transactions = 2;
182+
int entities = 2;
183+
LogsBloomAggregator logsBloom = new LogsBloomAggregator();
184+
var recordFileTemplate = recordFileBuilder.recordFile().recordItems(i -> i.count(transactions)
185+
.entities(entities)
186+
.subType(SubType.CONTRACT_CALL)
187+
.nonce(5)
188+
.isScheduled(false)
189+
.parentConsensusTimestamp(
190+
Timestamp.newBuilder().setSeconds(1403434L).build()));
191+
var recordFile = recordFileTemplate.build();
192+
recordFile.getItems().forEach(r -> {
193+
var rec = r.getTransactionRecord();
194+
var result = rec.hasContractCreateResult() ? rec.getContractCreateResult() : rec.getContractCallResult();
195+
logsBloom.aggregate(DomainUtils.toBytes(result.getBloom()));
196+
});
197+
198+
// when
199+
recordFileParser.parse(recordFile);
200+
201+
// then
202+
assertRecordFile(recordFile);
203+
var updatedRecordFileOptional = recordFileRepository.findById(recordFile.getConsensusEnd());
204+
assertThat(updatedRecordFileOptional).isPresent();
205+
var updatedRecordFile = updatedRecordFileOptional.get();
206+
assertThat(updatedRecordFile.getGasUsed()).isZero();
207+
assertThat(updatedRecordFile.getLogsBloom()).isEmpty();
208+
}
209+
210+
@Test
211+
void parseSingleFileWithPositiveNonceAndScheduledTopLevelContractCallItems() {
212+
// given
213+
int transactions = 2;
214+
int entities = 2;
215+
LogsBloomAggregator logsBloom = new LogsBloomAggregator();
216+
var recordFileTemplate = recordFileBuilder.recordFile().recordItems(i -> i.count(transactions)
217+
.entities(entities)
218+
.subType(SubType.CONTRACT_CALL)
219+
.nonce(8)
220+
.isScheduled(true));
221+
var recordFile = recordFileTemplate.build();
222+
recordFile.getItems().forEach(r -> {
223+
var rec = r.getTransactionRecord();
224+
var result = rec.hasContractCreateResult() ? rec.getContractCreateResult() : rec.getContractCallResult();
225+
logsBloom.aggregate(DomainUtils.toBytes(result.getBloom()));
226+
});
227+
228+
// when
229+
recordFileParser.parse(recordFile);
230+
231+
// then
232+
assertRecordFile(recordFile);
233+
var updatedRecordFileOptional = recordFileRepository.findById(recordFile.getConsensusEnd());
234+
assertThat(updatedRecordFileOptional).isPresent();
235+
var updatedRecordFile = updatedRecordFileOptional.get();
236+
assertThat(updatedRecordFile.getGasUsed()).isEqualTo(transactions * 100000L);
237+
assertThat(updatedRecordFile.getLogsBloom()).isEqualTo(logsBloom.getBloom());
238+
}
239+
240+
@Test
241+
void parseSingleFileWithOneTopLevelAndOneNotTopLevelContractCall() {
242+
// given
243+
int transactions = 2;
244+
int entities = 1;
245+
LogsBloomAggregator logsBloom = new LogsBloomAggregator();
246+
var recordFileTemplate = recordFileBuilder.recordFile().recordItems(i -> i.count(transactions)
247+
.entities(entities)
248+
.subType(SubType.CONTRACT_CALL)
249+
.nonce(5)
250+
.isScheduled(false)
251+
.parentConsensusTimestamp(
252+
Timestamp.newBuilder().setSeconds(1403434L).build()));
253+
var recordFile = recordFileTemplate.build();
254+
var transactionRecord = recordFile.getItems().get(1).getTransactionRecord();
255+
var result = transactionRecord.hasContractCreateResult()
256+
? transactionRecord.getContractCreateResult()
257+
: transactionRecord.getContractCallResult();
258+
logsBloom.aggregate(DomainUtils.toBytes(result.getBloom()));
259+
260+
// when
261+
recordFileParser.parse(recordFile);
262+
263+
// then
264+
assertRecordFile(recordFile);
265+
var updatedRecordFileOptional = recordFileRepository.findById(recordFile.getConsensusEnd());
266+
assertThat(updatedRecordFileOptional).isPresent();
267+
var updatedRecordFile = updatedRecordFileOptional.get();
268+
assertThat(updatedRecordFile.getGasUsed()).isEqualTo(entities * 100000L);
269+
assertThat(updatedRecordFile.getLogsBloom()).isEqualTo(logsBloom.getBloom());
270+
}
271+
116272
@Test
117273
void parseWithLogIndexValidation() {
118274
// given

importer/src/test/java/org/hiero/mirror/importer/parser/record/entity/EntityRecordItemListenerContractTest.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,6 +1064,60 @@ void contractCallBadContractId() {
10641064
() -> assertEntityTransactions(recordItem));
10651065
}
10661066

1067+
@Test
1068+
void testCallWithZeroNonceToBeTopLevel() {
1069+
var recordItem = recordItemBuilder
1070+
.contractCall()
1071+
.record(r -> {
1072+
r.getTransactionIDBuilder().setNonce(0);
1073+
})
1074+
.build();
1075+
1076+
assertThat(recordItem.isTopLevel()).isTrue();
1077+
}
1078+
1079+
@Test
1080+
void testCallWithPositiveNonceAndNotScheduledTypeToNotBeTopLevel() {
1081+
var recordItem = recordItemBuilder
1082+
.contractCall()
1083+
.record(r -> {
1084+
r.getTransactionIDBuilder().setNonce(1).setScheduled(false);
1085+
})
1086+
.build();
1087+
1088+
assertThat(recordItem.isTopLevel()).isFalse();
1089+
}
1090+
1091+
@Test
1092+
void testCallWithNonEmptyParentConsensusTimestampToNotBeTopLevel() {
1093+
var recordItem = recordItemBuilder
1094+
.contractCall()
1095+
.record(r -> {
1096+
r.getTransactionIDBuilder().setNonce(1).setScheduled(false);
1097+
r.setParentConsensusTimestamp(Timestamp.newBuilder()
1098+
.setSeconds(1499853)
1099+
.setNanos(0)
1100+
.build());
1101+
})
1102+
.build();
1103+
1104+
assertThat(recordItem.isTopLevel()).isFalse();
1105+
}
1106+
1107+
@Test
1108+
void testCallWithEmptyParentConsensusTimestampToBeTopLevel() {
1109+
var recordItem = recordItemBuilder
1110+
.contractCall()
1111+
.record(r -> {
1112+
r.getTransactionIDBuilder().setNonce(1).setScheduled(false);
1113+
r.setParentConsensusTimestamp(
1114+
Timestamp.newBuilder().setSeconds(0).setNanos(0).build());
1115+
})
1116+
.build();
1117+
1118+
assertThat(recordItem.isTopLevel()).isTrue();
1119+
}
1120+
10671121
private void assertFailedContractCreate(TransactionBody transactionBody, TransactionRecord txnRecord) {
10681122
var dbTransaction = getDbTransaction(txnRecord.getConsensusTimestamp());
10691123
var contractCreateBody = transactionBody.getContractCreateInstance();

importer/src/test/java/org/hiero/mirror/importer/test/performance/PerformanceProperties.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ public class PerformanceProperties {
3232

3333
public enum SubType {
3434
STANDARD,
35-
TOKEN_TRANSFER
35+
TOKEN_TRANSFER,
36+
CONTRACT_CALL;
3637
}
3738

3839
@Data

0 commit comments

Comments
 (0)