Trier.java
package hera.wallet.internal;
import static hera.util.ValidationUtils.assertNotNull;
import static org.slf4j.LoggerFactory.getLogger;
import hera.api.function.Function0;
import hera.api.function.Function2;
import hera.api.model.AccountAddress;
import hera.api.model.AccountState;
import hera.api.model.ChainIdHash;
import hera.api.model.TxHash;
import hera.api.model.internal.TryCountAndInterval;
import hera.api.transaction.NonceProvider;
import hera.client.AergoClient;
import hera.exception.RpcCommitException;
import hera.exception.RpcException;
import hera.key.Signer;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.slf4j.Logger;
@RequiredArgsConstructor
public class Trier {
protected final Logger logger = getLogger(getClass());
@Getter
@Setter
protected TryCountAndInterval tryCountAndInterval;
@Getter
@Setter
protected NonceProvider nonceProvider;
@Getter
@Setter
protected Function0<AergoClient> clientProvider;
/**
* Try transaction request with a requester. If {@code transactionRequester} fails, retry after
* refreshing nonce.
*
* @param signer a signer to sign transaction
* @param transactionRequester a transaction requester
* @return a transaction hash
*/
public TxHash request(final Signer signer,
final Function2<Signer, Long, TxHash> transactionRequester) {
assertNotNull(signer);
assertNotNull(transactionRequester);
logger.debug("Transaction try with signer: {}, transactionRequester: {}", signer,
transactionRequester);
TxHash txHash = null;
RpcException recentException = null;
int i = tryCountAndInterval.getCount();
while (0 <= i && null == txHash) {
final long nonceToBeUsed = nonceProvider.incrementAndGetNonce(signer.getPrincipal());
try {
txHash = transactionRequester.apply(signer, nonceToBeUsed);
} catch (RpcException e) {
recentException = e;
if (isNonceRelatedException(e)) {
logger.info("Request failed with invalid nonce.. try left: {}", i);
syncNonceOf(signer.getPrincipal());
} else if (isChainIdHashException(e)) {
logger.info("Request failed with invalid chain id hash.. try left: {}", i);
nonceProvider.bindNonce(signer.getPrincipal(), nonceToBeUsed - 1);
syncChainIdHash(clientProvider.apply());
} else {
throw e;
}
tryCountAndInterval.trySleep();
--i;
}
}
if (null == txHash && null != recentException) {
throw recentException;
}
return txHash;
}
protected boolean isNonceRelatedException(final RpcException e) {
if (!(e instanceof RpcCommitException)) {
return false;
}
final RpcCommitException cause = (RpcCommitException) e;
return cause.getCommitStatus() == RpcCommitException.CommitStatus.NONCE_TOO_LOW
|| cause.getCommitStatus() == RpcCommitException.CommitStatus.TX_HAS_SAME_NONCE;
}
protected boolean isChainIdHashException(final RpcException e) {
if (!(e instanceof RpcCommitException)) {
return false;
}
final RpcCommitException cause = (RpcCommitException) e;
// FIXME : no other way?
return cause.getMessage().indexOf("invalid chain id") != -1;
}
protected void syncNonceOf(final AccountAddress address) {
final AergoClient client = clientProvider.apply();
final AccountState recentState = client.getAccountOperation().getState(address);
nonceProvider.bindNonce(recentState);
}
protected void syncChainIdHash(final AergoClient client) {
// TODO: aergo client should have a role handling chain id hash
final ChainIdHash chainIdHash = client.getBlockchainOperation().getChainIdHash();
client.cacheChainIdHash(chainIdHash);
}
}