11/*
2- * Copyright 2020-2024 IEXEC BLOCKCHAIN TECH
2+ * Copyright 2020-2025 IEXEC BLOCKCHAIN TECH
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1616
1717package com .iexec .blockchain .chain ;
1818
19+ import com .iexec .blockchain .command .generic .SubmittedTx ;
1920import com .iexec .commons .poco .chain .*;
21+ import com .iexec .commons .poco .encoding .PoCoDataEncoder ;
2022import com .iexec .commons .poco .utils .BytesUtils ;
2123import com .iexec .commons .poco .utils .HashUtils ;
24+ import io .micrometer .core .instrument .Counter ;
25+ import io .micrometer .core .instrument .Metrics ;
26+ import lombok .extern .slf4j .Slf4j ;
2227import org .apache .commons .lang3 .StringUtils ;
2328import org .springframework .stereotype .Service ;
2429import org .web3j .protocol .core .methods .response .TransactionReceipt ;
30+ import org .web3j .protocol .exceptions .TransactionException ;
2531
32+ import java .io .IOException ;
2633import java .math .BigInteger ;
2734import java .nio .charset .StandardCharsets ;
28- import java .time .Instant ;
29- import java .time .temporal .ChronoUnit ;
3035import java .util .Date ;
3136
3237import static com .iexec .commons .poco .utils .BytesUtils .stringToBytes ;
3338
39+ @ Slf4j
3440@ Service
3541public class IexecHubService extends IexecHubAbstractService {
3642
43+ private final ChainConfig chainConfig ;
44+ private final SignerService signerService ;
45+ private final Web3jService web3jService ;
46+ private final Counter failureCounter = Metrics .counter ("iexec.poco.transaction" , "status" , "failure" );
47+ private final Counter successCounter = Metrics .counter ("iexec.poco.transaction" , "status" , "success" );
48+
3749 public IexecHubService (final SignerService signerService ,
3850 final Web3jService web3jService ,
3951 final ChainConfig chainConfig ) {
@@ -42,6 +54,9 @@ public IexecHubService(final SignerService signerService,
4254 web3jService ,
4355 chainConfig .getHubAddress ()
4456 );
57+ this .chainConfig = chainConfig ;
58+ this .signerService = signerService ;
59+ this .web3jService = web3jService ;
4560 }
4661
4762 public static boolean isByte32 (final String hexString ) {
@@ -51,12 +66,9 @@ public static boolean isByte32(final String hexString) {
5166
5267 public TransactionReceipt initializeTask (final String chainDealId ,
5368 final int taskIndex ) throws Exception {
54- addLatency ();
55- return iexecHubContract
56- .initialize (
57- stringToBytes (chainDealId ),
58- BigInteger .valueOf (taskIndex ))
59- .send ();
69+ final String txData = PoCoDataEncoder .encodeInitialize (chainDealId , taskIndex );
70+ final SubmittedTx submittedTx = submit ("initialize" , txData );
71+ return waitForTxMined (submittedTx );
6072 }
6173
6274 public TransactionReceipt contribute (final String chainTaskId ,
@@ -67,59 +79,64 @@ public TransactionReceipt contribute(final String chainTaskId,
6779 final String resultHash = HashUtils .concatenateAndHash (chainTaskId , resultDigest );
6880 final String resultSeal = HashUtils .concatenateAndHash (credentials .getAddress (), chainTaskId , resultDigest );
6981
70- return iexecHubContract
71- .contribute (
72- stringToBytes (chainTaskId ),
73- stringToBytes (resultHash ),
74- stringToBytes (resultSeal ),
75- enclaveChallenge ,
76- stringToBytes (enclaveSignature ),
77- stringToBytes (workerpoolSignature ))
78- .send ();
82+ final String txData = PoCoDataEncoder .encodeContribute (
83+ chainTaskId , resultHash , resultSeal , enclaveChallenge , enclaveSignature , workerpoolSignature );
84+ final SubmittedTx submittedTx = submit ("contribute" , txData );
85+ return waitForTxMined (submittedTx );
7986 }
8087
8188
8289 public TransactionReceipt reveal (final String chainTaskId ,
8390 final String resultDigest ) throws Exception {
84- return iexecHubContract
85- .reveal (
86- stringToBytes (chainTaskId ),
87- stringToBytes (resultDigest ))
88- .send ();
91+ final String txData = PoCoDataEncoder .encodeReveal (chainTaskId , resultDigest );
92+ final SubmittedTx submittedTx = submit ("reveal" , txData );
93+ return waitForTxMined (submittedTx );
8994 }
9095
9196 public TransactionReceipt finalizeTask (final String chainTaskId ,
9297 final String resultLink ,
9398 final String callbackData ) throws Exception {
94- addLatency ();
95- byte [] results = StringUtils .isNotEmpty (resultLink ) ?
99+ final byte [] results = StringUtils .isNotEmpty (resultLink ) ?
96100 resultLink .getBytes (StandardCharsets .UTF_8 ) : new byte [0 ];
97- byte [] resultsCallback = StringUtils .isNotEmpty (callbackData ) ?
101+ final byte [] resultsCallback = StringUtils .isNotEmpty (callbackData ) ?
98102 stringToBytes (callbackData ) : new byte [0 ];
99103
100- return iexecHubContract
101- .finalize (
102- stringToBytes (chainTaskId ),
103- results ,
104- resultsCallback )
105- .send ();
104+ final String txData = PoCoDataEncoder .encodeFinalize (chainTaskId , results , resultsCallback );
105+ final SubmittedTx submittedTx = submit ("finalize" , txData );
106+ return waitForTxMined (submittedTx );
106107 }
107108
108109 /**
109- * Synchronized sleep to ensure several transactions will never be sent in the same time interval .
110+ * Submits the transaction to the blockchain network mem-pool .
110111 * <p>
111- * This synchronized sleep is required for nonce computation on pending block .
112+ * This method can be {@code synchronized} as there is only a single {@code IexecHubService} instance .
112113 * When a first transaction will be emitted, it will be emitted and registered on the pending block.
113114 * After a latency, a second transaction can be sent on the pending block and the nonce will be computed successfully.
114115 * With a correct nonce, it becomes possible to perform several transactions from the same wallet in the same block.
115- *
116- * @throws InterruptedException if the calling thread is interrupted
117116 */
118- private synchronized void addLatency () throws InterruptedException {
119- final long deadline = Instant .now ().plus (1L , ChronoUnit .SECONDS ).toEpochMilli ();
120- while (Instant .now ().toEpochMilli () < deadline ) {
121- wait (deadline - Instant .now ().toEpochMilli ());
117+ private synchronized SubmittedTx submit (final String function , final String txData ) throws IOException {
118+ final BigInteger nonce = signerService .getNonce ();
119+ final BigInteger gasLimit = switch (function ) {
120+ case "initialize" -> signerService .estimateGas (chainConfig .getHubAddress (), txData );
121+ case "finalize" -> signerService .estimateGas (chainConfig .getHubAddress (), txData ).add (getCallbackGas ());
122+ default -> PoCoDataEncoder .getGasLimitForFunction (function );
123+ };
124+ final String txHash = signerService .signAndSendTransaction (
125+ nonce , web3jService .getUserGasPrice (), gasLimit , chainConfig .getHubAddress (), txData );
126+ log .info ("Transaction submitted [nonce:{}, hash:{}]" , nonce , txHash );
127+ return new SubmittedTx (nonce , gasLimit , txData , txHash );
128+ }
129+
130+ private TransactionReceipt waitForTxMined (final SubmittedTx submittedTx ) throws IOException , TransactionException {
131+ final TransactionReceipt receipt = txReceiptProcessor .waitForTransactionReceipt (submittedTx .hash ());
132+ log .info ("Transaction receipt [nonce:{}, hash:{}, status:{}, revert-reason:{}]" ,
133+ submittedTx .nonce (), submittedTx .hash (), receipt .getStatus (), receipt .getRevertReason ());
134+ if (!receipt .isStatusOK ()) {
135+ failureCounter .increment ();
136+ } else {
137+ successCounter .increment ();
122138 }
139+ return receipt ;
123140 }
124141
125142 public boolean hasEnoughGas () {
0 commit comments