Skip to content

Commit 6a62b45

Browse files
authored
fix: Cherry-pick: add pureChecks to ingest workflow (#12549)
Signed-off-by: Michael Heinrichs <[email protected]>
1 parent 969dd79 commit 6a62b45

File tree

7 files changed

+68
-22
lines changed

7 files changed

+68
-22
lines changed

hedera-node/hedera-app/src/main/java/com/hedera/node/app/grpc/impl/MethodBase.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ public void invoke(
140140
callsHandledSpeedometer.cycle();
141141
} catch (final Exception e) {
142142
// Track the number of times we failed to handle a call
143-
logger.error("Possibly CATASTROPHIC failure while handling a call and running the ingest workflow", e);
143+
logger.error("Possibly CATASTROPHIC failure while handling a GRPC message", e);
144144
callsFailedCounter.increment();
145145
responseObserver.onError(e);
146146
}

hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/ingest/IngestChecker.java

+3
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@ public TransactionInfo runAllChecks(
217217
throw new PreCheckException(BUSY);
218218
}
219219

220+
// 4a. Run pure checks
221+
dispatcher.dispatchPureChecks(txBody);
222+
220223
// 5. Get payer account
221224
final var storeFactory = new ReadableStoreFactory(state);
222225
final var payer = solvencyPreCheck.getPayerAccount(storeFactory, txInfo.payerID());

hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/ingest/IngestWorkflowImpl.java

+13
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import com.hedera.hapi.node.base.ResponseCodeEnum;
2222
import com.hedera.hapi.node.transaction.TransactionResponse;
23+
import com.hedera.node.app.spi.workflows.HandleException;
2324
import com.hedera.node.app.spi.workflows.InsufficientBalanceException;
2425
import com.hedera.node.app.spi.workflows.PreCheckException;
2526
import com.hedera.node.app.state.HederaState;
@@ -33,9 +34,14 @@
3334
import java.io.UncheckedIOException;
3435
import java.util.function.Supplier;
3536
import javax.inject.Inject;
37+
import org.apache.logging.log4j.LogManager;
38+
import org.apache.logging.log4j.Logger;
3639

3740
/** Implementation of {@link IngestWorkflow} */
3841
public final class IngestWorkflowImpl implements IngestWorkflow {
42+
43+
private static final Logger logger = LogManager.getLogger(IngestWorkflowImpl.class);
44+
3945
private final Supplier<AutoCloseableWrapper<HederaState>> stateAccessor;
4046
private final TransactionChecker transactionChecker;
4147
private final IngestChecker ingestChecker;
@@ -92,6 +98,13 @@ public void submitTransaction(@NonNull final Bytes requestBuffer, @NonNull final
9298
result = e.responseCode();
9399
} catch (final PreCheckException e) {
94100
result = e.responseCode();
101+
} catch (final HandleException e) {
102+
// Conceptually, this should never happen, because we should use PreCheckException only during pre-checks
103+
// But we catch it here to play it safe
104+
result = e.getStatus();
105+
} catch (final Exception e) {
106+
logger.error("Possibly CATASTROPHIC failure while running the ingest workflow", e);
107+
result = ResponseCodeEnum.FAIL_INVALID;
95108
}
96109

97110
// 8. Return PreCheck code and estimated fee

hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/query/QueryWorkflowImpl.java

+21-4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static com.hedera.hapi.node.base.HederaFunctionality.GET_ACCOUNT_DETAILS;
2020
import static com.hedera.hapi.node.base.HederaFunctionality.NETWORK_GET_EXECUTION_TIME;
2121
import static com.hedera.hapi.node.base.ResponseCodeEnum.BUSY;
22+
import static com.hedera.hapi.node.base.ResponseCodeEnum.FAIL_INVALID;
2223
import static com.hedera.hapi.node.base.ResponseCodeEnum.NOT_SUPPORTED;
2324
import static com.hedera.hapi.node.base.ResponseCodeEnum.OK;
2425
import static com.hedera.hapi.node.base.ResponseCodeEnum.PAYER_ACCOUNT_NOT_FOUND;
@@ -45,9 +46,11 @@
4546
import com.hedera.node.app.spi.authorization.Authorizer;
4647
import com.hedera.node.app.spi.fees.ExchangeRateInfo;
4748
import com.hedera.node.app.spi.records.RecordCache;
49+
import com.hedera.node.app.spi.workflows.HandleException;
4850
import com.hedera.node.app.spi.workflows.InsufficientBalanceException;
4951
import com.hedera.node.app.spi.workflows.PreCheckException;
5052
import com.hedera.node.app.spi.workflows.QueryContext;
53+
import com.hedera.node.app.spi.workflows.QueryHandler;
5154
import com.hedera.node.app.state.HederaState;
5255
import com.hedera.node.app.throttle.SynchronizedThrottleAccumulator;
5356
import com.hedera.node.app.workflows.dispatcher.ReadableStoreFactory;
@@ -275,11 +278,16 @@ public void handleQuery(@NonNull final Bytes requestBuffer, @NonNull final Buffe
275278
response = handler.findResponse(context, header);
276279
}
277280
} catch (InsufficientBalanceException e) {
278-
final var header = createResponseHeader(responseType, e.responseCode(), e.getEstimatedFee());
279-
response = handler.createEmptyResponse(header);
281+
response = createErrorResponse(handler, responseType, e.responseCode(), e.getEstimatedFee());
280282
} catch (PreCheckException e) {
281-
final var header = createResponseHeader(responseType, e.responseCode(), 0L);
282-
response = handler.createEmptyResponse(header);
283+
response = createErrorResponse(handler, responseType, e.responseCode(), 0L);
284+
} catch (HandleException e) {
285+
// Conceptually, this should never happen, because we should use PreCheckException only for queries
286+
// But we catch it here to play it safe
287+
response = createErrorResponse(handler, responseType, e.getStatus(), 0L);
288+
} catch (Exception e) {
289+
logger.error("Unexpected exception while handling a query", e);
290+
response = createErrorResponse(handler, responseType, FAIL_INVALID, 0L);
283291
}
284292
} else {
285293
response = DEFAULT_UNSUPPORTED_RESPONSE;
@@ -311,6 +319,15 @@ private Query parseQuery(Bytes requestBuffer) {
311319
}
312320
}
313321

