TransactionTrier.java
/*
* @copyright defined in LICENSE.txt
*/
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.Function1;
import hera.api.model.Account;
import hera.api.model.TxHash;
import hera.api.model.internal.TryCountAndInterval;
import hera.exception.RpcCommitException;
import hera.exception.WalletException;
import lombok.Getter;
import lombok.Setter;
import org.slf4j.Logger;
@Deprecated
public class TransactionTrier {
protected final Logger logger = getLogger(getClass());
protected final TryCountAndInterval tryCountAndInterval;
@Getter
@Setter
protected Function0<Account> accountProvider;
@Getter
@Setter
protected Runnable nonceSynchronizer;
@Getter
@Setter
protected Runnable chainIdSynchronizer;
public TransactionTrier(final TryCountAndInterval tryCountAndInterval) {
logger.debug("Binded nonce refresh: {}", tryCountAndInterval);
this.tryCountAndInterval = tryCountAndInterval;
}
/**
* Try transaction request with a requester. If {@code transactionRequester} fails, retry after
* refreshing nonce.
*
* @param transactionRequester a transaction requester
* @return a transaction hash
*/
public TxHash request(Function1<Long, TxHash> transactionRequester) {
return request(null, transactionRequester);
}
/**
* Try transaction request with a requester along with first trier. {@code transactionRequester}
* is invoked when {@code firstTrier} has failed. If {@code transactionRequester} fails, retry
* after refreshing nonce.
*
* @param firstTrier a first trier
* @param transactionRequester a transaction requester
* @return a transaction hash
*/
public TxHash request(Function0<TxHash> firstTrier,
Function1<Long, TxHash> transactionRequester) {
assertNotNull(transactionRequester);
logger.debug("Transaction try with firstTrier: {}, transactionRequester: {}", firstTrier,
transactionRequester);
TxHash txHash = null;
Exception exception = null;
boolean success = false;
if (null != firstTrier) {
try {
txHash = firstTrier.apply();
success = null != txHash;
} catch (Exception e) {
logger.debug("First try failure by {}", e.toString());
exception = e;
}
}
int i = tryCountAndInterval.getCount();
while (0 <= i && !success) {
try {
txHash = transactionRequester.apply(accountProvider.apply().incrementAndGetNonce());
success = true;
} catch (Exception e) {
if (isNonceRelatedException(e)) {
logger.info("Request failed with invalid nonce.. try left: {}", i);
nonceSynchronizer.run();
} else if (isChainIdHashException(e)) {
logger.info("Request failed with invalid chain id hash.. try left: {}", i);
chainIdSynchronizer.run();
nonceSynchronizer.run();
} else {
throw e;
}
exception = e;
tryCountAndInterval.trySleep();
--i;
}
}
if (!success) {
throw new WalletException(exception);
}
return txHash;
}
protected boolean isNonceRelatedException(final Exception 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 Exception e) {
if (!(e instanceof RpcCommitException)) {
return false;
}
final RpcCommitException cause = (RpcCommitException) e;
// FIXME : no another way?
return cause.getMessage().indexOf("invalid chain id") != -1;
}
}