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"