322+
private static Response createErrorResponse(
323+
@NonNull final QueryHandler handler,
324+
@NonNull final ResponseType responseType,
325+
@NonNull final ResponseCodeEnum responseCode,
326+
final long fee) {
327+
final var header = createResponseHeader(responseType, responseCode, fee);
328+
return handler.createEmptyResponse(header);
329+
}
330+
314331
private static ResponseHeader createResponseHeader(
315332
@NonNull final ResponseType type, @NonNull final ResponseCodeEnum responseCode, final long fee) {
316333
return ResponseHeader.newBuilder()

hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/ingest/IngestWorkflowImplTest.java

+28-15
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.hedera.node.app.workflows.ingest;
1818

1919
import static com.hedera.hapi.node.base.ResponseCodeEnum.BUSY;
20+
import static com.hedera.hapi.node.base.ResponseCodeEnum.FAIL_INVALID;
2021
import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_SIGNATURE;
2122
import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TRANSACTION;
2223
import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TRANSACTION_BODY;
@@ -250,14 +251,18 @@ void onsetFailsWithPreCheckException(ResponseCodeEnum failureReason) throws PreC
250251

251252
@Test
252253
@DisplayName("If some random exception is thrown from TransactionChecker, the exception is bubbled up")
253-
void randomException() throws PreCheckException {
254+
void randomException() throws PreCheckException, ParseException {
254255
// Given a WorkflowOnset that will throw a RuntimeException
255256
when(transactionChecker.parse(any())).thenThrow(new RuntimeException("parseAndCheck exception"));
256257

257-
// When the transaction is submitted, then the exception is bubbled up
258-
assertThatThrownBy(() -> workflow.submitTransaction(requestBuffer, responseBuffer))
259-
.isInstanceOf(RuntimeException.class)
260-
.hasMessageContaining("parseAndCheck exception");
258+
// When the transaction is submitted
259+
workflow.submitTransaction(requestBuffer, responseBuffer);
260+
261+
// Then the response will indicate the platform rejected the transaction
262+
final TransactionResponse response = parseResponse(responseBuffer);
263+
assertThat(response.nodeTransactionPrecheckCode()).isEqualTo(FAIL_INVALID);
264+
// And the cost will be zero
265+
assertThat(response.cost()).isZero();
261266
// And the transaction is not submitted to the platform
262267
verify(submissionManager, never()).submit(any(), any());
263268
}
@@ -296,15 +301,19 @@ void testIngestChecksFail(ResponseCodeEnum failureReason) throws PreCheckExcepti
296301

297302
@Test
298303
@DisplayName("If some random exception is thrown from IngestChecker, the exception is bubbled up")
299-
void randomException() throws PreCheckException {
304+
void randomException() throws PreCheckException, ParseException {
300305
// Given a ThrottleAccumulator that will throw a RuntimeException
301306
when(ingestChecker.runAllChecks(state, transaction, configuration))
302307
.thenThrow(new RuntimeException("runAllChecks exception"));
303308

304-
// When the transaction is submitted, then the exception is bubbled up
305-
assertThatThrownBy(() -> workflow.submitTransaction(requestBuffer, responseBuffer))
306-
.isInstanceOf(RuntimeException.class)
307-
.hasMessageContaining("runAllChecks exception");
309+
// When the transaction is submitted
310+
workflow.submitTransaction(requestBuffer, responseBuffer);
311+
312+
// Then the response will indicate the platform rejected the transaction
313+
final TransactionResponse response = parseResponse(responseBuffer);
314+
assertThat(response.nodeTransactionPrecheckCode()).isEqualTo(FAIL_INVALID);
315+
// And the cost will be zero
316+
assertThat(response.cost()).isZero();
308317
// And the transaction is not submitted to the platform
309318
verify(submissionManager, never()).submit(any(), any());
310319
}
@@ -334,16 +343,20 @@ void testSubmitFails() throws PreCheckException, ParseException {
334343

335344
@Test
336345
@DisplayName("If some random exception is thrown from submitting to the platform, the exception is bubbled up")
337-
void randomException() throws PreCheckException {
346+
void randomException() throws PreCheckException, ParseException {
338347
// Given a SubmissionManager that will throw a RuntimeException from submit
339348
doThrow(new RuntimeException("submit exception"))
340349
.when(submissionManager)
341350
.submit(any(), any());
342351

343-
// When the transaction is submitted, then the exception is bubbled up
344-
assertThatThrownBy(() -> workflow.submitTransaction(requestBuffer, responseBuffer))
345-
.isInstanceOf(RuntimeException.class)
346-
.hasMessageContaining("submit exception");
352+
// When the transaction is submitted
353+
workflow.submitTransaction(requestBuffer, responseBuffer);
354+
355+
// Then the response will indicate the platform rejected the transaction
356+
final TransactionResponse response = parseResponse(responseBuffer);
357+
assertThat(response.nodeTransactionPrecheckCode()).isEqualTo(FAIL_INVALID);
358+
// And the cost will be zero
359+
assertThat(response.cost()).isZero();
347360
}
348361
}
349362

hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoDeleteSuite.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ final HapiSpec mustSpecifyTargetId() {
209209
.then(cryptoDelete("0.0.0")
210210
.sansTargetId()
211211
.signedBy(DEFAULT_PAYER)
212-
.hasKnownStatus(ACCOUNT_ID_DOES_NOT_EXIST));
212+
.hasPrecheck(ACCOUNT_ID_DOES_NOT_EXIST));
213213
}
214214

215215
@HapiTest

hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenTransactSpecs.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,7 @@ public HapiSpec missingEntitiesRejected() {
674674
cryptoTransfer(moving(100_000_000_000_000L, SENTINEL_ACCOUNT)
675675
.between(DEFAULT_PAYER, FUNDING))
676676
.signedBy(DEFAULT_PAYER)
677-
.hasKnownStatus(INVALID_TOKEN_ID));
677+
.hasPrecheck(INVALID_TOKEN_ID));
678678
}
679679

680680
@HapiTest

0 commit comments

Comments
 (0)