diff --git a/eosiojava/build.gradle b/eosiojava/build.gradle
index 4092474..6aa59b2 100644
--- a/eosiojava/build.gradle
+++ b/eosiojava/build.gradle
@@ -31,6 +31,7 @@ dependencies {
api 'com.google.code.gson:gson:2.8.5'
api 'org.bouncycastle:bcprov-jdk15on:1.61'
api 'org.bouncycastle:bcpkix-jdk15on:1.61'
+ api 'com.vdurmont:semver4j:3.1.0'
// This works on android and non-android, but is necessary to keep us to 1.7 targets.
api 'com.google.guava:guava:27.1-android'
testCompile 'junit:junit:4.12'
@@ -47,7 +48,7 @@ test {
def libraryGroupId = 'one.block'
def libraryArtifactId = 'eosiojava'
-def libraryVersion = '1.0.0'
+def libraryVersion = '1.0.1'
task sourcesJar(type: Jar, dependsOn: classes){
classifier = 'sources'
diff --git a/eosiojava/src/main/java/one/block/eosiojava/error/ErrorConstants.java b/eosiojava/src/main/java/one/block/eosiojava/error/ErrorConstants.java
index 6faa3df..d1222b3 100644
--- a/eosiojava/src/main/java/one/block/eosiojava/error/ErrorConstants.java
+++ b/eosiojava/src/main/java/one/block/eosiojava/error/ErrorConstants.java
@@ -166,6 +166,11 @@ private ErrorConstants(){
*/
public static final String TRANSACTION_PROCESSOR_PREPARE_RPC_GET_BLOCK_INFO = "Error happened on calling GetBlockInfo RPC.";
+ /**
+ * Error message get thrown if {@link IRPCProvider#getBlock(GetBlockRequest)} thrown exception during process of {@link TransactionProcessor#prepare(List)}
+ */
+ public static final String TRANSACTION_PROCESSOR_PREPARE_RPC_GET_BLOCK = "Error happened on calling GetBlock RPC.";
+
/**
* Error message get thrown if chain id from {@link GetInfoResponse#getChainId()} does not match with the input chain id
*/
diff --git a/eosiojava/src/main/java/one/block/eosiojava/interfaces/IRPCProvider.java b/eosiojava/src/main/java/one/block/eosiojava/interfaces/IRPCProvider.java
index 394f195..c78e6c8 100644
--- a/eosiojava/src/main/java/one/block/eosiojava/interfaces/IRPCProvider.java
+++ b/eosiojava/src/main/java/one/block/eosiojava/interfaces/IRPCProvider.java
@@ -1,16 +1,19 @@
package one.block.eosiojava.interfaces;
import one.block.eosiojava.error.rpcProvider.GetBlockInfoRpcError;
+import one.block.eosiojava.error.rpcProvider.GetBlockRpcError;
import one.block.eosiojava.error.rpcProvider.GetInfoRpcError;
import one.block.eosiojava.error.rpcProvider.GetRawAbiRpcError;
import one.block.eosiojava.error.rpcProvider.GetRequiredKeysRpcError;
import one.block.eosiojava.error.rpcProvider.SendTransactionRpcError;
import one.block.eosiojava.models.rpcProvider.request.GetBlockInfoRequest;
+import one.block.eosiojava.models.rpcProvider.request.GetBlockRequest;
import one.block.eosiojava.models.rpcProvider.request.GetRawAbiRequest;
import one.block.eosiojava.models.rpcProvider.request.GetRequiredKeysRequest;
import one.block.eosiojava.models.rpcProvider.request.SendTransactionRequest;
import one.block.eosiojava.models.rpcProvider.response.GetBlockInfoResponse;
+import one.block.eosiojava.models.rpcProvider.response.GetBlockResponse;
import one.block.eosiojava.models.rpcProvider.response.GetInfoResponse;
import one.block.eosiojava.models.rpcProvider.response.GetRawAbiResponse;
import one.block.eosiojava.models.rpcProvider.response.GetRequiredKeysResponse;
@@ -43,6 +46,17 @@ public interface IRPCProvider {
@NotNull
GetBlockInfoResponse getBlockInfo(GetBlockInfoRequest getBlockInfoRequest) throws GetBlockInfoRpcError;
+ /**
+ * Returns an object containing various details about a specific block on the blockchain.
+ *
+ * @param getBlockRequest Info of a specific block.
+ * @return the info/status of a specific block in the request
+ * @throws GetBlockRpcError thrown if there are any exceptions/backend error during the
+ * getBlockInfo() process.
+ */
+ @NotNull
+ GetBlockResponse getBlock(GetBlockRequest getBlockRequest) throws GetBlockRpcError;
+
/**
* Gets raw abi for a given contract.
*
diff --git a/eosiojava/src/main/java/one/block/eosiojava/models/rpcProvider/TransactionConfig.java b/eosiojava/src/main/java/one/block/eosiojava/models/rpcProvider/TransactionConfig.java
index 1a3e35e..af30322 100644
--- a/eosiojava/src/main/java/one/block/eosiojava/models/rpcProvider/TransactionConfig.java
+++ b/eosiojava/src/main/java/one/block/eosiojava/models/rpcProvider/TransactionConfig.java
@@ -29,6 +29,18 @@ public class TransactionConfig {
*/
private static final boolean DEFAULT_USE_LAST_IRREVERSIBLE = true;
+ /**
+ * Default chain version string to use if none is specified and we cannot get the server version from
+ * the chain itself.
+ */
+ private static final String DEFAULT_CHAIN_VERSION_STRING = "2.0.0";
+
+ /**
+ * Chain version at which {@link one.block.eosiojava.interfaces.IRPCProvider#getBlockInfo(GetBlockInfoRequest)}
+ * became available.
+ */
+ private static final String GET_BLOCK_INFO_AVAILABLE_STRING = "2.1.0";
+
/**
* The Expires seconds.
*
@@ -55,6 +67,15 @@ public class TransactionConfig {
*/
private boolean useLastIrreversible = DEFAULT_USE_LAST_IRREVERSIBLE;
+ /**
+ * Version of nodeos that the transaction is targeting. This will allow the library to work with 2.1+ version
+ * chains using {@link one.block.eosiojava.interfaces.IRPCProvider#getBlockInfo(GetBlockInfoRequest)} for
+ * calculating TAPOS rather than {@link one.block.eosiojava.interfaces.IRPCProvider#getBlock(GetBlockInfoRequest)}.
+ * If the value is left unset, the transaction will determine the chain version from the {@link IRPCProvider#getInfo()} call
+ * and use that as the chain version string.
+ */
+ private String chainVersionString = null;
+
/**
* Gets the expiration time for the transaction.
*
@@ -117,4 +138,41 @@ public void setBlocksBehind(int blocksBehind) {
public void setUseLastIrreversible(boolean useLastIrreversible) {
this.useLastIrreversible = useLastIrreversible;
}
+
+ /**
+ * Gets the current chain version that the transaction is targeting.
+ *
+ * 2.1+ version chains will use {@link one.block.eosiojava.interfaces.IRPCProvider#getBlockInfo(GetBlockInfoRequest)} for
+ * calculating TAPOS rather than {@link one.block.eosiojava.interfaces.IRPCProvider#getBlock(GetBlockRequest)}. If
+ * the value is left unset, the transaction will determine the chain version from the {@link IRPCProvider#getInfo()} call
+ * and use that as the chain version string.
+ * @return chainVersionString current nodeos version that we are targeting for this transaction
+ */
+ public String getChainVersionString() { return chainVersionString; }
+
+ /**
+ * Sets the current chain version that the transaction is targeting.
+ *
+ * 2.1+ version chains will use {@link one.block.eosiojava.interfaces.IRPCProvider#getBlockInfo(GetBlockInfoRequest)} for
+ * calculating TAPOS rather than {@link one.block.eosiojava.interfaces.IRPCProvider#getBlock(GetBlockRequest)}. If
+ * the value is left unset, the transaction will determine the chain version from the {@link IRPCProvider#getInfo()} call
+ * and use that as the chain version string.
+ * @param chainVersionString set the target nodeos version that this transaction is for
+ */
+ public void setChainVersionString(String chainVersionString) { this.chainVersionString = chainVersionString; }
+
+ /**
+ * Get the default chain version string to use for transactions if one is not specified and the version cannot
+ * be read from the chain itself.
+ * @return defaultChainVersionString the version of nodeos if one is not set and the version cannot be read from the chain itself.
+ */
+ public String getDefaultChainVersionString() { return DEFAULT_CHAIN_VERSION_STRING; }
+
+ /**
+ * Chain version at which {@link one.block.eosiojava.interfaces.IRPCProvider#getBlockInfo(GetBlockInfoRequest)}
+ * became available.
+ * @return getBlockInfoAvailableString the version of nodeos where {@link one.block.eosiojava.interfaces.IRPCProvider#getBlockInfo(GetBlockInfoRequest)}
+ * became available.
+ */
+ public String getGetBlockInfoAvailableString() { return GET_BLOCK_INFO_AVAILABLE_STRING; }
}
diff --git a/eosiojava/src/main/java/one/block/eosiojava/models/rpcProvider/request/GetBlockRequest.java b/eosiojava/src/main/java/one/block/eosiojava/models/rpcProvider/request/GetBlockRequest.java
index 892e87e..ccfbca4 100644
--- a/eosiojava/src/main/java/one/block/eosiojava/models/rpcProvider/request/GetBlockRequest.java
+++ b/eosiojava/src/main/java/one/block/eosiojava/models/rpcProvider/request/GetBlockRequest.java
@@ -4,7 +4,7 @@
import org.jetbrains.annotations.NotNull;
/**
- * The request class for getBlock() RPC call {@link one.block.eosiojava.interfaces.IRPCProvider#getBlockInfo(GetBlockInfoRequest)}
+ * The request class for getBlock() RPC call {@link one.block.eosiojava.interfaces.IRPCProvider#getBlock(GetBlockRequest)}
*/
public class GetBlockRequest {
diff --git a/eosiojava/src/main/java/one/block/eosiojava/session/TransactionProcessor.java b/eosiojava/src/main/java/one/block/eosiojava/session/TransactionProcessor.java
index 745e8d1..cd94a44 100644
--- a/eosiojava/src/main/java/one/block/eosiojava/session/TransactionProcessor.java
+++ b/eosiojava/src/main/java/one/block/eosiojava/session/TransactionProcessor.java
@@ -7,9 +7,13 @@
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
+
+import com.vdurmont.semver4j.Semver;
+import com.vdurmont.semver4j.SemverException;
import one.block.eosiojava.error.ErrorConstants;
import one.block.eosiojava.error.abiProvider.GetAbiError;
import one.block.eosiojava.error.rpcProvider.GetBlockInfoRpcError;
+import one.block.eosiojava.error.rpcProvider.GetBlockRpcError;
import one.block.eosiojava.error.rpcProvider.GetInfoRpcError;
import one.block.eosiojava.error.rpcProvider.GetRequiredKeysRpcError;
import one.block.eosiojava.error.rpcProvider.SendTransactionRpcError;
@@ -51,9 +55,15 @@
import one.block.eosiojava.models.rpcProvider.ContextFreeData;
import one.block.eosiojava.models.rpcProvider.TransactionConfig;
import one.block.eosiojava.models.rpcProvider.request.GetBlockInfoRequest;
+import one.block.eosiojava.models.rpcProvider.request.GetBlockRequest;
import one.block.eosiojava.models.rpcProvider.request.GetRequiredKeysRequest;
import one.block.eosiojava.models.rpcProvider.request.SendTransactionRequest;
-import one.block.eosiojava.models.rpcProvider.response.*;
+
+import one.block.eosiojava.models.rpcProvider.response.GetBlockInfoResponse;
+import one.block.eosiojava.models.rpcProvider.response.GetBlockResponse;
+import one.block.eosiojava.models.rpcProvider.response.GetInfoResponse;
+import one.block.eosiojava.models.rpcProvider.response.GetRequiredKeysResponse;
+import one.block.eosiojava.models.rpcProvider.response.SendTransactionResponse;
import one.block.eosiojava.models.signatureProvider.EosioTransactionSignatureRequest;
import one.block.eosiojava.models.signatureProvider.EosioTransactionSignatureResponse;
import one.block.eosiojava.utilities.DateFormatter;
@@ -203,6 +213,13 @@ public class TransactionProcessor {
*/
private static final String PACKED_TRANSACTION_V0_PREFIX = "00";
+ /**
+ * Index for the first character of a string, which in a version returned from
+ * the blockchain may contain a 'v'. If it does we will remove it before comparing
+ * versions against the transaction targeted version.
+ */
+ private static final int CHAR_INDEX_V_PREFIX = 0;
+
/**
* Constructor with all provider references from {@link TransactionSession}
* @param serializationProvider the serialization provider.
@@ -374,6 +391,15 @@ public void prepare(@NotNull List actions, @NotNull List context
getInfoResponse.getChainId()));
}
+ String chainVersionString = this.transactionConfig.getChainVersionString();
+ if (Strings.isNullOrEmpty(chainVersionString)) {
+ if (Strings.isNullOrEmpty(getInfoResponse.getServerVersion())) {
+ chainVersionString = this.transactionConfig.getDefaultChainVersionString();
+ } else {
+ chainVersionString = getInfoResponse.getServerVersionString();
+ }
+ }
+
// Assigning value to refBlockNum and refBlockPrefix
BigInteger taposBlockNum;
@@ -393,20 +419,55 @@ public void prepare(@NotNull List actions, @NotNull List context
}
}
- GetBlockInfoResponse getBlockInfoResponse;
+
+ boolean useGetBlockInfo = false;
+ // Remove any leading non-digit character.
+ if (!Character.isDigit(chainVersionString.charAt(CHAR_INDEX_V_PREFIX))) {
+ chainVersionString = chainVersionString.substring(1);
+ }
try {
- getBlockInfoResponse = this.rpcProvider
- .getBlockInfo(new GetBlockInfoRequest(taposBlockNum));
- } catch (GetBlockInfoRpcError getBlockInfoRpcError) {
- throw new TransactionPrepareRpcError(
- ErrorConstants.TRANSACTION_PROCESSOR_PREPARE_RPC_GET_BLOCK_INFO, getBlockInfoRpcError);
+ // Strip any kind of beta or rc suffix.
+ Semver chainVersion = new Semver(chainVersionString).withClearedSuffix();
+ Semver getBlockInfoAvailableVersion = new Semver(this.transactionConfig.getGetBlockInfoAvailableString());
+ useGetBlockInfo = chainVersion.isGreaterThanOrEqualTo(getBlockInfoAvailableVersion);
+ } catch (SemverException semverException) {
+ // Default back to getBlock as the safer alternative for now.
+ useGetBlockInfo = false;
+ }
+
+ String strHeadBlockTime;
+ BigInteger blockNum;
+ BigInteger refBlockPrefix;
+
+ if (useGetBlockInfo) {
+ GetBlockInfoResponse getBlockInfoResponse;
+ try {
+ getBlockInfoResponse = this.rpcProvider
+ .getBlockInfo(new GetBlockInfoRequest(taposBlockNum));
+ strHeadBlockTime = getBlockInfoResponse.getTimestamp();
+ blockNum = getBlockInfoResponse.getBlockNum();
+ refBlockPrefix = getBlockInfoResponse.getRefBlockPrefix();
+ } catch (GetBlockInfoRpcError getBlockInfoRpcError) {
+ throw new TransactionPrepareRpcError(
+ ErrorConstants.TRANSACTION_PROCESSOR_PREPARE_RPC_GET_BLOCK_INFO, getBlockInfoRpcError);
+ }
+ } else {
+ GetBlockResponse getBlockResponse;
+ try {
+ getBlockResponse = this.rpcProvider
+ .getBlock(new GetBlockRequest(taposBlockNum.toString()));
+ strHeadBlockTime = getBlockResponse.getTimestamp();
+ blockNum = getBlockResponse.getBlockNum();
+ refBlockPrefix = getBlockResponse.getRefBlockPrefix();
+ } catch (GetBlockRpcError getBlockRpcError) {
+ throw new TransactionPrepareRpcError(
+ ErrorConstants.TRANSACTION_PROCESSOR_PREPARE_RPC_GET_BLOCK, getBlockRpcError);
+ }
}
// Calculate the expiration based on the taposBlockNum expiration
if (preparingTransaction.getExpiration().isEmpty()) {
- String strHeadBlockTime = getBlockInfoResponse.getTimestamp();
-
long taposBlockTime;
try {
@@ -424,8 +485,7 @@ public void prepare(@NotNull List actions, @NotNull List context
}
// Restrict the refBlockNum to 32 bit unsigned value
- BigInteger refBlockNum = getBlockInfoResponse.getBlockNum().and(BigInteger.valueOf(0xffff));
- BigInteger refBlockPrefix = getBlockInfoResponse.getRefBlockPrefix();
+ BigInteger refBlockNum = blockNum.and(BigInteger.valueOf(0xffff));
preparingTransaction.setRefBlockNum(refBlockNum);
preparingTransaction.setRefBlockPrefix(refBlockPrefix);
diff --git a/eosiojava/src/test/java/one/block/eosiojava/session/NegativeTransactionProcessorTest.java b/eosiojava/src/test/java/one/block/eosiojava/session/NegativeTransactionProcessorTest.java
index 2a88fe8..c4414c1 100644
--- a/eosiojava/src/test/java/one/block/eosiojava/session/NegativeTransactionProcessorTest.java
+++ b/eosiojava/src/test/java/one/block/eosiojava/session/NegativeTransactionProcessorTest.java
@@ -137,7 +137,7 @@ public void prepare_thenFailWithWrongDateFormat() throws TransactionPrepareError
String weirdHeadBlockTime = "2019-04-01TGM22:08:40.000";
String mockedGetInfoResponseWithWeirdDateFormat = "{\n"
- + " \"server_version\": \"1\",\n"
+ + " \"server_version\": \"2\",\n"
+ " \"chain_id\": \"sample chain id\",\n"
+ " \"head_block_num\": " + headBlockNum + ",\n"
+ " \"last_irreversible_block_num\": 1,\n"
@@ -149,7 +149,7 @@ public void prepare_thenFailWithWrongDateFormat() throws TransactionPrepareError
+ " \"virtual_block_net_limit\": 1,\n"
+ " \"block_cpu_limit\": 1,\n"
+ " \"block_net_limit\": 1,\n"
- + " \"server_version_string\": \"v1.3.0\"\n"
+ + " \"server_version_string\": \"v2.1.0\"\n"
+ "}";
String mockedGetBlockInfoResponseWithWeirdDateFormat = "{\n"
@@ -595,7 +595,7 @@ private List defaultActions() {
.and(BigInteger.valueOf(0xffff));
private static final String mockedGetInfoResponse = "{\n"
- + " \"server_version\": \"1\",\n"
+ + " \"server_version\": \"2\",\n"
+ " \"chain_id\": \"sample chain id\",\n"
+ " \"head_block_num\": " + headBlockNum + ",\n"
+ " \"last_irreversible_block_num\": 1,\n"
@@ -607,7 +607,7 @@ private List defaultActions() {
+ " \"virtual_block_net_limit\": 1,\n"
+ " \"block_cpu_limit\": 1,\n"
+ " \"block_net_limit\": 1,\n"
- + " \"server_version_string\": \"v1.3.0\"\n"
+ + " \"server_version_string\": \"v2.1.0\"\n"
+ "}";
private static final String mockedGetBlockInfoResponse = "{\n"
diff --git a/eosiojava/src/test/java/one/block/eosiojava/session/TransactionProcessorTest.java b/eosiojava/src/test/java/one/block/eosiojava/session/TransactionProcessorTest.java
index 6fab0a8..123782e 100644
--- a/eosiojava/src/test/java/one/block/eosiojava/session/TransactionProcessorTest.java
+++ b/eosiojava/src/test/java/one/block/eosiojava/session/TransactionProcessorTest.java
@@ -7,14 +7,18 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import one.block.eosiojava.error.abiProvider.GetAbiError;
import one.block.eosiojava.error.rpcProvider.GetBlockInfoRpcError;
+import one.block.eosiojava.error.rpcProvider.GetBlockRpcError;
import one.block.eosiojava.error.rpcProvider.GetInfoRpcError;
import one.block.eosiojava.error.rpcProvider.GetRequiredKeysRpcError;
import one.block.eosiojava.error.rpcProvider.SendTransactionRpcError;
@@ -42,6 +46,7 @@
import one.block.eosiojava.models.rpcProvider.Transaction;
import one.block.eosiojava.models.rpcProvider.TransactionConfig;
import one.block.eosiojava.models.rpcProvider.request.GetBlockInfoRequest;
+import one.block.eosiojava.models.rpcProvider.request.GetBlockRequest;
import one.block.eosiojava.models.rpcProvider.request.GetRequiredKeysRequest;
import one.block.eosiojava.models.rpcProvider.request.SendTransactionRequest;
import one.block.eosiojava.models.rpcProvider.response.*;
@@ -53,11 +58,11 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
+import org.junit.runners.Parameterized;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
-@RunWith(JUnit4.class)
+@RunWith(Parameterized.class)
public class TransactionProcessorTest {
private IRPCProvider mockedRpcProvider = mock(IRPCProvider.class);
@@ -66,6 +71,16 @@ public class TransactionProcessorTest {
private ISerializationProvider mockedSerializationProvider = mock(ISerializationProvider.class);
private TransactionSession session;
+ @Parameterized.Parameter(0)
+ public boolean runWithGetBlockInfo;
+
+ // creates the test data
+ @Parameterized.Parameters(name = "{index}: Test with runWithGetBlockInfo={0} ")
+ public static Collection