diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd78c4222..56634c6a2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## 2.29.0
+
+### Added
+- `populateAccountEvmAddress` and `populateAccountEvmAddressAsync` to `AccountId`
+
+### Fixed
+- duplicate transaction IDs at high TPS (>25K)
+- android compatibility issues
+
+### Changed
+- GRPC configurations
+
## 2.28.0
### Added
diff --git a/README.md b/README.md
index 5b1946c93..7acb68036 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@
Select _one_ of the following depending on your target platform.
```groovy
-implementation 'com.hedera.hashgraph:sdk:2.28.0'
+implementation 'com.hedera.hashgraph:sdk:2.29.0'
```
Select _one_ of the following to provide the gRPC implementation.
@@ -56,7 +56,7 @@ Select _one_ of the following depending on your target platform.
com.hedera.hashgraph
sdk
- 2.28.0
+ 2.29.0
```
diff --git a/build.gradle b/build.gradle
index c58870b4a..8b86ff6bf 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,10 +3,10 @@ plugins {
// Gradle plugin to discover dependency updates
//
- id "com.github.ben-manes.versions" version "0.47.0"
+ id "com.github.ben-manes.versions" version "0.48.0"
id "jacoco"
- id "org.sonarqube" version "4.3.0.3225"
+ id "org.sonarqube" version "4.3.1.3277"
id "io.github.gradle-nexus.publish-plugin" version "1.3.0"
}
@@ -28,10 +28,10 @@ allprojects {
dependencies {
// https://github.com/google/error-prone
// https://errorprone.info/
- errorprone "com.google.errorprone:error_prone_core:2.19.1"
+ errorprone "com.google.errorprone:error_prone_core:2.21.1"
// https://github.com/uber/NullAway
- errorprone "com.uber.nullaway:nullaway:0.10.11"
+ errorprone "com.uber.nullaway:nullaway:0.10.14"
// https://github.com/grpc/grpc-java-api-checker
errorprone "io.grpc:grpc-java-api-checker:1.1.0"
diff --git a/example-android/app/build.gradle b/example-android/app/build.gradle
index 6c6952653..edefbdae7 100644
--- a/example-android/app/build.gradle
+++ b/example-android/app/build.gradle
@@ -50,7 +50,7 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
- implementation 'com.hedera.hashgraph:sdk:2.28.0'
+ implementation 'com.hedera.hashgraph:sdk:2.29.0'
implementation 'org.slf4j:slf4j-simple:2.0.7'
implementation 'io.grpc:grpc-okhttp:1.49.2'
diff --git a/example-android/build.gradle b/example-android/build.gradle
index cd20c03ac..c314d7b3c 100644
--- a/example-android/build.gradle
+++ b/example-android/build.gradle
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.9.0'
+ ext.kotlin_version = '1.9.10'
repositories {
google()
@@ -10,7 +10,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.1.0'
+ classpath 'com.android.tools.build:gradle:8.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.quittle:android-emulator-plugin:0.4.5'
diff --git a/examples/build.gradle b/examples/build.gradle
index 678474347..04ef84045 100644
--- a/examples/build.gradle
+++ b/examples/build.gradle
@@ -2,10 +2,10 @@ dependencies {
implementation project(":sdk")
implementation "com.google.code.gson:gson:2.10.1"
- implementation "org.slf4j:slf4j-simple:2.0.7"
- implementation "io.grpc:grpc-netty-shaded:1.56.1"
+ implementation "org.slf4j:slf4j-simple:2.0.9"
+ implementation "io.grpc:grpc-netty-shaded:1.57.2"
implementation "io.github.cdimascio:java-dotenv:5.3.1"
- implementation "com.google.errorprone:error_prone_core:2.19.1"
+ implementation "com.google.errorprone:error_prone_core:2.21.1"
}
tasks.addRule("Pattern: run: Runs an example.") { String taskName ->
diff --git a/sdk/build.gradle b/sdk/build.gradle
index 39f59b0ff..f9b06e02e 100644
--- a/sdk/build.gradle
+++ b/sdk/build.gradle
@@ -4,7 +4,7 @@ plugins {
id "signing"
id "maven-publish"
id "com.google.protobuf" version "0.9.4"
- id "com.github.spotbugs" version "5.0.14"
+ id "com.github.spotbugs" version "5.1.3"
}
apply from: "../version.gradle"
@@ -32,42 +32,42 @@ dependencies {
implementation group: 'com.github.spotbugs', name: 'spotbugs-annotations', version: '4.7.3'
// https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15to18
- implementation group: 'org.bouncycastle', name: 'bcprov-jdk15to18', version: '1.75'
+ implementation group: 'org.bouncycastle', name: 'bcprov-jdk15to18', version: '1.76'
// https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15to18
- implementation group: 'org.bouncycastle', name: 'bcpkix-jdk15to18', version: '1.75'
+ implementation group: 'org.bouncycastle', name: 'bcpkix-jdk15to18', version: '1.76'
// https://mvnrepository.com/artifact/org.slf4j/slf4j-api
- implementation 'org.slf4j:slf4j-api:2.0.7'
+ implementation 'org.slf4j:slf4j-api:2.0.9'
- implementation "io.grpc:grpc-core:1.57.0"
- implementation "io.grpc:grpc-stub:1.56.1"
+ implementation "io.grpc:grpc-core:1.57.2"
+ implementation "io.grpc:grpc-stub:1.58.0"
implementation "com.google.code.gson:gson:2.10.1"
implementation 'javax.annotation:javax.annotation-api:1.3.2'
- implementation 'com.esaulpaugh:headlong:9.3.0'
+ implementation 'com.esaulpaugh:headlong:10.0.0'
testImplementation "org.assertj:assertj-core:3.24.2"
testImplementation "io.github.json-snapshot:json-snapshot:1.0.17"
testImplementation "org.junit.jupiter:junit-jupiter-engine:5.10.0"
testImplementation "org.junit.jupiter:junit-jupiter-params:5.10.0"
- testRuntimeOnly "org.slf4j:slf4j-simple:2.0.7"
- testRuntimeOnly "io.grpc:grpc-netty-shaded:1.56.1"
- testRuntimeOnly 'org.slf4j:slf4j-nop:2.0.7'
+ testRuntimeOnly "org.slf4j:slf4j-simple:2.0.9"
+ testRuntimeOnly "io.grpc:grpc-netty-shaded:1.57.2"
+ testRuntimeOnly 'org.slf4j:slf4j-nop:2.0.9'
- integrationTestRuntimeOnly 'io.grpc:grpc-netty-shaded:1.56.1'
- integrationTestRuntimeOnly 'org.slf4j:slf4j-nop:2.0.7'
+ integrationTestRuntimeOnly 'io.grpc:grpc-netty-shaded:1.57.2'
+ integrationTestRuntimeOnly 'org.slf4j:slf4j-nop:2.0.9'
}
// https://github.com/google/protobuf-gradle-plugin
protobuf {
protoc {
- artifact = "com.google.protobuf:protoc:3.23.4"
+ artifact = "com.google.protobuf:protoc:3.24.3"
}
plugins {
grpc {
- artifact = "io.grpc:protoc-gen-grpc-java:1.57.0"
+ artifact = "io.grpc:protoc-gen-grpc-java:1.58.0"
}
}
}
diff --git a/sdk/src/integrationTest/java/AccountIdPopulationIntegrationTest.java b/sdk/src/integrationTest/java/AccountIdPopulationIntegrationTest.java
index 4d1d5eb87..be0fb8e1f 100644
--- a/sdk/src/integrationTest/java/AccountIdPopulationIntegrationTest.java
+++ b/sdk/src/integrationTest/java/AccountIdPopulationIntegrationTest.java
@@ -60,4 +60,55 @@ void canPopulateAccountIdNumAsync() throws Exception {
assertThat(newAccountId.num).isEqualTo(accountId.num);
}
+
+ @Test
+ @DisplayName("Can populate AccountId evm address from mirror node (using sync method)")
+ void canPopulateAccountIdEvmAddressSync() throws Exception {
+ var testEnv = new IntegrationTestEnv(1);
+
+ var privateKey = PrivateKey.generateECDSA();
+ var publicKey = privateKey.getPublicKey();
+
+ var evmAddress = publicKey.toEvmAddress();
+ var evmAddressAccount = AccountId.fromEvmAddress(evmAddress);
+
+ var tx = new TransferTransaction().addHbarTransfer(evmAddressAccount, new Hbar(1))
+ .addHbarTransfer(testEnv.operatorId, new Hbar(-1)).execute(testEnv.client);
+
+ var receipt = new TransactionReceiptQuery().setTransactionId(tx.transactionId).setIncludeChildren(true)
+ .execute(testEnv.client);
+
+ var newAccountId = receipt.children.get(0).accountId;
+
+ Thread.sleep(5000);
+ var accountId = newAccountId.populateAccountEvmAddress(testEnv.client);
+
+ assertThat(evmAddressAccount.evmAddress).isEqualTo(accountId.evmAddress);
+ }
+
+ @Test
+ @DisplayName("Can populate AccountId evm address from mirror node (using async method)")
+ void canPopulateAccountIdEvmAddressAsync() throws Exception {
+ var testEnv = new IntegrationTestEnv(1);
+
+ var privateKey = PrivateKey.generateECDSA();
+ var publicKey = privateKey.getPublicKey();
+
+ var evmAddress = publicKey.toEvmAddress();
+ var evmAddressAccount = AccountId.fromEvmAddress(evmAddress);
+
+ var tx = new TransferTransaction().addHbarTransfer(evmAddressAccount, new Hbar(1))
+ .addHbarTransfer(testEnv.operatorId, new Hbar(-1)).execute(testEnv.client);
+
+ var receipt = new TransactionReceiptQuery().setTransactionId(tx.transactionId).setIncludeChildren(true)
+ .execute(testEnv.client);
+
+ var newAccountId = receipt.children.get(0).accountId;
+
+ Thread.sleep(5000);
+ var accountId = newAccountId.populateAccountEvmAddressAsync(testEnv.client).get();
+
+ assertThat(evmAddressAccount.evmAddress).isEqualTo(accountId.evmAddress);
+ }
+
}
diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/AccountId.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/AccountId.java
index f60991014..bd5178468 100644
--- a/sdk/src/main/java/com/hedera/hashgraph/sdk/AccountId.java
+++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/AccountId.java
@@ -332,6 +332,36 @@ public CompletableFuture populateAccountNumAsync(Client client) {
this.evmAddress));
}
+ /**
+ * Populates `evmAddress` field of the `AccountId` extracted from the Mirror Node.
+ * Sync version
+ *
+ * @param client
+ * @return populated AccountId instance
+ */
+ public AccountId populateAccountEvmAddress(Client client) throws ExecutionException, InterruptedException {
+ return populateAccountEvmAddressAsync(client).get();
+ }
+
+ /**
+ * Populates `evmAddress` field of the `AccountId` extracted from the Mirror Node.
+ * Async version
+ *
+ * @param client
+ * @return populated AccountId instance
+ */
+ public CompletableFuture populateAccountEvmAddressAsync(Client client) {
+ return EntityIdHelper.getEvmAddressFromMirrorNodeAsync(client, num)
+ .thenApply(evmAddressFromMirrorNode ->
+ new AccountId(
+ this.shard,
+ this.realm,
+ this.num,
+ this.checksum,
+ this.aliasKey,
+ evmAddressFromMirrorNode));
+ }
+
/**
* @param client to validate against
* @throws BadEntityIdException if entity ID is formatted poorly
diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/AccountInfo.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/AccountInfo.java
index 47f172946..463674855 100644
--- a/sdk/src/main/java/com/hedera/hashgraph/sdk/AccountInfo.java
+++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/AccountInfo.java
@@ -19,6 +19,8 @@
*/
package com.hedera.hashgraph.sdk;
+import static java.util.stream.Collectors.toList;
+
import com.google.common.base.MoreObjects;
import com.google.protobuf.InvalidProtocolBufferException;
import com.hedera.hashgraph.sdk.proto.CryptoGetInfoResponse;
@@ -261,7 +263,7 @@ static AccountInfo fromProtobuf(CryptoGetInfoResponse.AccountInfo accountInfo) {
var liveHashes = Arrays.stream(accountInfo.getLiveHashesList().toArray())
.map((liveHash) -> LiveHash.fromProtobuf((com.hedera.hashgraph.sdk.proto.LiveHash) liveHash))
- .toList();
+ .collect(toList());
Map relationships = new HashMap<>();
@@ -317,7 +319,7 @@ public static AccountInfo fromBytes(byte[] bytes) throws InvalidProtocolBufferEx
CryptoGetInfoResponse.AccountInfo toProtobuf() {
var hashes = Arrays.stream(liveHashes.toArray())
.map((liveHash) -> ((LiveHash) liveHash).toProtobuf())
- .toList();
+ .collect(toList());
var accountInfoBuilder = CryptoGetInfoResponse.AccountInfo.newBuilder()
.setAccountID(accountId.toProtobuf())
diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/Delayer.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/Delayer.java
index 33b53df59..cb187f221 100644
--- a/sdk/src/main/java/com/hedera/hashgraph/sdk/Delayer.java
+++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/Delayer.java
@@ -20,6 +20,8 @@
package com.hedera.hashgraph.sdk;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
@@ -33,6 +35,12 @@
final class Delayer {
private static final Logger logger = LoggerFactory.getLogger(Delayer.class);
+ private static final ScheduledExecutorService SCHEDULER = Executors.newSingleThreadScheduledExecutor(r -> {
+ Thread t = new Thread(r);
+ t.setDaemon(true);
+ return t;
+ });
+
private static final Duration MIN_DELAY = Duration.ofMillis(500);
/**
@@ -67,6 +75,10 @@ static CompletableFuture delayFor(long milliseconds, Executor executor) {
return CompletableFuture.runAsync(
() -> {
},
- CompletableFuture.delayedExecutor(milliseconds, TimeUnit.MILLISECONDS, executor));
+ delayedExecutor(milliseconds, TimeUnit.MILLISECONDS, executor));
+ }
+
+ private static Executor delayedExecutor(long delay, TimeUnit unit, Executor executor) {
+ return r -> SCHEDULER.schedule(() -> executor.execute(r), delay, unit);
}
}
diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java
index a24e9d066..0c8e13ca1 100644
--- a/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java
+++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/EntityIdHelper.java
@@ -298,6 +298,22 @@ public static CompletableFuture getAccountNumFromMirrorNodeAsync(Client cl
parseNumFromMirrorNodeResponse(response, "account"));
}
+ /**
+ * Get EvmAddress from mirror node using account num.
+ *
+ * @param client
+ * @param num
+ * @return
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ public static CompletableFuture getEvmAddressFromMirrorNodeAsync(Client client, long num) {
+ String apiEndpoint = "/accounts/" + num;
+ return performQueryToMirrorNodeAsync(client, apiEndpoint)
+ .thenApply(response ->
+ EvmAddress.fromString(parseEvmAddressFromMirrorNodeResponse(response, "evm_address")));
+ }
+
/**
* Get ContractId num from mirror node using evm address.
*
@@ -350,6 +366,15 @@ private static long parseNumFromMirrorNodeResponse(String responseBody, String m
return Long.parseLong(num.substring(num.lastIndexOf(".") + 1));
}
+ private static String parseEvmAddressFromMirrorNodeResponse(String responseBody, String memberName) {
+ JsonParser jsonParser = new JsonParser();
+ JsonObject jsonObject = jsonParser.parse(responseBody).getAsJsonObject();
+
+ String evmAddress = jsonObject.get(memberName).getAsString();
+
+ return evmAddress.substring(evmAddress.lastIndexOf(".") + 1);
+ }
+
@FunctionalInterface
interface WithIdNums {
R apply(long shard, long realm, long num, @Nullable String checksum);
diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/TransactionId.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/TransactionId.java
index 5dc8a939f..88ca2cd0f 100644
--- a/sdk/src/main/java/com/hedera/hashgraph/sdk/TransactionId.java
+++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/TransactionId.java
@@ -19,22 +19,21 @@
*/
package com.hedera.hashgraph.sdk;
+import static java.util.concurrent.CompletableFuture.completedFuture;
+import static java.util.concurrent.CompletableFuture.failedFuture;
+
import com.google.errorprone.annotations.Var;
import com.google.protobuf.InvalidProtocolBufferException;
import com.hedera.hashgraph.sdk.proto.TransactionID;
-import java.util.concurrent.CompletableFuture;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
-
-import javax.annotation.Nullable;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeoutException;
-
-import static java.util.concurrent.CompletableFuture.completedFuture;
-import static java.util.concurrent.CompletableFuture.failedFuture;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import javax.annotation.Nullable;
/**
* The client-generated ID for a transaction.
@@ -64,6 +63,13 @@ public final class TransactionId implements Comparable {
@Nullable
private Integer nonce = null;
+ private static final long NANOSECONDS_PER_MILLISECOND = 1_000_000L;
+
+ private static final long TIMESTAMP_INCREMENT_NANOSECONDS = 1_000L;
+
+ private static final AtomicLong monotonicTime = new AtomicLong();
+
+
/**
* No longer part of the public API. Use `Transaction.withValidStart()` instead.
*
@@ -97,8 +103,27 @@ public static TransactionId withValidStart(AccountId accountId, Instant validSta
* @return {@link com.hedera.hashgraph.sdk.TransactionId}
*/
public static TransactionId generate(AccountId accountId) {
- Instant instant = Clock.systemUTC().instant().minusNanos((long) (Math.random() * 5000000000L + 8000000000L));
- return new TransactionId(accountId, instant);
+ long currentTime;
+ long lastTime;
+
+ // Loop to ensure the generated timestamp is strictly increasing,
+ // and it handles the case where the system clock appears to move backward
+ // or if multiple threads attempt to generate a timestamp concurrently.
+ do {
+ // Get the current time in nanoseconds.
+ currentTime = System.currentTimeMillis() * NANOSECONDS_PER_MILLISECOND;
+
+ // Get the last recorded timestamp.
+ lastTime = monotonicTime.get();
+
+ // If the current time is less than or equal to the last recorded time,
+ // adjust the timestamp to ensure it is strictly increasing.
+ if (currentTime <= lastTime) {
+ currentTime = lastTime + TIMESTAMP_INCREMENT_NANOSECONDS;
+ }
+ } while (!monotonicTime.compareAndSet(lastTime, currentTime));
+
+ return new TransactionId(accountId, Instant.ofEpochSecond(0, currentTime));
}
/**
diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/ContractInfoTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/ContractInfoTest.snap
index af3e6e448..5875fdf23 100644
--- a/sdk/src/test/java/com/hedera/hashgraph/sdk/ContractInfoTest.snap
+++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/ContractInfoTest.snap
@@ -24,6 +24,7 @@ com.hedera.hashgraph.sdk.ContractInfoTest.toProtobuf=[
"memoizedSerializedSize": 0,
"isMutable": false
},
+ "bitField0_": 27,
"contractID_": {
"memoizedHashCode": 0,
"memoizedSerializedSize": 2147483647,
diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/ContractNonceInfoTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/ContractNonceInfoTest.snap
index 53e34851f..1e1cc8e76 100644
--- a/sdk/src/test/java/com/hedera/hashgraph/sdk/ContractNonceInfoTest.snap
+++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/ContractNonceInfoTest.snap
@@ -24,6 +24,7 @@ com.hedera.hashgraph.sdk.ContractNonceInfoTest.toProtobuf=[
"memoizedSerializedSize": 0,
"isMutable": false
},
+ "bitField0_": 1,
"contractId_": {
"memoizedHashCode": 0,
"memoizedSerializedSize": 2147483647,
diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/DuplicateTransactionTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/DuplicateTransactionTest.java
new file mode 100644
index 000000000..4c5fca41f
--- /dev/null
+++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/DuplicateTransactionTest.java
@@ -0,0 +1,24 @@
+package com.hedera.hashgraph.sdk;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+import java.util.HashSet;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+public class DuplicateTransactionTest {
+
+ @Test
+ @DisplayName("Should generate unique transaction ids")
+ void generateTransactionIds() {
+ TransactionId[] ids = new TransactionId[1000000];
+ AccountId accountId = AccountId.fromString("0.0.1000");
+ for (int i = 0; i < ids.length; ++i) {
+ ids[i] = TransactionId.generate(accountId);
+ }
+ HashSet set = new HashSet<>(ids.length);
+ for (int i = 0; i < ids.length; ++i) {
+ assertThat(set.add(ids[i])).as("ids[%d] is not unique", i).isTrue();
+ }
+ }
+}
diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/FreezeTransactionTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/FreezeTransactionTest.java
index fee6937a5..438333703 100644
--- a/sdk/src/test/java/com/hedera/hashgraph/sdk/FreezeTransactionTest.java
+++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/FreezeTransactionTest.java
@@ -19,24 +19,31 @@
*/
package com.hedera.hashgraph.sdk;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.google.protobuf.ByteString;
import com.hedera.hashgraph.sdk.proto.FreezeTransactionBody;
import com.hedera.hashgraph.sdk.proto.SchedulableTransactionBody;
-import com.hedera.hashgraph.sdk.proto.SystemUndeleteTransactionBody;
+import com.hedera.hashgraph.sdk.proto.Timestamp;
+import com.hedera.hashgraph.sdk.proto.TransactionBody;
import io.github.jsonSnapshot.SnapshotMatcher;
+import java.time.Instant;
+import java.util.Arrays;
import org.bouncycastle.util.encoders.Hex;
-import org.junit.AfterClass;
+import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
-import java.time.Instant;
-
-import java.util.Arrays;
-
-import static org.assertj.core.api.Assertions.assertThat;
public class FreezeTransactionTest {
private static final PrivateKey unusedPrivateKey = PrivateKey.fromString(
"302e020100300506032b657004220420db484b828e64b2d8f12ce3c0a0e93a0b8cce7af1bb8f39c97732394482538e10");
+ private static final FileId testFileId = FileId.fromString("4.5.6");
+ private static final byte[] testFileHash = Hex.decode("1723904587120938954702349857");
+ private static final FreezeType testFreezeType = FreezeType.TELEMETRY_UPGRADE;
+
final Instant validStart = Instant.ofEpochSecond(1554158542);
@BeforeAll
@@ -44,29 +51,22 @@ public static void beforeAll() {
SnapshotMatcher.start();
}
- @AfterClass
+ @AfterAll
public static void afterAll() {
SnapshotMatcher.validateSnapshots();
}
@Test
void shouldSerialize() {
- SnapshotMatcher.expect(spawnTestTransaction()
- .toString()
- ).toMatchSnapshot();
+ SnapshotMatcher.expect(spawnTestTransaction().toString()).toMatchSnapshot();
}
private FreezeTransaction spawnTestTransaction() {
- return new FreezeTransaction()
- .setNodeAccountIds(Arrays.asList(AccountId.fromString("0.0.5005"), AccountId.fromString("0.0.5006")))
+ return new FreezeTransaction().setNodeAccountIds(
+ Arrays.asList(AccountId.fromString("0.0.5005"), AccountId.fromString("0.0.5006")))
.setTransactionId(TransactionId.withValidStart(AccountId.fromString("0.0.5006"), validStart))
- .setFileId(FileId.fromString("4.5.6"))
- .setFileHash(Hex.decode("1723904587120938954702349857"))
- .setStartTime(validStart)
- .setFreezeType(FreezeType.FREEZE_ABORT)
- .setMaxTransactionFee(Hbar.fromTinybars(100_000))
- .freeze()
- .sign(unusedPrivateKey);
+ .setFileId(testFileId).setFileHash(testFileHash).setStartTime(validStart).setFreezeType(testFreezeType)
+ .setMaxTransactionFee(Hbar.fromTinybars(100_000)).freeze().sign(unusedPrivateKey);
}
@Test
@@ -79,11 +79,79 @@ void shouldBytes() throws Exception {
@Test
void fromScheduledTransaction() {
var transactionBody = SchedulableTransactionBody.newBuilder()
- .setFreeze(FreezeTransactionBody.newBuilder().build())
- .build();
+ .setFreeze(FreezeTransactionBody.newBuilder().build()).build();
var tx = Transaction.fromScheduledTransaction(transactionBody);
assertThat(tx).isInstanceOf(FreezeTransaction.class);
}
+
+ @Test
+ void constructFreezeTransactionFromTransactionBodyProtobuf() {
+ var transactionBody = FreezeTransactionBody.newBuilder().setUpdateFile(testFileId.toProtobuf())
+ .setFileHash(ByteString.copyFrom(testFileHash))
+ .setStartTime(Timestamp.newBuilder().setSeconds(validStart.getEpochSecond()))
+ .setFreezeType(testFreezeType.code);
+
+ var tx = TransactionBody.newBuilder().setFreeze(transactionBody).build();
+ var freezeTransaction = new FreezeTransaction(tx);
+
+ assertNotNull(freezeTransaction.getFileId());
+ assertThat(freezeTransaction.getFileId()).isEqualTo(testFileId);
+ assertThat(freezeTransaction.getFileHash()).isEqualTo(testFileHash);
+ assertNotNull(freezeTransaction.getStartTime());
+ assertThat(freezeTransaction.getStartTime().getEpochSecond()).isEqualTo(validStart.getEpochSecond());
+ assertThat(freezeTransaction.getFreezeType()).isEqualTo(testFreezeType);
+ }
+
+ @Test
+ void getSetFileId() {
+ var freezeTransaction = new FreezeTransaction().setFileId(testFileId);
+ assertNotNull(freezeTransaction.getFileId());
+ assertThat(freezeTransaction.getFileId()).isEqualTo(testFileId);
+ }
+
+ @Test
+ void getSetFileIdFrozen() {
+ var tx = spawnTestTransaction();
+ assertThrows(IllegalStateException.class, () -> tx.setFileId(testFileId));
+ }
+
+ @Test
+ void getSetFileHash() {
+ var freezeTransaction = new FreezeTransaction().setFileHash(testFileHash);
+ assertNotNull(freezeTransaction.getFileHash());
+ assertThat(freezeTransaction.getFileHash()).isEqualTo(testFileHash);
+ }
+
+ @Test
+ void getSetFileHashFrozen() {
+ var tx = spawnTestTransaction();
+ assertThrows(IllegalStateException.class, () -> tx.setFileHash(testFileHash));
+ }
+
+ @Test
+ void getSetStartTime() {
+ var freezeTransaction = new FreezeTransaction().setStartTime(validStart);
+ assertNotNull(freezeTransaction.getStartTime());
+ assertThat(freezeTransaction.getStartTime().getEpochSecond()).isEqualTo(validStart.getEpochSecond());
+ }
+
+ @Test
+ void getSetStartTimeFrozen() {
+ var tx = spawnTestTransaction();
+ assertThrows(IllegalStateException.class, () -> tx.setStartTime(validStart));
+ }
+
+ @Test
+ void getSetFreezeType() {
+ var freezeTransaction = new FreezeTransaction().setFreezeType(testFreezeType);
+ assertThat(freezeTransaction.getFreezeType()).isEqualTo(testFreezeType);
+ }
+
+ @Test
+ void getSetFreezeTypeFrozen() {
+ var tx = spawnTestTransaction();
+ assertThrows(IllegalStateException.class, () -> tx.setFreezeType(testFreezeType));
+ }
}
diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/FreezeTransactionTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/FreezeTransactionTest.snap
index 5d7faae92..ba3efc007 100644
--- a/sdk/src/test/java/com/hedera/hashgraph/sdk/FreezeTransactionTest.snap
+++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/FreezeTransactionTest.snap
@@ -1,3 +1,3 @@
com.hedera.hashgraph.sdk.FreezeTransactionTest.shouldSerialize=[
- "# com.hedera.hashgraph.sdk.proto.TransactionBody\nfreeze {\n file_hash: \"\\027#\\220E\\207\\022\\t8\\225G\\0024\\230W\"\n freeze_type: FREEZE_ABORT\n freeze_type_value: 4\n start_time {\n seconds: 1554158542\n }\n update_file {\n file_num: 6\n realm_num: 5\n shard_num: 4\n }\n}\nnode_account_i_d {\n account_num: 5005\n realm_num: 0\n shard_num: 0\n}\ntransaction_fee: 100000\ntransaction_i_d {\n account_i_d {\n account_num: 5006\n realm_num: 0\n shard_num: 0\n }\n transaction_valid_start {\n seconds: 1554158542\n }\n}\ntransaction_valid_duration {\n seconds: 120\n}"
+ "# com.hedera.hashgraph.sdk.proto.TransactionBody\nfreeze {\n file_hash: \"\\027#\\220E\\207\\022\\t8\\225G\\0024\\230W\"\n freeze_type: TELEMETRY_UPGRADE\n freeze_type_value: 5\n start_time {\n seconds: 1554158542\n }\n update_file {\n file_num: 6\n realm_num: 5\n shard_num: 4\n }\n}\nnode_account_i_d {\n account_num: 5005\n realm_num: 0\n shard_num: 0\n}\ntransaction_fee: 100000\ntransaction_i_d {\n account_i_d {\n account_num: 5006\n realm_num: 0\n shard_num: 0\n }\n transaction_valid_start {\n seconds: 1554158542\n }\n}\ntransaction_valid_duration {\n seconds: 120\n}"
]
\ No newline at end of file
diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/SystemDeleteTransactionTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/SystemDeleteTransactionTest.java
index 93c3075be..8c3f3b9e1 100644
--- a/sdk/src/test/java/com/hedera/hashgraph/sdk/SystemDeleteTransactionTest.java
+++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/SystemDeleteTransactionTest.java
@@ -19,23 +19,28 @@
*/
package com.hedera.hashgraph.sdk;
-import com.hedera.hashgraph.sdk.proto.FileUpdateTransactionBody;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
import com.hedera.hashgraph.sdk.proto.SchedulableTransactionBody;
import com.hedera.hashgraph.sdk.proto.SystemDeleteTransactionBody;
+import com.hedera.hashgraph.sdk.proto.TimestampSeconds;
+import com.hedera.hashgraph.sdk.proto.TransactionBody;
import io.github.jsonSnapshot.SnapshotMatcher;
+import java.time.Instant;
+import java.util.Arrays;
import org.junit.AfterClass;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
-import java.time.Instant;
-
-import java.util.Arrays;
-
-import static org.assertj.core.api.Assertions.assertThat;
public class SystemDeleteTransactionTest {
private static final PrivateKey unusedPrivateKey = PrivateKey.fromString(
"302e020100300506032b657004220420db484b828e64b2d8f12ce3c0a0e93a0b8cce7af1bb8f39c97732394482538e10");
-
+ private static final FileId testFileId = FileId.fromString("4.2.0");
+ private static final ContractId testContractId = ContractId.fromString("0.6.9");
final Instant validStart = Instant.ofEpochSecond(1554158542);
@BeforeAll
@@ -50,38 +55,28 @@ public static void afterAll() {
@Test
void shouldSerializeFile() {
- SnapshotMatcher.expect(spawnTestTransactionFile()
- .toString()
- ).toMatchSnapshot();
+ SnapshotMatcher.expect(spawnTestTransactionFile().toString()).toMatchSnapshot();
}
private SystemDeleteTransaction spawnTestTransactionFile() {
- return new SystemDeleteTransaction()
- .setNodeAccountIds(Arrays.asList(AccountId.fromString("0.0.5005"), AccountId.fromString("0.0.5006")))
+ return new SystemDeleteTransaction().setNodeAccountIds(
+ Arrays.asList(AccountId.fromString("0.0.5005"), AccountId.fromString("0.0.5006")))
.setTransactionId(TransactionId.withValidStart(AccountId.fromString("0.0.5006"), validStart))
- .setFileId(FileId.fromString("0.0.444"))
- .setExpirationTime(validStart)
- .setMaxTransactionFee(new Hbar(1))
- .freeze()
- .sign(unusedPrivateKey);
+ .setFileId(FileId.fromString("0.0.444")).setExpirationTime(validStart).setMaxTransactionFee(new Hbar(1))
+ .freeze().sign(unusedPrivateKey);
}
@Test
void shouldSerializeContract() {
- SnapshotMatcher.expect(spawnTestTransactionContract()
- .toString()
- ).toMatchSnapshot();
+ SnapshotMatcher.expect(spawnTestTransactionContract().toString()).toMatchSnapshot();
}
private SystemDeleteTransaction spawnTestTransactionContract() {
- return new SystemDeleteTransaction()
- .setNodeAccountIds(Arrays.asList(AccountId.fromString("0.0.5005"), AccountId.fromString("0.0.5006")))
+ return new SystemDeleteTransaction().setNodeAccountIds(
+ Arrays.asList(AccountId.fromString("0.0.5005"), AccountId.fromString("0.0.5006")))
.setTransactionId(TransactionId.withValidStart(AccountId.fromString("0.0.5006"), validStart))
- .setContractId(ContractId.fromString("0.0.444"))
- .setExpirationTime(validStart)
- .setMaxTransactionFee(new Hbar(1))
- .freeze()
- .sign(unusedPrivateKey);
+ .setContractId(ContractId.fromString("0.0.444")).setExpirationTime(validStart)
+ .setMaxTransactionFee(new Hbar(1)).freeze().sign(unusedPrivateKey);
}
@Test
@@ -101,11 +96,101 @@ void shouldBytesFile() throws Exception {
@Test
void fromScheduledTransaction() {
var transactionBody = SchedulableTransactionBody.newBuilder()
- .setSystemDelete(SystemDeleteTransactionBody.newBuilder().build())
- .build();
+ .setSystemDelete(SystemDeleteTransactionBody.newBuilder().build()).build();
var tx = Transaction.fromScheduledTransaction(transactionBody);
assertThat(tx).isInstanceOf(SystemDeleteTransaction.class);
}
+
+ @Test
+ void constructSystemDeleteTransactionFromTransactionBodyProtobuf() {
+ var transactionBodyWithFileId = SystemDeleteTransactionBody.newBuilder().setFileID(testFileId.toProtobuf())
+ .setExpirationTime(TimestampSeconds.newBuilder().setSeconds(validStart.getEpochSecond()));
+
+ var transactionBodyWithContractId = SystemDeleteTransactionBody.newBuilder()
+ .setContractID(testContractId.toProtobuf())
+ .setExpirationTime(TimestampSeconds.newBuilder().setSeconds(validStart.getEpochSecond()));
+
+ var txWithFileId = TransactionBody.newBuilder().setSystemDelete(transactionBodyWithFileId).build();
+ var systemDeleteTransactionWithFileId = new SystemDeleteTransaction(txWithFileId);
+
+ var txWithContractId = TransactionBody.newBuilder().setSystemDelete(transactionBodyWithContractId).build();
+ var systemDeleteTransactionWithContractId = new SystemDeleteTransaction(txWithContractId);
+
+ assertNotNull(systemDeleteTransactionWithFileId.getFileId());
+ assertThat(systemDeleteTransactionWithFileId.getFileId()).isEqualTo(testFileId);
+ assertNull(systemDeleteTransactionWithFileId.getContractId());
+ assertThat(systemDeleteTransactionWithFileId.getExpirationTime().getEpochSecond()).isEqualTo(
+ validStart.getEpochSecond());
+
+ assertNull(systemDeleteTransactionWithContractId.getFileId());
+ assertNotNull(systemDeleteTransactionWithContractId.getContractId());
+ assertThat(systemDeleteTransactionWithContractId.getContractId()).isEqualTo(testContractId);
+ assertThat(systemDeleteTransactionWithContractId.getExpirationTime().getEpochSecond()).isEqualTo(
+ validStart.getEpochSecond());
+ }
+
+ @Test
+ void getSetFileId() {
+ var systemDeleteTransaction = new SystemDeleteTransaction().setFileId(testFileId);
+ assertNotNull(systemDeleteTransaction.getFileId());
+ assertThat(systemDeleteTransaction.getFileId()).isEqualTo(testFileId);
+ }
+
+ @Test
+ void getSetFileIdFrozen() {
+ var tx = spawnTestTransactionFile();
+ assertThrows(IllegalStateException.class, () -> tx.setFileId(testFileId));
+ }
+
+ @Test
+ void getSetContractId() {
+ var systemDeleteTransaction = new SystemDeleteTransaction().setContractId(testContractId);
+ assertNotNull(systemDeleteTransaction.getContractId());
+ assertThat(systemDeleteTransaction.getContractId()).isEqualTo(testContractId);
+ }
+
+ @Test
+ void getSetContractIdFrozen() {
+ var tx = spawnTestTransactionContract();
+ assertThrows(IllegalStateException.class, () -> tx.setContractId(testContractId));
+ }
+
+ @Test
+ void getSetExpirationTime() {
+ var systemDeleteTransaction = new SystemDeleteTransaction().setExpirationTime(validStart);
+ assertNotNull(systemDeleteTransaction.getExpirationTime());
+ assertThat(systemDeleteTransaction.getExpirationTime().getEpochSecond()).isEqualTo(validStart.getEpochSecond());
+ }
+
+ @Test
+ void getSetExpirationTimeFrozen() {
+ var tx = spawnTestTransactionFile();
+ assertThrows(IllegalStateException.class, () -> tx.setExpirationTime(validStart));
+ }
+
+ // ported from C++ SDK, in Java it does not pass
+ @Test
+ @Disabled
+ void resetFileId() {
+ var systemDeleteTransaction = new SystemDeleteTransaction();
+ systemDeleteTransaction.setFileId(testFileId);
+ systemDeleteTransaction.setContractId(testContractId);
+
+ assertNull(systemDeleteTransaction.getFileId());
+ assertNotNull(systemDeleteTransaction.getContractId());
+ }
+
+ // ported from C++ SDK, in Java it does not pass
+ @Test
+ @Disabled
+ void resetContractId() {
+ var systemDeleteTransaction = new SystemDeleteTransaction();
+ systemDeleteTransaction.setContractId(testContractId);
+ systemDeleteTransaction.setFileId(testFileId);
+
+ assertNull(systemDeleteTransaction.getContractId());
+ assertNotNull(systemDeleteTransaction.getFileId());
+ }
}
diff --git a/version.gradle b/version.gradle
index 3da30a4d7..387605d1b 100644
--- a/version.gradle
+++ b/version.gradle
@@ -2,4 +2,4 @@
// and `sdk/build.gradle` so I extracted them into another file and made both aforementioned
// files import this one.
group = "com.hedera.hashgraph"
-version = "2.28.0"
+version = "2.29.0"