diff --git a/.gitignore b/.gitignore index 39df3fb..f27f9d4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ build sdk/gradle.properties flowdb .DS_Store +.kotlin/errors \ No newline at end of file diff --git a/java-example/src/main/java/org/onflow/examples/java/getNodeVersionInfo/GetNodeVersionInfoAccessAPIConnector.java b/java-example/src/main/java/org/onflow/examples/java/getNodeVersionInfo/GetNodeVersionInfoAccessAPIConnector.java new file mode 100644 index 0000000..08d96d5 --- /dev/null +++ b/java-example/src/main/java/org/onflow/examples/java/getNodeVersionInfo/GetNodeVersionInfoAccessAPIConnector.java @@ -0,0 +1,23 @@ +package org.onflow.examples.java.getNodeVersionInfo; + +import org.onflow.flow.sdk.FlowAccessApi; +import org.onflow.flow.sdk.FlowNodeVersionInfo; + +public class GetNodeVersionInfoAccessAPIConnector { + private final FlowAccessApi accessAPI; + + public GetNodeVersionInfoAccessAPIConnector(FlowAccessApi accessAPI) { + this.accessAPI = accessAPI; + } + + public FlowNodeVersionInfo getNodeVersionInfo() { + FlowAccessApi.AccessApiCallResponse response = accessAPI.getNodeVersionInfo(); + if (response instanceof FlowAccessApi.AccessApiCallResponse.Success) { + return ((FlowAccessApi.AccessApiCallResponse.Success) response).getData(); + } else { + FlowAccessApi.AccessApiCallResponse.Error errorResponse = (FlowAccessApi.AccessApiCallResponse.Error) response; + throw new RuntimeException(errorResponse.getMessage(), errorResponse.getThrowable()); + } + } +} + diff --git a/java-example/src/test/java/org/onflow/examples/java/getNodeVersionInfo/GetNodeVersionInfoAccessAPIConnectorTest.java b/java-example/src/test/java/org/onflow/examples/java/getNodeVersionInfo/GetNodeVersionInfoAccessAPIConnectorTest.java new file mode 100644 index 0000000..57fb367 --- /dev/null +++ b/java-example/src/test/java/org/onflow/examples/java/getNodeVersionInfo/GetNodeVersionInfoAccessAPIConnectorTest.java @@ -0,0 +1,31 @@ +package org.onflow.examples.java.getNodeVersionInfo; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.onflow.flow.common.test.FlowEmulatorProjectTest; +import org.onflow.flow.common.test.FlowTestClient; +import org.onflow.flow.sdk.FlowAccessApi; +import org.onflow.flow.sdk.FlowNodeVersionInfo; + +import static org.junit.jupiter.api.Assertions.*; + +@FlowEmulatorProjectTest(flowJsonLocation = "../flow/flow.json") +public class GetNodeVersionInfoAccessAPIConnectorTest { + @FlowTestClient + private FlowAccessApi accessAPI; + private GetNodeVersionInfoAccessAPIConnector nodeVersionInfoConnector; + @BeforeEach + public void setup() { + nodeVersionInfoConnector = new GetNodeVersionInfoAccessAPIConnector(accessAPI); + } + + @Test + public void canFetchNodeVersionInfo() { + FlowNodeVersionInfo nodeVersionInfo = nodeVersionInfoConnector.getNodeVersionInfo(); + assertNotNull(nodeVersionInfo, "Node version info should not be null"); + assertEquals(nodeVersionInfo.getProtocolVersion(), 0); + assertEquals(nodeVersionInfo.getSporkRootBlockHeight(), 0); + assertEquals(nodeVersionInfo.getNodeRootBlockHeight(), 0); + assertNull(nodeVersionInfo.getCompatibleRange()); + } +} diff --git a/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/getNodeVersionInfo/GetNodeVersionInfoAccessAPIConnector.kt b/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/getNodeVersionInfo/GetNodeVersionInfoAccessAPIConnector.kt new file mode 100644 index 0000000..0e3c27e --- /dev/null +++ b/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/getNodeVersionInfo/GetNodeVersionInfoAccessAPIConnector.kt @@ -0,0 +1,14 @@ +package org.onflow.examples.kotlin.getNodeVersionInfo + +import org.onflow.flow.sdk.FlowAccessApi +import org.onflow.flow.sdk.FlowNodeVersionInfo + +internal class GetNodeVersionInfoAccessAPIConnector( + private val accessAPI: FlowAccessApi +) { + fun getNodeVersionInfo(): FlowNodeVersionInfo = + when (val response = accessAPI.getNodeVersionInfo()) { + is FlowAccessApi.AccessApiCallResponse.Success -> response.data + is FlowAccessApi.AccessApiCallResponse.Error -> throw Exception(response.message, response.throwable) + } +} diff --git a/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/getNodeVersionInfo/GetNodeVersionInfoAccessAPIConnectorTest.kt b/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/getNodeVersionInfo/GetNodeVersionInfoAccessAPIConnectorTest.kt new file mode 100644 index 0000000..aab13ca --- /dev/null +++ b/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/getNodeVersionInfo/GetNodeVersionInfoAccessAPIConnectorTest.kt @@ -0,0 +1,32 @@ +package org.onflow.examples.kotlin.getNodeVersionInfo + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.onflow.flow.common.test.FlowEmulatorProjectTest +import org.onflow.flow.common.test.FlowTestClient +import org.onflow.flow.sdk.FlowAccessApi +import org.onflow.flow.sdk.FlowNodeVersionInfo + +@FlowEmulatorProjectTest(flowJsonLocation = "../flow/flow.json") +internal class GetNodeVersionInfoAccessAPIConnectorTest { + @FlowTestClient + lateinit var accessAPI: FlowAccessApi + + private lateinit var nodeVersionInfoConnector: GetNodeVersionInfoAccessAPIConnector + + @BeforeEach + fun setup() { + nodeVersionInfoConnector = GetNodeVersionInfoAccessAPIConnector(accessAPI) + } + + @Test + fun `Can fetch node version info`() { + val nodeVersionInfo: FlowNodeVersionInfo = nodeVersionInfoConnector.getNodeVersionInfo() + assertNotNull(nodeVersionInfo, "Node version info should not be null") + assertEquals(nodeVersionInfo.protocolVersion, 0) + assertEquals(nodeVersionInfo.sporkRootBlockHeight, 0) + assertEquals(nodeVersionInfo.nodeRootBlockHeight, 0) + assertEquals(nodeVersionInfo.compatibleRange, null) + } +} diff --git a/sdk/build.gradle.kts b/sdk/build.gradle.kts index 11a9506..4a25d6a 100644 --- a/sdk/build.gradle.kts +++ b/sdk/build.gradle.kts @@ -32,7 +32,7 @@ sourceSets { dependencies { api("org.jetbrains.kotlin:kotlin-reflect:2.0.21") - api("org.onflow:flow:1.0.0") + api("org.onflow:flow:1.1.0") api("com.github.TrustedDataFramework:java-rlp:1.1.20") api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") api("org.bouncycastle:bcpkix-jdk18on:1.78.1") diff --git a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt index d296d8b..9471bc6 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt @@ -55,6 +55,24 @@ class TransactionIntegrationTest { assertThat(networkParams).isEqualTo(FlowChainId.EMULATOR) } + @Test + fun `Can get node version info`() { + val nodeVersionInfo = try { + handleResult( + accessAPI.getNodeVersionInfo(), + "Failed to get network parameters" + ) + } catch (e: Exception) { + fail("Failed to retrieve network parameters: ${e.message}") + } + + assertThat(nodeVersionInfo).isNotNull() + assertThat(nodeVersionInfo.protocolVersion).isEqualTo(0) + assertThat(nodeVersionInfo.sporkRootBlockHeight).isEqualTo(0) + assertThat(nodeVersionInfo.nodeRootBlockHeight).isEqualTo(0) + assertThat(nodeVersionInfo.compatibleRange).isEqualTo(null) + } + @Test fun `Can parse events`() { val txResult = createAndSubmitAccountCreationTransaction( diff --git a/sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt index 4b6ce90..78be56d 100644 --- a/sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt +++ b/sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt @@ -50,6 +50,8 @@ interface AsyncFlowAccessApi { fun getLatestProtocolStateSnapshot(): CompletableFuture> + fun getNodeVersionInfo(): CompletableFuture> + fun getTransactionsByBlockId(id: FlowId): CompletableFuture>> fun getTransactionResultsByBlockId(id: FlowId): CompletableFuture>> diff --git a/sdk/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt index e51f95b..f18239c 100644 --- a/sdk/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt +++ b/sdk/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt @@ -62,6 +62,8 @@ interface FlowAccessApi { fun getLatestProtocolStateSnapshot(): AccessApiCallResponse + fun getNodeVersionInfo(): AccessApiCallResponse + fun getTransactionsByBlockId(id: FlowId): AccessApiCallResponse> fun getTransactionResultsByBlockId(id: FlowId): AccessApiCallResponse> diff --git a/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt index 3409386..f5fd81e 100644 --- a/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt +++ b/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt @@ -588,6 +588,32 @@ class AsyncFlowAccessApiImpl( } } + override fun getNodeVersionInfo(): CompletableFuture> { + return try { + completableFuture( + try { + api.getNodeVersionInfo(Access.GetNodeVersionInfoRequest.newBuilder().build()) + } catch (e: Exception) { + return CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get node version info", e)) + } + ).handle { response, ex -> + if (ex != null) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get node version info", ex) + } else { + val compatibleRange = if (response.info.hasCompatibleRange()) { + FlowCompatibleRange(response.info.compatibleRange.startHeight, response.info.compatibleRange.endHeight) + } else { + null + } + + FlowAccessApi.AccessApiCallResponse.Success(FlowNodeVersionInfo(response.info.semver, response.info.commit, response.info.sporkId.toByteArray(), response.info.protocolVersion, response.info.sporkRootBlockHeight, response.info.nodeRootBlockHeight, compatibleRange)) + } + } + } catch (e: Exception) { + CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get node version info", e)) + } + } + override fun getTransactionsByBlockId(id: FlowId): CompletableFuture>> { return try { completableFuture( diff --git a/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt index ba54509..751cb94 100644 --- a/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt +++ b/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt @@ -341,6 +341,25 @@ class FlowAccessApiImpl( FlowAccessApi.AccessApiCallResponse.Error("Failed to get latest protocol state snapshot", e) } + override fun getNodeVersionInfo(): FlowAccessApi.AccessApiCallResponse = + try { + val ret = api.getNodeVersionInfo( + Access.GetNodeVersionInfoRequest + .newBuilder() + .build() + ) + + val compatibleRange = if (ret.info.hasCompatibleRange()) { + FlowCompatibleRange(ret.info.compatibleRange.startHeight, ret.info.compatibleRange.endHeight) + } else { + null + } + + FlowAccessApi.AccessApiCallResponse.Success(FlowNodeVersionInfo(ret.info.semver, ret.info.commit, ret.info.sporkId.toByteArray(), ret.info.protocolVersion, ret.info.sporkRootBlockHeight, ret.info.nodeRootBlockHeight, compatibleRange)) + } catch (e: Exception) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get node version info", e) + } + override fun getTransactionsByBlockId(id: FlowId): FlowAccessApi.AccessApiCallResponse> = try { val ret = api.getTransactionsByBlockID( diff --git a/sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt index 72f45d0..5e6a0b6 100644 --- a/sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt +++ b/sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt @@ -193,14 +193,15 @@ data class FlowAccountKey( .setSequenceNumber(sequenceNumber) .setRevoked(revoked) - val encoded: ByteArray get() = RLPCodec.encode( - arrayOf( - publicKey.bytes, - signAlgo.code, - hashAlgo.code, - weight + val encoded: ByteArray + get() = RLPCodec.encode( + arrayOf( + publicKey.bytes, + signAlgo.code, + hashAlgo.code, + weight + ) ) - ) } data class FlowEventResult( @@ -313,8 +314,13 @@ data class FlowTransactionResult( @JvmOverloads fun getEventsOfType(type: String, exact: Boolean = false, expectedCount: Int? = null): List { val ret = this.events - .filter { if (exact) { it.type == type } else { it.type.endsWith(type) } } - .map { it.event } + .filter { + if (exact) { + it.type == type + } else { + it.type.endsWith(type) + } + }.map { it.event } check(expectedCount == null || ret.size == expectedCount) { "Expected $expectedCount events of type $type but there were ${ret.size}" } return ret } @@ -450,12 +456,12 @@ data class FlowTransaction( return ret } - val signerMap: Map get() { - return signerList - .withIndex() - .map { it.value to it.index } - .toMap() - } + val signerMap: Map + get() { + return signerList + .withIndex() + .associate { it.value to it.index } + } companion object { @JvmStatic @@ -621,14 +627,34 @@ data class FlowTransactionSignature( data class FlowBlockHeader( val id: FlowId, val parentId: FlowId, - val height: Long + val height: Long, + val timestamp: LocalDateTime, + val payloadHash: ByteArray, + val view: Long, + val parentVoterSigData: ByteArray, + val proposerId: FlowId, + val proposerSigData: ByteArray, + val chainId: FlowChainId, + val parentVoterIndices: ByteArray, + val lastViewTc: FlowTimeoutCertificate, + val parentView: Long ) : Serializable { companion object { @JvmStatic fun of(value: BlockHeaderOuterClass.BlockHeader): FlowBlockHeader = FlowBlockHeader( id = FlowId.of(value.id.toByteArray()), parentId = FlowId.of(value.parentId.toByteArray()), - height = value.height + height = value.height, + timestamp = value.timestamp.asLocalDateTime(), + payloadHash = value.payloadHash.toByteArray(), + view = value.view, + parentVoterSigData = value.parentVoterSigData.toByteArray(), + proposerId = FlowId.of(value.proposerId.toByteArray()), + proposerSigData = value.proposerSigData.toByteArray(), + chainId = FlowChainId.of(value.chainId), + parentVoterIndices = value.parentVoterIndices.toByteArray(), + lastViewTc = FlowTimeoutCertificate.of(value.lastViewTc), + parentView = value.parentView ) } @@ -637,6 +663,54 @@ data class FlowBlockHeader( .setId(id.byteStringValue) .setParentId(parentId.byteStringValue) .setHeight(height) + .setTimestamp(timestamp.asTimestamp()) + .setPayloadHash(UnsafeByteOperations.unsafeWrap(payloadHash)) + .setView(view) + .setParentVoterSigData(UnsafeByteOperations.unsafeWrap(parentVoterSigData)) + .setProposerId(proposerId.byteStringValue) + .setProposerSigData(UnsafeByteOperations.unsafeWrap(proposerSigData)) + .setChainId(chainId.id) + .setParentVoterIndices(UnsafeByteOperations.unsafeWrap(parentVoterIndices)) + .setLastViewTc(lastViewTc.builder().build()) + .setParentView(parentView) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is FlowBlockHeader) return false + + if (id != other.id) return false + if (parentId != other.parentId) return false + if (height != other.height) return false + if (timestamp != other.timestamp) return false + if (!payloadHash.contentEquals(other.payloadHash)) return false + if (view != other.view) return false + if (!parentVoterSigData.contentEquals(other.parentVoterSigData)) return false + if (proposerId != other.proposerId) return false + if (!proposerSigData.contentEquals(other.proposerSigData)) return false + if (chainId != other.chainId) return false + if (!parentVoterIndices.contentEquals(other.parentVoterIndices)) return false + if (lastViewTc != other.lastViewTc) return false + if (parentView != other.parentView) return false + + return true + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + parentId.hashCode() + result = 31 * result + height.hashCode() + result = 31 * result + timestamp.hashCode() + result = 31 * result + payloadHash.contentHashCode() + result = 31 * result + view.hashCode() + result = 31 * result + parentVoterSigData.contentHashCode() + result = 31 * result + proposerId.hashCode() + result = 31 * result + proposerSigData.contentHashCode() + result = 31 * result + chainId.hashCode() + result = 31 * result + parentVoterIndices.contentHashCode() + result = 31 * result + lastViewTc.hashCode() + result = 31 * result + parentView.hashCode() + return result + } } data class FlowBlock( @@ -647,6 +721,10 @@ data class FlowBlock( val collectionGuarantees: List, val blockSeals: List, val signatures: List, + val executionReceiptMetaList: List, + val executionResultList: List, + val blockHeader: FlowBlockHeader, + val protocolStateId: FlowId ) : Serializable { companion object { @JvmStatic @@ -658,6 +736,10 @@ data class FlowBlock( collectionGuarantees = value.collectionGuaranteesList.map { FlowCollectionGuarantee.of(it) }, blockSeals = value.blockSealsList.map { FlowBlockSeal.of(it) }, signatures = value.signaturesList.map { FlowSignature(it.toByteArray()) }, + executionReceiptMetaList = value.executionReceiptMetaListList.map { FlowExecutionReceiptMeta.of(it) }, + executionResultList = value.executionResultListList.map { FlowExecutionResult.of(it) }, + blockHeader = FlowBlockHeader.of(value.blockHeader), + protocolStateId = FlowId.of(value.protocolStateId.toByteArray()) ) } @@ -670,6 +752,10 @@ data class FlowBlock( .addAllCollectionGuarantees(collectionGuarantees.map { it.builder().build() }) .addAllBlockSeals(blockSeals.map { it.builder().build() }) .addAllSignatures(signatures.map { it.byteStringValue }) + .addAllExecutionReceiptMetaList(executionReceiptMetaList.map { it.builder().build() }) + .addAllExecutionResultList(executionResultList.map { it.builder().build() }) + .setBlockHeader(blockHeader.builder().build()) + .setProtocolStateId(protocolStateId.byteStringValue) } data class FlowChunk( @@ -699,6 +785,19 @@ data class FlowChunk( ) } + @JvmOverloads + fun builder(builder: ExecutionResultOuterClass.Chunk.Builder = ExecutionResultOuterClass.Chunk.newBuilder()): ExecutionResultOuterClass.Chunk.Builder = builder + .setCollectionIndex(collectionIndex) + .setStartState(ByteString.copyFrom(startState)) + .setEventCollection(ByteString.copyFrom(eventCollection)) + .setBlockId(blockId.byteStringValue) + .setTotalComputationUsed(totalComputationUsed) + .setNumberOfTransactions(numberOfTransactions) + .setIndex(index) + .setEndState(ByteString.copyFrom(endState)) + .setExecutionDataId(executionDataId.byteStringValue) + .setStateDeltaCommitment(ByteString.copyFrom(stateDeltaCommitment)) + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is FlowChunk) return false @@ -743,6 +842,11 @@ data class FlowServiceEvent( ) } + @JvmOverloads + fun builder(builder: ExecutionResultOuterClass.ServiceEvent.Builder = ExecutionResultOuterClass.ServiceEvent.newBuilder()): ExecutionResultOuterClass.ServiceEvent.Builder = builder + .setType(type) + .setPayload(ByteString.copyFrom(payload)) + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is FlowServiceEvent) return false @@ -773,6 +877,134 @@ data class FlowExecutionResult( chunks = grpcExecutionResult.executionResult.chunksList.map { FlowChunk.of(it) }, serviceEvents = grpcExecutionResult.executionResult.serviceEventsList.map { FlowServiceEvent.of(it) }, ) + + fun of(grpcExecutionResult: ExecutionResultOuterClass.ExecutionResult) = FlowExecutionResult( + blockId = FlowId.of(grpcExecutionResult.blockId.toByteArray()), + previousResultId = FlowId.of(grpcExecutionResult.previousResultId.toByteArray()), + chunks = grpcExecutionResult.chunksList.map { FlowChunk.of(it) }, + serviceEvents = grpcExecutionResult.serviceEventsList.map { FlowServiceEvent.of(it) }, + ) + } + + @JvmOverloads + fun builder(builder: ExecutionResultOuterClass.ExecutionResult.Builder = ExecutionResultOuterClass.ExecutionResult.newBuilder()): ExecutionResultOuterClass.ExecutionResult.Builder = builder + .setBlockId(blockId.byteStringValue) + .setPreviousResultId(previousResultId.byteStringValue) + .addAllChunks(chunks.map { it.builder().build() }) + .addAllServiceEvents(serviceEvents.map { it.builder().build() }) +} + +data class FlowExecutionReceiptMeta( + val executorId: FlowId, + val resultId: FlowId, + val spocks: List, + val executorSignature: FlowSignature, +) : Serializable { + companion object { + fun of(grpcExecutionResult: ExecutionResultOuterClass.ExecutionReceiptMeta) = FlowExecutionReceiptMeta( + executorId = FlowId.of(grpcExecutionResult.executorId.toByteArray()), + resultId = FlowId.of(grpcExecutionResult.resultId.toByteArray()), + spocks = grpcExecutionResult.spocksList.map { it.toByteArray() }, + executorSignature = FlowSignature(grpcExecutionResult.executorSignature.toByteArray()) + ) + } + + @JvmOverloads + fun builder(builder: ExecutionResultOuterClass.ExecutionReceiptMeta.Builder = ExecutionResultOuterClass.ExecutionReceiptMeta.newBuilder()): ExecutionResultOuterClass.ExecutionReceiptMeta.Builder = builder + .setExecutorId(executorId.byteStringValue) + .setResultId(resultId.byteStringValue) + .addAllSpocks(spocks.map { ByteString.copyFrom(it) }) + .setExecutorSignature(executorSignature.byteStringValue) +} + +data class FlowTimeoutCertificate( + val view: Long, + val highQcViews: List, + val highestQc: FlowQuorumCertificate, + val signerIndices: ByteArray, + val sigData: ByteArray +) : Serializable { + companion object { + fun of(grpcExecutionResult: BlockHeaderOuterClass.TimeoutCertificate) = FlowTimeoutCertificate( + view = grpcExecutionResult.view, + highQcViews = grpcExecutionResult.highQcViewsList, + highestQc = FlowQuorumCertificate.of(grpcExecutionResult.highestQc), + signerIndices = grpcExecutionResult.signerIndices.toByteArray(), + sigData = grpcExecutionResult.sigData.toByteArray() + ) + } + + @JvmOverloads + fun builder(builder: BlockHeaderOuterClass.TimeoutCertificate.Builder = BlockHeaderOuterClass.TimeoutCertificate.newBuilder()): BlockHeaderOuterClass.TimeoutCertificate.Builder = builder + .setView(view) + .addAllHighQcViews(highQcViews) + .setHighestQc(highestQc.builder().build()) + .setSignerIndices(ByteString.copyFrom(signerIndices)) + .setSigData(ByteString.copyFrom(sigData)) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is FlowTimeoutCertificate) return false + + if (view != other.view) return false + if (highQcViews != other.highQcViews) return false + if (highestQc != other.highestQc) return false + if (!signerIndices.contentEquals(other.signerIndices)) return false + if (!sigData.contentEquals(other.sigData)) return false + + return true + } + + override fun hashCode(): Int { + var result = view.hashCode() + result = 31 * result + highQcViews.hashCode() + result = 31 * result + highestQc.hashCode() + result = 31 * result + signerIndices.contentHashCode() + result = 31 * result + sigData.contentHashCode() + return result + } +} + +data class FlowQuorumCertificate( + val view: Long, + val blockId: FlowId, + val signerIndices: ByteArray, + val sigData: ByteArray +) : Serializable { + companion object { + fun of(grpcExecutionResult: BlockHeaderOuterClass.QuorumCertificate) = FlowQuorumCertificate( + view = grpcExecutionResult.view, + blockId = FlowId.of(grpcExecutionResult.blockId.toByteArray()), + signerIndices = grpcExecutionResult.signerIndices.toByteArray(), + sigData = grpcExecutionResult.sigData.toByteArray() + ) + } + + @JvmOverloads + fun builder(builder: BlockHeaderOuterClass.QuorumCertificate.Builder = BlockHeaderOuterClass.QuorumCertificate.newBuilder()): BlockHeaderOuterClass.QuorumCertificate.Builder = builder + .setView(view) + .setBlockId(blockId.byteStringValue) + .setSignerIndices(ByteString.copyFrom(signerIndices)) + .setSigData(ByteString.copyFrom(sigData)) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is FlowQuorumCertificate) return false + + if (view != other.view) return false + if (blockId != other.blockId) return false + if (!signerIndices.contentEquals(other.signerIndices)) return false + if (!sigData.contentEquals(other.sigData)) return false + + return true + } + + override fun hashCode(): Int { + var result = view.hashCode() + result = 31 * result + blockId.hashCode() + result = 31 * result + signerIndices.contentHashCode() + result = 31 * result + sigData.contentHashCode() + return result } } @@ -921,13 +1153,19 @@ data class FlowBlockExecutionData( data class FlowCollectionGuarantee( val id: FlowId, - val signatures: List + val signatures: List, + val referenceBlockId: FlowId, + val signature: FlowSignature, + val signerIndices: ByteArray ) : Serializable { companion object { @JvmStatic fun of(value: CollectionOuterClass.CollectionGuarantee) = FlowCollectionGuarantee( id = FlowId.of(value.collectionId.toByteArray()), - signatures = value.signaturesList.map { FlowSignature(it.toByteArray()) } + signatures = value.signaturesList.map { FlowSignature(it.toByteArray()) }, + referenceBlockId = FlowId.of(value.referenceBlockId.toByteArray()), + signature = FlowSignature(value.signature.toByteArray()), + signerIndices = value.signerIndices.toByteArray() ) } @@ -935,6 +1173,31 @@ data class FlowCollectionGuarantee( fun builder(builder: CollectionOuterClass.CollectionGuarantee.Builder = CollectionOuterClass.CollectionGuarantee.newBuilder()): CollectionOuterClass.CollectionGuarantee.Builder = builder .setCollectionId(id.byteStringValue) .addAllSignatures(signatures.map { it.byteStringValue }) + .setReferenceBlockId(referenceBlockId.byteStringValue) + .setSignature(signature.byteStringValue) + .setSignerIndices(UnsafeByteOperations.unsafeWrap(signerIndices)) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is FlowCollectionGuarantee) return false + + if (id != other.id) return false + if (signatures != other.signatures) return false + if (referenceBlockId != other.referenceBlockId) return false + if (signature != other.signature) return false + if (!signerIndices.contentEquals(other.signerIndices)) return false + + return true + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + signatures.hashCode() + result = 31 * result + referenceBlockId.hashCode() + result = 31 * result + signature.hashCode() + result = 31 * result + signerIndices.contentHashCode() + return result + } } data class FlowBlockSeal( @@ -1165,6 +1428,82 @@ data class FlowSnapshot( override fun hashCode(): Int = bytes.contentHashCode() } +data class FlowCompatibleRange( + val startHeight: Long, + val endHeight: Long +) : Serializable + +data class FlowNodeVersionInfo( + val semver: String, + val commit: String, + val sporkId: ByteArray, + val protocolVersion: Long, + val sporkRootBlockHeight: Long, + val nodeRootBlockHeight: Long, + val compatibleRange: FlowCompatibleRange? +) : Serializable { + companion object { + @JvmStatic + fun of(value: NodeVersionInfoOuterClass.NodeVersionInfo) = FlowNodeVersionInfo( + semver = value.semver, + commit = value.commit, + sporkId = value.sporkId.toByteArray(), + protocolVersion = value.protocolVersion, + sporkRootBlockHeight = value.sporkRootBlockHeight, + nodeRootBlockHeight = value.nodeRootBlockHeight, + compatibleRange = if (value.hasCompatibleRange()) { + FlowCompatibleRange(value.compatibleRange.startHeight, value.compatibleRange.endHeight) + } else { + null + } + ) + } + + @JvmOverloads + fun builder(builder: NodeVersionInfoOuterClass.NodeVersionInfo.Builder = NodeVersionInfoOuterClass.NodeVersionInfo.newBuilder()): NodeVersionInfoOuterClass.NodeVersionInfo.Builder = builder + .setSemver(semver) + .setCommit(commit) + .setSporkId(UnsafeByteOperations.unsafeWrap(sporkId)) + .setProtocolVersion(protocolVersion) + .setSporkRootBlockHeight(sporkRootBlockHeight) + .setNodeRootBlockHeight(nodeRootBlockHeight) + .setCompatibleRange( + compatibleRange?.let { + NodeVersionInfoOuterClass.CompatibleRange + .newBuilder() + .setStartHeight(it.startHeight) + .setEndHeight(it.endHeight) + .build() + } + ) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is FlowNodeVersionInfo) return false + + if (semver != other.semver) return false + if (commit != other.commit) return false + if (!sporkId.contentEquals(other.sporkId)) return false + if (protocolVersion != other.protocolVersion) return false + if (sporkRootBlockHeight != other.sporkRootBlockHeight) return false + if (nodeRootBlockHeight != other.nodeRootBlockHeight) return false + if (compatibleRange != other.compatibleRange) return false + + return true + } + + override fun hashCode(): Int { + var result = semver.hashCode() + result = 31 * result + commit.hashCode() + result = 31 * result + sporkId.contentHashCode() + result = 31 * result + protocolVersion.hashCode() + result = 31 * result + sporkRootBlockHeight.hashCode() + result = 31 * result + nodeRootBlockHeight.hashCode() + result = 31 * result + compatibleRange.hashCode() + return result + } +} + data class FlowEventPayload( override val bytes: ByteArray ) : Serializable, diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt index 10f418c..a0c98d0 100644 --- a/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt @@ -3,6 +3,7 @@ package org.onflow.flow.sdk.impl import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.SettableFuture import com.google.protobuf.ByteString +import org.junit.jupiter.api.Assertions.assertArrayEquals import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -13,6 +14,7 @@ import org.onflow.flow.sdk.* import org.onflow.protobuf.access.Access import org.onflow.protobuf.access.AccessAPIGrpc import org.onflow.protobuf.entities.ExecutionResultOuterClass +import org.onflow.protobuf.entities.NodeVersionInfoOuterClass import org.onflow.protobuf.entities.TransactionOuterClass import java.math.BigDecimal import java.time.LocalDateTime @@ -27,6 +29,35 @@ class AsyncFlowAccessApiImplTest { companion object { val BLOCK_ID_BYTES = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1) val PARENT_ID_BYTES = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2) + + const val HEIGHT = 123L + + val mockBlockHeader = FlowBlockHeader( + id = FlowId.of(BLOCK_ID_BYTES), + parentId = FlowId.of(PARENT_ID_BYTES), + height = 123L, + timestamp = LocalDateTime.of(2024, 10, 15, 18, 33, 12), + payloadHash = ByteArray(32) { 0 }, + view = 1L, + parentVoterSigData = ByteArray(32) { 0 }, + proposerId = FlowId.of(PARENT_ID_BYTES), + proposerSigData = ByteArray(32) { 0 }, + chainId = FlowChainId.MAINNET, + parentVoterIndices = ByteArray(32) { 0 }, + lastViewTc = FlowTimeoutCertificate( + view = 1L, + highQcViews = emptyList(), + highestQc = FlowQuorumCertificate( + view = 1L, + blockId = FlowId.of(BLOCK_ID_BYTES), + signerIndices = ByteArray(32) { 0 }, + sigData = ByteArray(32) { 0 } + ), + signerIndices = ByteArray(32) { 0 }, + sigData = ByteArray(32) { 0 } + ), + parentView = 1L + ) } private fun setupFutureMock(response: T): ListenableFuture { @@ -46,7 +77,7 @@ class AsyncFlowAccessApiImplTest { @Test fun `test getLatestBlockHeader`() { - val mockBlockHeader = FlowBlockHeader(FlowId("01"), FlowId("01"), 123L) + val mockBlockHeader = mockBlockHeader val blockHeaderResponse = Access.BlockHeaderResponse .newBuilder() .setBlock(mockBlockHeader.builder().build()) @@ -61,8 +92,8 @@ class AsyncFlowAccessApiImplTest { @Test fun `test getBlockHeaderById`() { - val blockId = FlowId("01") - val mockBlockHeader = FlowBlockHeader(blockId, FlowId("01"), 123L) + val blockId = FlowId.of(BLOCK_ID_BYTES) + val mockBlockHeader = mockBlockHeader val blockHeaderResponse = Access.BlockHeaderResponse .newBuilder() .setBlock(mockBlockHeader.builder().build()) @@ -77,8 +108,8 @@ class AsyncFlowAccessApiImplTest { @Test fun `test getBlockHeaderByHeight`() { - val height = 123L - val mockBlockHeader = FlowBlockHeader(FlowId("01"), FlowId("01"), height) + val height = HEIGHT + val mockBlockHeader = mockBlockHeader val blockHeaderResponse = Access.BlockHeaderResponse .newBuilder() .setBlock(mockBlockHeader.builder().build()) @@ -93,7 +124,19 @@ class AsyncFlowAccessApiImplTest { @Test fun `test getLatestBlock`() { - val mockBlock = FlowBlock(FlowId("01"), FlowId("01"), 123L, LocalDateTime.now(), emptyList(), emptyList(), emptyList()) + val mockBlock = FlowBlock( + id = FlowId.of(BLOCK_ID_BYTES), + parentId = FlowId.of(PARENT_ID_BYTES), + height = 123L, + timestamp = LocalDateTime.now(), + collectionGuarantees = emptyList(), + blockSeals = emptyList(), + signatures = emptyList(), + executionReceiptMetaList = emptyList(), + executionResultList = emptyList(), + blockHeader = mockBlockHeader, + protocolStateId = FlowId.of(ByteArray(32)) + ) val blockResponse = Access.BlockResponse .newBuilder() .setBlock(mockBlock.builder().build()) @@ -108,8 +151,20 @@ class AsyncFlowAccessApiImplTest { @Test fun `test getBlockById`() { - val blockId = FlowId("01") - val mockBlock = FlowBlock(blockId, FlowId("01"), 123L, LocalDateTime.now(), emptyList(), emptyList(), emptyList()) + val blockId = FlowId.of(BLOCK_ID_BYTES) + val mockBlock = FlowBlock( + id = blockId, + parentId = FlowId.of(PARENT_ID_BYTES), + height = 123L, + timestamp = LocalDateTime.now(), + collectionGuarantees = emptyList(), + blockSeals = emptyList(), + signatures = emptyList(), + executionReceiptMetaList = emptyList(), + executionResultList = emptyList(), + blockHeader = mockBlockHeader, + protocolStateId = FlowId.of(ByteArray(32)) + ) val blockResponse = Access.BlockResponse .newBuilder() .setBlock(mockBlock.builder().build()) @@ -124,8 +179,20 @@ class AsyncFlowAccessApiImplTest { @Test fun `test getBlockByHeight`() { - val height = 123L - val mockBlock = FlowBlock(FlowId("01"), FlowId("01"), height, LocalDateTime.now(), emptyList(), emptyList(), emptyList()) + val height = HEIGHT + val mockBlock = FlowBlock( + FlowId("01"), + FlowId("01"), + height, + LocalDateTime.now(), + emptyList(), + emptyList(), + emptyList(), + emptyList(), + emptyList(), + mockBlockHeader, + FlowId.of(ByteArray(32)) + ) val blockResponse = Access.BlockResponse .newBuilder() .setBlock(mockBlock.builder().build()) @@ -395,6 +462,46 @@ class AsyncFlowAccessApiImplTest { assertEquals(mockFlowSnapshot, result.data) } + @Test + fun `test getNodeVersionInfo`() { + val mockNodeVersionInfo = Access.GetNodeVersionInfoResponse + .newBuilder() + .setInfo( + NodeVersionInfoOuterClass.NodeVersionInfo + .newBuilder() + .setSemver("v0.0.1") + .setCommit("123456") + .setSporkId(ByteString.copyFromUtf8("sporkId")) + .setProtocolVersion(5) + .setSporkRootBlockHeight(1000) + .setNodeRootBlockHeight(1001) + .setCompatibleRange( + NodeVersionInfoOuterClass.CompatibleRange + .newBuilder() + .setStartHeight(100) + .setEndHeight(200) + .build() + ).build() + ).build() + + `when`(api.getNodeVersionInfo(any())).thenReturn(setupFutureMock(mockNodeVersionInfo)) + + val result = asyncFlowAccessApi.getNodeVersionInfo().get() + + assert(result is FlowAccessApi.AccessApiCallResponse.Success) + result as FlowAccessApi.AccessApiCallResponse.Success + val nodeVersionInfo = result.data + + assertEquals("v0.0.1", nodeVersionInfo.semver) + assertEquals("123456", nodeVersionInfo.commit) + assertArrayEquals("sporkId".toByteArray(), nodeVersionInfo.sporkId) + assertEquals(5, nodeVersionInfo.protocolVersion) + assertEquals(1000L, nodeVersionInfo.sporkRootBlockHeight) + assertEquals(1001L, nodeVersionInfo.nodeRootBlockHeight) + assertEquals(100L, nodeVersionInfo.compatibleRange?.startHeight) + assertEquals(200L, nodeVersionInfo.compatibleRange?.endHeight) + } + @Test fun `test getTransactionsByBlockId`() { val blockId = FlowId("01") diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt index 5a52ab1..1a0afc6 100644 --- a/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt @@ -6,18 +6,14 @@ import kotlinx.coroutines.channels.consumeEach import kotlinx.coroutines.test.* import org.onflow.flow.sdk.* import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.fail +import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.* import org.onflow.protobuf.access.Access import org.onflow.protobuf.access.AccessAPIGrpc -import org.onflow.protobuf.entities.BlockExecutionDataOuterClass -import org.onflow.protobuf.entities.EventOuterClass -import org.onflow.protobuf.entities.ExecutionResultOuterClass -import org.onflow.protobuf.entities.TransactionOuterClass +import org.onflow.protobuf.entities.* import org.onflow.protobuf.executiondata.ExecutionDataAPIGrpc import org.onflow.protobuf.executiondata.Executiondata import java.io.ByteArrayOutputStream @@ -36,6 +32,27 @@ class FlowAccessApiImplTest { private val testDispatcher = StandardTestDispatcher() private val testScope = TestScope(testDispatcher) + companion object { + val mockBlockHeader = FlowBlockHeader( + id = FlowId.of(AsyncFlowAccessApiImplTest.BLOCK_ID_BYTES), + parentId = FlowId.of(AsyncFlowAccessApiImplTest.PARENT_ID_BYTES), + height = 123L, + timestamp = LocalDateTime.now(), + payloadHash = ByteArray(32), + view = 1L, + parentVoterSigData = ByteArray(32), + proposerId = FlowId.of(AsyncFlowAccessApiImplTest.PARENT_ID_BYTES), + proposerSigData = ByteArray(32), + chainId = FlowChainId.MAINNET, + parentVoterIndices = ByteArray(32), + lastViewTc = FlowTimeoutCertificate(1L, emptyList(), FlowQuorumCertificate(1L, FlowId.of(AsyncFlowAccessApiImplTest.BLOCK_ID_BYTES), ByteArray(32), ByteArray(32)), ByteArray(32), ByteArray(32)), + parentView = 1L + ) + + val blockId = FlowId("01") + val mockBlock = FlowBlock(blockId, FlowId("01"), 123L, LocalDateTime.now(), emptyList(), emptyList(), emptyList(), emptyList(), emptyList(), mockBlockHeader, FlowId("01")) + } + @BeforeEach fun setUp() { mockApi = mock(AccessAPIGrpc.AccessAPIBlockingStub::class.java) @@ -59,7 +76,7 @@ class FlowAccessApiImplTest { @Test fun `Test getLatestBlockHeader`() { - val mockBlockHeader = FlowBlockHeader(FlowId("01"), FlowId("01"), 123L) + val mockBlockHeader = mockBlockHeader val blockHeaderProto = Access.BlockHeaderResponse .newBuilder() .setBlock(mockBlockHeader.builder().build()) @@ -73,8 +90,8 @@ class FlowAccessApiImplTest { @Test fun `Test getBlockHeaderById`() { - val blockId = FlowId("01") - val mockBlockHeader = FlowBlockHeader(blockId, FlowId("01"), 123L) + val blockId = blockId + val mockBlockHeader = mockBlockHeader val blockHeaderProto = Access.BlockHeaderResponse .newBuilder() .setBlock(mockBlockHeader.builder().build()) @@ -89,7 +106,7 @@ class FlowAccessApiImplTest { @Test fun `Test getBlockHeaderByHeight`() { val height = 123L - val mockBlockHeader = FlowBlockHeader(FlowId("01"), FlowId("01"), height) + val mockBlockHeader = mockBlockHeader val blockHeaderProto = Access.BlockHeaderResponse .newBuilder() .setBlock(mockBlockHeader.builder().build()) @@ -103,7 +120,7 @@ class FlowAccessApiImplTest { @Test fun `Test getLatestBlock`() { - val mockBlock = FlowBlock(FlowId("01"), FlowId("01"), 123L, LocalDateTime.now(), emptyList(), emptyList(), emptyList()) + val mockBlock = mockBlock val blockProto = Access.BlockResponse .newBuilder() .setBlock(mockBlock.builder().build()) @@ -117,8 +134,9 @@ class FlowAccessApiImplTest { @Test fun `Test getBlockById`() { - val blockId = FlowId("01") - val mockBlock = FlowBlock(blockId, FlowId("01"), 123L, LocalDateTime.now(), emptyList(), emptyList(), emptyList()) + val blockId = blockId + val mockBlock = mockBlock + val blockProto = Access.BlockResponse .newBuilder() .setBlock(mockBlock.builder().build()) @@ -133,7 +151,7 @@ class FlowAccessApiImplTest { @Test fun `Test getBlockByHeight`() { val height = 123L - val mockBlock = FlowBlock(FlowId("01"), FlowId("01"), height, LocalDateTime.now(), emptyList(), emptyList(), emptyList()) + val mockBlock = mockBlock val blockProto = Access.BlockResponse .newBuilder() .setBlock(mockBlock.builder().build()) @@ -427,6 +445,44 @@ class FlowAccessApiImplTest { assertResultSuccess(result) { assertEquals(mockFlowSnapshot, it) } } + @Test + fun `Test getNodeVersionInfo`() { + val mockNodeVersionInfo = Access.GetNodeVersionInfoResponse + .newBuilder() + .setInfo( + NodeVersionInfoOuterClass.NodeVersionInfo + .newBuilder() + .setSemver("v0.0.1") + .setCommit("123456") + .setSporkId(ByteString.copyFromUtf8("sporkId")) + .setProtocolVersion(5) + .setSporkRootBlockHeight(1000) + .setNodeRootBlockHeight(1001) + .setCompatibleRange( + NodeVersionInfoOuterClass.CompatibleRange + .newBuilder() + .setStartHeight(100) + .setEndHeight(200) + .build() + ).build() + ).build() + + `when`(mockApi.getNodeVersionInfo(any())).thenReturn(mockNodeVersionInfo) + + val result = flowAccessApiImpl.getNodeVersionInfo() + + assertResultSuccess(result) { + assertEquals("v0.0.1", it.semver) + assertEquals("123456", it.commit) + assertArrayEquals("sporkId".toByteArray(), it.sporkId) + assertEquals(5, it.protocolVersion) + assertEquals(1000L, it.sporkRootBlockHeight) + assertEquals(1001L, it.nodeRootBlockHeight) + assertEquals(100L, it.compatibleRange?.startHeight) + assertEquals(200L, it.compatibleRange?.endHeight) + } + } + @Test fun `Test getTransactionsByBlockId`() { val blockId = FlowId("01") diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockHeaderTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockHeaderTest.kt new file mode 100644 index 0000000..756d486 --- /dev/null +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockHeaderTest.kt @@ -0,0 +1,248 @@ +package org.onflow.flow.sdk.models + +import com.google.protobuf.ByteString +import com.google.protobuf.UnsafeByteOperations +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.mockito.Mockito.* +import org.onflow.flow.sdk.* +import org.onflow.protobuf.entities.BlockHeaderOuterClass +import java.time.LocalDateTime + +class FlowBlockHeaderTest { + companion object { + val idBytes = ByteArray(32) { 0x01 } + val parentIdBytes = ByteArray(32) { 0x02 } + val proposerIdBytes = ByteArray(32) { 0x03 } + val payloadHashBytes = ByteArray(32) { 0x04 } + val voterSigDataBytes = ByteArray(32) { 0x05 } + val proposerSigDataBytes = ByteArray(32) { 0x06 } + val voterIndicesBytes = ByteArray(32) { 0x07 } + + val id = FlowId.of(idBytes) + val parentId = FlowId.of(parentIdBytes) + val proposerId = FlowId.of(proposerIdBytes) + val chainId = FlowChainId.of("mainnet") + val timestamp: LocalDateTime = LocalDateTime.now() + val timeoutCertificate: FlowTimeoutCertificate = mock(FlowTimeoutCertificate::class.java) + } + + @Test + fun `test of() method`() { + val chainId = "mainnet" + + // Mock block header + val grpcBlockHeader = mock(BlockHeaderOuterClass.BlockHeader::class.java) + `when`(grpcBlockHeader.id).thenReturn(ByteString.copyFrom(idBytes)) + `when`(grpcBlockHeader.parentId).thenReturn(ByteString.copyFrom(parentIdBytes)) + `when`(grpcBlockHeader.height).thenReturn(123L) + `when`(grpcBlockHeader.timestamp).thenReturn(timestamp.asTimestamp()) + `when`(grpcBlockHeader.payloadHash).thenReturn(ByteString.copyFrom(payloadHashBytes)) + `when`(grpcBlockHeader.view).thenReturn(456L) + `when`(grpcBlockHeader.parentVoterSigData).thenReturn(ByteString.copyFrom(voterSigDataBytes)) + `when`(grpcBlockHeader.proposerId).thenReturn(ByteString.copyFrom(proposerIdBytes)) + `when`(grpcBlockHeader.proposerSigData).thenReturn(ByteString.copyFrom(proposerSigDataBytes)) + `when`(grpcBlockHeader.chainId).thenReturn(chainId) + `when`(grpcBlockHeader.parentVoterIndices).thenReturn(ByteString.copyFrom(voterIndicesBytes)) + + // Mock timeout certificate and quorum certificate + val timeoutCertificateMock = mock(BlockHeaderOuterClass.TimeoutCertificate::class.java) + `when`(timeoutCertificateMock.signerIndices).thenReturn(ByteString.copyFrom(idBytes)) + `when`(timeoutCertificateMock.sigData).thenReturn(ByteString.copyFrom(idBytes)) + + val highestQcMock = mock(BlockHeaderOuterClass.QuorumCertificate::class.java) + `when`(highestQcMock.blockId).thenReturn(ByteString.copyFrom(idBytes)) + `when`(highestQcMock.signerIndices).thenReturn(ByteString.copyFrom(idBytes)) + `when`(highestQcMock.sigData).thenReturn(ByteString.copyFrom(idBytes)) + + `when`(grpcBlockHeader.lastViewTc).thenReturn(timeoutCertificateMock) + `when`(timeoutCertificateMock.highestQc).thenReturn(highestQcMock) + `when`(grpcBlockHeader.parentView).thenReturn(789L) + + val flowBlockHeader = FlowBlockHeader.of(grpcBlockHeader) + + assertEquals(FlowId.of(idBytes), flowBlockHeader.id) + assertEquals(FlowId.of(parentIdBytes), flowBlockHeader.parentId) + assertEquals(123L, flowBlockHeader.height) + assertEquals(timestamp, flowBlockHeader.timestamp) + assertArrayEquals(payloadHashBytes, flowBlockHeader.payloadHash) + assertEquals(456L, flowBlockHeader.view) + assertArrayEquals(voterSigDataBytes, flowBlockHeader.parentVoterSigData) + assertEquals(FlowId.of(proposerIdBytes), flowBlockHeader.proposerId) + assertArrayEquals(proposerSigDataBytes, flowBlockHeader.proposerSigData) + assertEquals(FlowChainId.of(chainId), flowBlockHeader.chainId) + assertArrayEquals(voterIndicesBytes, flowBlockHeader.parentVoterIndices) + assertEquals(789L, flowBlockHeader.parentView) + } + + @Test + fun `test builder() method`() { + // Mock FlowQuorumCertificate + val view = 123L + val blockIdBytes = ByteArray(32) { 0x01 } + val signerIndicesBytes = ByteArray(32) { 0x02 } + val sigDataBytes = ByteArray(32) { 0x03 } + + val grpcQuorumCertificate = mock(BlockHeaderOuterClass.QuorumCertificate::class.java) + `when`(grpcQuorumCertificate.view).thenReturn(view) + `when`(grpcQuorumCertificate.blockId).thenReturn(ByteString.copyFrom(blockIdBytes)) + `when`(grpcQuorumCertificate.signerIndices).thenReturn(ByteString.copyFrom(signerIndicesBytes)) + `when`(grpcQuorumCertificate.sigData).thenReturn(ByteString.copyFrom(sigDataBytes)) + + val flowQuorumCertificate = FlowQuorumCertificate.of(grpcQuorumCertificate) + + // Mock FlowTimeoutCertificate + val highQcViews = listOf(1L, 2L) + val signerIndices = byteArrayOf(1, 2) + val sigData = byteArrayOf(3, 4) + + val timeoutCertificate = FlowTimeoutCertificate( + view = view, + highQcViews = highQcViews, + highestQc = flowQuorumCertificate, + signerIndices = signerIndices, + sigData = sigData + ) + + val flowBlockHeader = FlowBlockHeader( + id = id, + parentId = parentId, + height = 123L, + timestamp = timestamp, + payloadHash = payloadHashBytes, + view = 456L, + parentVoterSigData = voterSigDataBytes, + proposerId = proposerId, + proposerSigData = proposerSigDataBytes, + chainId = chainId, + parentVoterIndices = voterIndicesBytes, + lastViewTc = timeoutCertificate, + parentView = 789L + ) + + val result = flowBlockHeader.builder().build() + + assertEquals(id.byteStringValue, result.id) + assertEquals(parentId.byteStringValue, result.parentId) + assertEquals(123L, result.height) + assertEquals(timestamp.asTimestamp(), result.timestamp) + assertEquals(UnsafeByteOperations.unsafeWrap(payloadHashBytes), result.payloadHash) + assertEquals(456L, result.view) + assertEquals(UnsafeByteOperations.unsafeWrap(voterSigDataBytes), result.parentVoterSigData) + assertEquals(proposerId.byteStringValue, result.proposerId) + assertEquals(UnsafeByteOperations.unsafeWrap(proposerSigDataBytes), result.proposerSigData) + assertEquals(chainId.id, result.chainId) + assertEquals(UnsafeByteOperations.unsafeWrap(voterIndicesBytes), result.parentVoterIndices) + assertEquals(789L, result.parentView) + } + + @Test + fun `test equals() method`() { + val flowBlockHeader1 = FlowBlockHeader( + id = id, + parentId = parentId, + height = 123L, + timestamp = timestamp, + payloadHash = payloadHashBytes, + view = 456L, + parentVoterSigData = voterSigDataBytes, + proposerId = proposerId, + proposerSigData = proposerSigDataBytes, + chainId = chainId, + parentVoterIndices = voterIndicesBytes, + lastViewTc = timeoutCertificate, + parentView = 789L + ) + + val flowBlockHeader2 = FlowBlockHeader( + id = id, + parentId = parentId, + height = 123L, + timestamp = timestamp, + payloadHash = payloadHashBytes, + view = 456L, + parentVoterSigData = voterSigDataBytes, + proposerId = proposerId, + proposerSigData = proposerSigDataBytes, + chainId = chainId, + parentVoterIndices = voterIndicesBytes, + lastViewTc = timeoutCertificate, + parentView = 789L + ) + + assertEquals(flowBlockHeader1, flowBlockHeader2) + + val flowBlockHeader3 = FlowBlockHeader( + id = FlowId.of(ByteArray(32) { 0x08 }), + parentId = parentId, + height = 124L, + timestamp = timestamp.plusDays(1), + payloadHash = ByteArray(32) { 0x09 }, + view = 789L, + parentVoterSigData = voterSigDataBytes, + proposerId = proposerId, + proposerSigData = proposerSigDataBytes, + chainId = chainId, + parentVoterIndices = voterIndicesBytes, + lastViewTc = timeoutCertificate, + parentView = 100L + ) + + assertNotEquals(flowBlockHeader1, flowBlockHeader3) + } + + @Test + fun `test hashCode() method`() { + val flowBlockHeader1 = FlowBlockHeader( + id = id, + parentId = parentId, + height = 123L, + timestamp = timestamp, + payloadHash = payloadHashBytes, + view = 456L, + parentVoterSigData = voterSigDataBytes, + proposerId = proposerId, + proposerSigData = proposerSigDataBytes, + chainId = chainId, + parentVoterIndices = voterIndicesBytes, + lastViewTc = timeoutCertificate, + parentView = 789L + ) + + val flowBlockHeader2 = FlowBlockHeader( + id = id, + parentId = parentId, + height = 123L, + timestamp = timestamp, + payloadHash = payloadHashBytes, + view = 456L, + parentVoterSigData = voterSigDataBytes, + proposerId = proposerId, + proposerSigData = proposerSigDataBytes, + chainId = chainId, + parentVoterIndices = voterIndicesBytes, + lastViewTc = timeoutCertificate, + parentView = 789L + ) + + assertEquals(flowBlockHeader1.hashCode(), flowBlockHeader2.hashCode()) + + val flowBlockHeader3 = FlowBlockHeader( + id = FlowId.of(ByteArray(32) { 0x08 }), + parentId = parentId, + height = 124L, + timestamp = timestamp.plusDays(1), + payloadHash = ByteArray(32) { 0x09 }, + view = 789L, + parentVoterSigData = voterSigDataBytes, + proposerId = proposerId, + proposerSigData = proposerSigDataBytes, + chainId = chainId, + parentVoterIndices = voterIndicesBytes, + lastViewTc = timeoutCertificate, + parentView = 100L + ) + + assertNotEquals(flowBlockHeader1.hashCode(), flowBlockHeader3.hashCode()) + } +} diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockTest.kt index ec9886f..725d8f4 100644 --- a/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockTest.kt +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockTest.kt @@ -2,12 +2,10 @@ package org.onflow.flow.sdk.models import com.google.protobuf.ByteString import com.google.protobuf.Timestamp -import org.onflow.flow.sdk.FlowBlock -import org.onflow.flow.sdk.FlowId -import org.onflow.flow.sdk.asTimestamp -import org.onflow.flow.sdk.fixedSize import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.onflow.flow.sdk.* +import org.onflow.protobuf.entities.BlockHeaderOuterClass import org.onflow.protobuf.entities.BlockOuterClass import java.time.Instant import java.time.LocalDateTime @@ -25,6 +23,15 @@ class FlowBlockTest { .setParentId(ByteString.copyFromUtf8("parent_id")) .setHeight(123) .setTimestamp(timestamp) + .setBlockHeader( + BlockHeaderOuterClass.BlockHeader + .newBuilder() + .setId(ByteString.copyFromUtf8("header_id")) + .setParentId(ByteString.copyFromUtf8("header_parent_id")) + .setHeight(124) + .setTimestamp(timestamp) + .build() + ).setProtocolStateId(ByteString.copyFromUtf8("protocol_state_id")) val flowBlock = FlowBlock.of(blockBuilder.build()) @@ -41,13 +48,24 @@ class FlowBlockTest { assert(flowBlock.id.bytes.contentEquals(fixedSize("id".toByteArray(), 32))) assert(flowBlock.parentId.bytes.contentEquals(fixedSize("parent_id".toByteArray(), 32))) assertEquals(flowBlock.height, 123L) - assertEquals( - expectedUtcDateTime, - actualUtcDateTime, - ) + assertEquals(expectedUtcDateTime, actualUtcDateTime) assert(flowBlock.collectionGuarantees.isEmpty()) assert(flowBlock.blockSeals.isEmpty()) assert(flowBlock.signatures.isEmpty()) + assert(flowBlock.executionReceiptMetaList.isEmpty()) + assert(flowBlock.executionResultList.isEmpty()) + assert( + flowBlock.blockHeader.id.bytes + .contentEquals( + fixedSize("header_id".toByteArray(), 32) + ) + ) + assert( + flowBlock.protocolStateId.bytes + .contentEquals( + fixedSize("protocol_state_id".toByteArray(), 32) + ) + ) } @Test @@ -59,7 +77,25 @@ class FlowBlockTest { timestamp = LocalDateTime.now(), collectionGuarantees = emptyList(), blockSeals = emptyList(), - signatures = emptyList() + signatures = emptyList(), + executionReceiptMetaList = emptyList(), + executionResultList = emptyList(), + blockHeader = FlowBlockHeader( + id = FlowId.of("header_id".toByteArray()), + parentId = FlowId.of("header_parent_id".toByteArray()), + height = 124, + timestamp = LocalDateTime.now(), + payloadHash = ByteArray(32), + view = 1, + parentVoterSigData = ByteArray(32), + proposerId = FlowId.of("proposer_id".toByteArray()), + proposerSigData = ByteArray(32), + chainId = FlowChainId.MAINNET, + parentVoterIndices = ByteArray(32), + lastViewTc = FlowTimeoutCertificate(1L, emptyList(), FlowQuorumCertificate(1L, FlowId.of("block_id".toByteArray()), ByteArray(32), ByteArray(32)), ByteArray(32), ByteArray(32)), + parentView = 1 + ), + protocolStateId = FlowId.of("protocol_state_id".toByteArray()) ) val blockBuilder = flowBlock.builder() @@ -67,9 +103,19 @@ class FlowBlockTest { assert(blockBuilder.id.toByteArray().contentEquals(fixedSize("id".toByteArray(), 32))) assert(blockBuilder.parentId.toByteArray().contentEquals(fixedSize("parent_id".toByteArray(), 32))) assert(blockBuilder.height == 123L) - assert(blockBuilder.timestamp == flowBlock.timestamp.asTimestamp()) + assert(blockBuilder.timestamp.equals(flowBlock.timestamp.asTimestamp())) assert(blockBuilder.collectionGuaranteesList.isEmpty()) assert(blockBuilder.blockSealsList.isEmpty()) assert(blockBuilder.signaturesList.isEmpty()) + assert( + blockBuilder.blockHeader.id + .toByteArray() + .contentEquals(fixedSize("header_id".toByteArray(), 32)) + ) + assert( + blockBuilder.protocolStateId + .toByteArray() + .contentEquals(fixedSize("protocol_state_id".toByteArray(), 32)) + ) } } diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowChunkTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowChunkTest.kt new file mode 100644 index 0000000..228dd7e --- /dev/null +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowChunkTest.kt @@ -0,0 +1,202 @@ +package org.onflow.flow.sdk.models + +import com.google.protobuf.ByteString +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.mockito.Mockito.* +import org.onflow.flow.sdk.FlowChunk +import org.onflow.flow.sdk.FlowId +import org.onflow.protobuf.entities.ExecutionResultOuterClass + +class FlowChunkTest { + @Test + fun `test of() method`() { + val collectionIndex = 1 + val startStateBytes = ByteArray(32) { 0x01 } + val eventCollectionBytes = ByteArray(32) { 0x02 } + val blockIdBytes = ByteArray(32) { 0x03 } + val totalComputationUsed = 1000L + val numberOfTransactions = 10 + val index = 5L + val endStateBytes = ByteArray(32) { 0x04 } + val executionDataIdBytes = ByteArray(32) { 0x05 } + val stateDeltaCommitmentBytes = ByteArray(32) { 0x06 } + + val grpcChunk = mock(ExecutionResultOuterClass.Chunk::class.java) + `when`(grpcChunk.collectionIndex).thenReturn(collectionIndex) + `when`(grpcChunk.startState).thenReturn(ByteString.copyFrom(startStateBytes)) + `when`(grpcChunk.eventCollection).thenReturn(ByteString.copyFrom(eventCollectionBytes)) + `when`(grpcChunk.blockId).thenReturn(ByteString.copyFrom(blockIdBytes)) + `when`(grpcChunk.totalComputationUsed).thenReturn(totalComputationUsed) + `when`(grpcChunk.numberOfTransactions).thenReturn(numberOfTransactions) + `when`(grpcChunk.index).thenReturn(index) + `when`(grpcChunk.endState).thenReturn(ByteString.copyFrom(endStateBytes)) + `when`(grpcChunk.executionDataId).thenReturn(ByteString.copyFrom(executionDataIdBytes)) + `when`(grpcChunk.stateDeltaCommitment).thenReturn(ByteString.copyFrom(stateDeltaCommitmentBytes)) + + val flowChunk = FlowChunk.of(grpcChunk) + + assertEquals(collectionIndex, flowChunk.collectionIndex) + assertArrayEquals(startStateBytes, flowChunk.startState) + assertArrayEquals(eventCollectionBytes, flowChunk.eventCollection) + assertEquals(FlowId.of(blockIdBytes), flowChunk.blockId) + assertEquals(totalComputationUsed, flowChunk.totalComputationUsed) + assertEquals(numberOfTransactions, flowChunk.numberOfTransactions) + assertEquals(index, flowChunk.index) + assertArrayEquals(endStateBytes, flowChunk.endState) + assertEquals(FlowId.of(executionDataIdBytes), flowChunk.executionDataId) + assertArrayEquals(stateDeltaCommitmentBytes, flowChunk.stateDeltaCommitment) + } + + @Test + fun `test builder() method`() { + val collectionIndex = 1 + val startStateBytes = ByteArray(32) { 0x01 } + val eventCollectionBytes = ByteArray(32) { 0x02 } + val blockId = FlowId.of(ByteArray(32) { 0x03 }) + val totalComputationUsed = 1000L + val numberOfTransactions = 10 + val index = 5L + val endStateBytes = ByteArray(32) { 0x04 } + val executionDataId = FlowId.of(ByteArray(32) { 0x05 }) + val stateDeltaCommitmentBytes = ByteArray(32) { 0x06 } + + val flowChunk = FlowChunk( + collectionIndex = collectionIndex, + startState = startStateBytes, + eventCollection = eventCollectionBytes, + blockId = blockId, + totalComputationUsed = totalComputationUsed, + numberOfTransactions = numberOfTransactions, + index = index, + endState = endStateBytes, + executionDataId = executionDataId, + stateDeltaCommitment = stateDeltaCommitmentBytes + ) + + val result = flowChunk.builder().build() + + assertEquals(collectionIndex, result.collectionIndex) + assertEquals(ByteString.copyFrom(startStateBytes), result.startState) + assertEquals(ByteString.copyFrom(eventCollectionBytes), result.eventCollection) + assertEquals(blockId.byteStringValue, result.blockId) + assertEquals(totalComputationUsed, result.totalComputationUsed) + assertEquals(numberOfTransactions, result.numberOfTransactions) + assertEquals(index, result.index) + assertEquals(ByteString.copyFrom(endStateBytes), result.endState) + assertEquals(executionDataId.byteStringValue, result.executionDataId) + assertEquals(ByteString.copyFrom(stateDeltaCommitmentBytes), result.stateDeltaCommitment) + } + + @Test + fun `test equals() method`() { + val collectionIndex1 = 1 + val collectionIndex2 = 2 + val startState1 = ByteArray(32) { 0x01 } + val startState2 = ByteArray(32) { 0x02 } + val blockId1 = FlowId.of(ByteArray(32) { 0x03 }) + val blockId2 = FlowId.of(ByteArray(32) { 0x04 }) + val totalComputationUsed1 = 1000L + val totalComputationUsed2 = 2000L + val endState1 = ByteArray(32) { 0x04 } + val endState2 = ByteArray(32) { 0x05 } + + val flowChunk1 = FlowChunk( + collectionIndex = collectionIndex1, + startState = startState1, + eventCollection = startState1, + blockId = blockId1, + totalComputationUsed = totalComputationUsed1, + numberOfTransactions = 10, + index = 5L, + endState = endState1, + executionDataId = blockId1, + stateDeltaCommitment = startState1 + ) + + val flowChunk2 = FlowChunk( + collectionIndex = collectionIndex1, + startState = startState1, + eventCollection = startState1, + blockId = blockId1, + totalComputationUsed = totalComputationUsed1, + numberOfTransactions = 10, + index = 5L, + endState = endState1, + executionDataId = blockId1, + stateDeltaCommitment = startState1 + ) + + val flowChunk3 = FlowChunk( + collectionIndex = collectionIndex2, + startState = startState2, + eventCollection = startState2, + blockId = blockId2, + totalComputationUsed = totalComputationUsed2, + numberOfTransactions = 20, + index = 10L, + endState = endState2, + executionDataId = blockId2, + stateDeltaCommitment = startState2 + ) + + assertEquals(flowChunk1, flowChunk2) + assertNotEquals(flowChunk1, flowChunk3) + } + + @Test + fun `test hashCode() method`() { + val collectionIndex1 = 1 + val collectionIndex2 = 2 + val startState1 = ByteArray(32) { 0x01 } + val startState2 = ByteArray(32) { 0x02 } + val blockId1 = FlowId.of(ByteArray(32) { 0x03 }) + val blockId2 = FlowId.of(ByteArray(32) { 0x04 }) + val totalComputationUsed1 = 1000L + val totalComputationUsed2 = 2000L + val endState1 = ByteArray(32) { 0x04 } + val endState2 = ByteArray(32) { 0x05 } + + val flowChunk1 = FlowChunk( + collectionIndex = collectionIndex1, + startState = startState1, + eventCollection = startState1, + blockId = blockId1, + totalComputationUsed = totalComputationUsed1, + numberOfTransactions = 10, + index = 5L, + endState = endState1, + executionDataId = blockId1, + stateDeltaCommitment = startState1 + ) + + val flowChunk2 = FlowChunk( + collectionIndex = collectionIndex1, + startState = startState1, + eventCollection = startState1, + blockId = blockId1, + totalComputationUsed = totalComputationUsed1, + numberOfTransactions = 10, + index = 5L, + endState = endState1, + executionDataId = blockId1, + stateDeltaCommitment = startState1 + ) + + val flowChunk3 = FlowChunk( + collectionIndex = collectionIndex2, + startState = startState2, + eventCollection = startState2, + blockId = blockId2, + totalComputationUsed = totalComputationUsed2, + numberOfTransactions = 20, + index = 10L, + endState = endState2, + executionDataId = blockId2, + stateDeltaCommitment = startState2 + ) + + assertEquals(flowChunk1.hashCode(), flowChunk2.hashCode()) + assertNotEquals(flowChunk1.hashCode(), flowChunk3.hashCode()) + } +} diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowCollectionGuaranteeTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowCollectionGuaranteeTest.kt index ef3e6e3..69c22c5 100644 --- a/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowCollectionGuaranteeTest.kt +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowCollectionGuaranteeTest.kt @@ -7,19 +7,26 @@ import org.onflow.flow.sdk.FlowSignature import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.onflow.protobuf.entities.CollectionOuterClass +import com.google.protobuf.UnsafeByteOperations class FlowCollectionGuaranteeTest { @Test fun `Test building FlowCollectionGuarantee from CollectionOuterClass`() { val collectionIdBytes = byteArrayOf(1, 2, 3) + val referenceBlockIdBytes = byteArrayOf(10, 11, 12) + val signatureBytes = byteArrayOf(13, 14, 15) val signature1Bytes = byteArrayOf(4, 5, 6) val signature2Bytes = byteArrayOf(7, 8, 9) + val signerIndicesBytes = byteArrayOf(16, 17) val collectionGuaranteeBuilder = CollectionOuterClass.CollectionGuarantee .newBuilder() .setCollectionId(ByteString.copyFrom(collectionIdBytes)) .addSignatures(ByteString.copyFrom(signature1Bytes)) .addSignatures(ByteString.copyFrom(signature2Bytes)) + .setReferenceBlockId(ByteString.copyFrom(referenceBlockIdBytes)) + .setSignature(ByteString.copyFrom(signatureBytes)) + .setSignerIndices(UnsafeByteOperations.unsafeWrap(signerIndicesBytes)) val flowCollectionGuarantee = FlowCollectionGuarantee.of(collectionGuaranteeBuilder.build()) @@ -27,14 +34,20 @@ class FlowCollectionGuaranteeTest { assertEquals(2, flowCollectionGuarantee.signatures.size) assertEquals(FlowSignature(signature1Bytes), flowCollectionGuarantee.signatures[0]) assertEquals(FlowSignature(signature2Bytes), flowCollectionGuarantee.signatures[1]) + assertEquals(FlowId.of(referenceBlockIdBytes), flowCollectionGuarantee.referenceBlockId) + assertEquals(FlowSignature(signatureBytes), flowCollectionGuarantee.signature) + assertEquals(signerIndicesBytes.toList(), flowCollectionGuarantee.signerIndices.toList()) } @Test fun `Test building CollectionOuterClass from FlowCollectionGuarantee`() { val collectionId = FlowId.of(byteArrayOf(1, 2, 3)) + val referenceBlockId = FlowId.of(byteArrayOf(10, 11, 12)) + val signature = FlowSignature(byteArrayOf(13, 14, 15)) val signatures = listOf(FlowSignature(byteArrayOf(4, 5, 6)), FlowSignature(byteArrayOf(7, 8, 9))) + val signerIndices = byteArrayOf(16, 17) - val flowCollectionGuarantee = FlowCollectionGuarantee(collectionId, signatures) + val flowCollectionGuarantee = FlowCollectionGuarantee(collectionId, signatures, referenceBlockId, signature, signerIndices) val collectionGuaranteeBuilder = flowCollectionGuarantee.builder() val collectionGuarantee = collectionGuaranteeBuilder.build() @@ -43,5 +56,8 @@ class FlowCollectionGuaranteeTest { assertEquals(2, collectionGuarantee.signaturesCount) assertEquals(ByteString.copyFrom(signatures[0].bytes), collectionGuarantee.getSignatures(0)) assertEquals(ByteString.copyFrom(signatures[1].bytes), collectionGuarantee.getSignatures(1)) + assertEquals(ByteString.copyFrom(referenceBlockId.bytes), collectionGuarantee.referenceBlockId) + assertEquals(ByteString.copyFrom(signature.bytes), collectionGuarantee.signature) + assertEquals(ByteString.copyFrom(signerIndices), collectionGuarantee.signerIndices) } } diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowExecutionReceiptMetaTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowExecutionReceiptMetaTest.kt new file mode 100644 index 0000000..fc40ed6 --- /dev/null +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowExecutionReceiptMetaTest.kt @@ -0,0 +1,92 @@ +package org.onflow.flow.sdk.models + +import com.google.protobuf.ByteString +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.mockito.Mockito.* +import org.onflow.flow.sdk.FlowExecutionReceiptMeta +import org.onflow.flow.sdk.FlowId +import org.onflow.flow.sdk.FlowSignature +import org.onflow.protobuf.entities.ExecutionResultOuterClass + +class FlowExecutionReceiptMetaTest { + @Test + fun `test of() method`() { + val executorIdBytes = ByteArray(32) { 0x01 } + val resultIdBytes = ByteArray(32) { 0x02 } + val spocksBytes = listOf(ByteArray(32) { 0x03 }, ByteArray(32) { 0x04 }) + val executorSignatureBytes = ByteArray(32) { 0x05 } + + val grpcMeta = mock(ExecutionResultOuterClass.ExecutionReceiptMeta::class.java) + `when`(grpcMeta.executorId).thenReturn(ByteString.copyFrom(executorIdBytes)) + `when`(grpcMeta.resultId).thenReturn(ByteString.copyFrom(resultIdBytes)) + `when`(grpcMeta.spocksList).thenReturn(spocksBytes.map { ByteString.copyFrom(it) }) + `when`(grpcMeta.executorSignature).thenReturn(ByteString.copyFrom(executorSignatureBytes)) + + val flowMeta = FlowExecutionReceiptMeta.of(grpcMeta) + + assertEquals(FlowId.of(executorIdBytes), flowMeta.executorId) + assertEquals(FlowId.of(resultIdBytes), flowMeta.resultId) + assertArrayEquals(spocksBytes[0], flowMeta.spocks[0]) + assertArrayEquals(spocksBytes[1], flowMeta.spocks[1]) + assertEquals(FlowSignature(executorSignatureBytes), flowMeta.executorSignature) + } + + @Test + fun `test builder() method`() { + val executorId = FlowId.of(ByteArray(32) { 0x01 }) + val resultId = FlowId.of(ByteArray(32) { 0x02 }) + val spocks = listOf(ByteArray(32) { 0x03 }, ByteArray(32) { 0x04 }) + val executorSignature = FlowSignature(ByteArray(32) { 0x05 }) + + val flowMeta = FlowExecutionReceiptMeta(executorId, resultId, spocks, executorSignature) + val result = flowMeta.builder().build() + + assertEquals(executorId.byteStringValue, result.executorId) + assertEquals(resultId.byteStringValue, result.resultId) + assertEquals(spocks.size, result.spocksCount) + assertEquals(ByteString.copyFrom(spocks[0]), result.spocksList[0]) + assertEquals(ByteString.copyFrom(spocks[1]), result.spocksList[1]) + assertEquals(executorSignature.byteStringValue, result.executorSignature) + } + + @Test + fun `test equals() method`() { + val executorId1 = FlowId.of(ByteArray(32) { 0x01 }) + val resultId1 = FlowId.of(ByteArray(32) { 0x02 }) + val spocks1 = listOf(ByteArray(32) { 0x03 }) + val executorSignature1 = FlowSignature(ByteArray(32) { 0x04 }) + + val executorId2 = FlowId.of(ByteArray(32) { 0x05 }) + val resultId2 = FlowId.of(ByteArray(32) { 0x06 }) + val spocks2 = listOf(ByteArray(32) { 0x07 }) + val executorSignature2 = FlowSignature(ByteArray(32) { 0x08 }) + + val meta1 = FlowExecutionReceiptMeta(executorId1, resultId1, spocks1, executorSignature1) + val meta2 = FlowExecutionReceiptMeta(executorId1, resultId1, spocks1, executorSignature1) + val meta3 = FlowExecutionReceiptMeta(executorId2, resultId2, spocks2, executorSignature2) + + assertEquals(meta1, meta2) + assertNotEquals(meta1, meta3) + } + + @Test + fun `test hashCode() method`() { + val executorId1 = FlowId.of(ByteArray(32) { 0x01 }) + val resultId1 = FlowId.of(ByteArray(32) { 0x02 }) + val spocks1 = listOf(ByteArray(32) { 0x03 }) + val executorSignature1 = FlowSignature(ByteArray(32) { 0x04 }) + + val executorId2 = FlowId.of(ByteArray(32) { 0x05 }) + val resultId2 = FlowId.of(ByteArray(32) { 0x06 }) + val spocks2 = listOf(ByteArray(32) { 0x07 }) + val executorSignature2 = FlowSignature(ByteArray(32) { 0x08 }) + + val meta1 = FlowExecutionReceiptMeta(executorId1, resultId1, spocks1, executorSignature1) + val meta2 = FlowExecutionReceiptMeta(executorId1, resultId1, spocks1, executorSignature1) + val meta3 = FlowExecutionReceiptMeta(executorId2, resultId2, spocks2, executorSignature2) + + assertEquals(meta1.hashCode(), meta2.hashCode()) + assertNotEquals(meta1.hashCode(), meta3.hashCode()) + } +} diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowExecutionResultTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowExecutionResultTest.kt new file mode 100644 index 0000000..c19d954 --- /dev/null +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowExecutionResultTest.kt @@ -0,0 +1,212 @@ +package org.onflow.flow.sdk.models + +import com.google.protobuf.ByteString +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.mockito.Mockito.* +import org.onflow.flow.sdk.* +import org.onflow.protobuf.access.Access +import org.onflow.protobuf.entities.ExecutionResultOuterClass + +class FlowExecutionResultTest { + companion object { + val collectionIndex = 1 + val startStateBytes = ByteArray(32) { 0x01 } + val eventCollectionBytes = ByteArray(32) { 0x02 } + val totalComputationUsed = 1000L + val numberOfTransactions = 10 + val index = 5L + val endStateBytes = ByteArray(32) { 0x04 } + val executionDataIdBytes = ByteArray(32) { 0x05 } + val stateDeltaCommitmentBytes = ByteArray(32) { 0x06 } + val payloadBytes = ByteArray(32) { 0x07 } + + val blockIdBytes = ByteArray(32) { 0x01 } + val previousResultIdBytes = ByteArray(32) { 0x02 } + } + + @Test + fun `test of() method with ExecutionResultByIDResponse`() { + // Mock inner ExecutionResult + val grpcExecutionResultInner = mock(ExecutionResultOuterClass.ExecutionResult::class.java) + `when`(grpcExecutionResultInner.blockId).thenReturn(ByteString.copyFrom(blockIdBytes)) + `when`(grpcExecutionResultInner.previousResultId).thenReturn(ByteString.copyFrom(previousResultIdBytes)) + + // Mock flow chunk + val grpcChunk = mock(ExecutionResultOuterClass.Chunk::class.java) + `when`(grpcChunk.collectionIndex).thenReturn(collectionIndex) + `when`(grpcChunk.startState).thenReturn(ByteString.copyFrom(startStateBytes)) + `when`(grpcChunk.eventCollection).thenReturn(ByteString.copyFrom(eventCollectionBytes)) + `when`(grpcChunk.blockId).thenReturn(ByteString.copyFrom(blockIdBytes)) + `when`(grpcChunk.totalComputationUsed).thenReturn(totalComputationUsed) + `when`(grpcChunk.numberOfTransactions).thenReturn(numberOfTransactions) + `when`(grpcChunk.index).thenReturn(index) + `when`(grpcChunk.endState).thenReturn(ByteString.copyFrom(endStateBytes)) + `when`(grpcChunk.executionDataId).thenReturn(ByteString.copyFrom(executionDataIdBytes)) + `when`(grpcChunk.stateDeltaCommitment).thenReturn(ByteString.copyFrom(stateDeltaCommitmentBytes)) + + `when`(grpcExecutionResultInner.chunksList).thenReturn(listOf(grpcChunk)) + + // Mock service event + val serviceEvent = mock(ExecutionResultOuterClass.ServiceEvent::class.java) + `when`(serviceEvent.type).thenReturn("EventType") + `when`(serviceEvent.payload).thenReturn(ByteString.copyFrom(payloadBytes)) + `when`(grpcExecutionResultInner.serviceEventsList).thenReturn(listOf(serviceEvent)) + + // Mock outer Access.ExecutionResultByIDResponse + val grpcExecutionResult = mock(Access.ExecutionResultByIDResponse::class.java) + `when`(grpcExecutionResult.executionResult).thenReturn(grpcExecutionResultInner) + + val flowExecutionResult = FlowExecutionResult.of(grpcExecutionResult) + + assertEquals(FlowId.of(blockIdBytes), flowExecutionResult.blockId) + assertEquals(FlowId.of(previousResultIdBytes), flowExecutionResult.previousResultId) + assertEquals(1, flowExecutionResult.chunks.size) + assertEquals(1, flowExecutionResult.serviceEvents.size) + assertEquals("EventType", flowExecutionResult.serviceEvents[0].type) + assertArrayEquals(payloadBytes, flowExecutionResult.serviceEvents[0].payload) + } + + @Test + fun `test of() method with ExecutionResult`() { + // Mock flow chunk + val grpcChunk = mock(ExecutionResultOuterClass.Chunk::class.java) + `when`(grpcChunk.collectionIndex).thenReturn(collectionIndex) + `when`(grpcChunk.startState).thenReturn(ByteString.copyFrom(startStateBytes)) + `when`(grpcChunk.eventCollection).thenReturn(ByteString.copyFrom(eventCollectionBytes)) + `when`(grpcChunk.blockId).thenReturn(ByteString.copyFrom(blockIdBytes)) + `when`(grpcChunk.totalComputationUsed).thenReturn(totalComputationUsed) + `when`(grpcChunk.numberOfTransactions).thenReturn(numberOfTransactions) + `when`(grpcChunk.index).thenReturn(index) + `when`(grpcChunk.endState).thenReturn(ByteString.copyFrom(endStateBytes)) + `when`(grpcChunk.executionDataId).thenReturn(ByteString.copyFrom(executionDataIdBytes)) + `when`(grpcChunk.stateDeltaCommitment).thenReturn(ByteString.copyFrom(stateDeltaCommitmentBytes)) + + // Mock service event + val serviceEvent = mock(ExecutionResultOuterClass.ServiceEvent::class.java) + `when`(serviceEvent.type).thenReturn("EventType") + `when`(serviceEvent.payload).thenReturn(ByteString.copyFrom(payloadBytes)) + + // Mock ExecutionResult + val grpcExecutionResult = mock(ExecutionResultOuterClass.ExecutionResult::class.java) + `when`(grpcExecutionResult.blockId).thenReturn(ByteString.copyFrom(blockIdBytes)) + `when`(grpcExecutionResult.previousResultId).thenReturn(ByteString.copyFrom(previousResultIdBytes)) + `when`(grpcExecutionResult.chunksList).thenReturn(listOf(grpcChunk)) + `when`(grpcExecutionResult.serviceEventsList).thenReturn(listOf(serviceEvent)) + + val flowExecutionResult = FlowExecutionResult.of(grpcExecutionResult) + + assertEquals(FlowId.of(blockIdBytes), flowExecutionResult.blockId) + assertEquals(FlowId.of(previousResultIdBytes), flowExecutionResult.previousResultId) + assertEquals(1, flowExecutionResult.chunks.size) + assertEquals(1, flowExecutionResult.serviceEvents.size) + assertEquals("EventType", flowExecutionResult.serviceEvents[0].type) + assertArrayEquals(payloadBytes, flowExecutionResult.serviceEvents[0].payload) + } + + @Test + fun `test builder() method`() { + val serviceEvent = FlowServiceEvent("EventType", blockIdBytes) + + val flowChunk = FlowChunk( + collectionIndex = collectionIndex, + startState = startStateBytes, + eventCollection = eventCollectionBytes, + blockId = FlowId.of(blockIdBytes), + totalComputationUsed = totalComputationUsed, + numberOfTransactions = numberOfTransactions, + index = index, + endState = endStateBytes, + executionDataId = FlowId.of(blockIdBytes), + stateDeltaCommitment = stateDeltaCommitmentBytes + ) + + val flowExecutionResult = FlowExecutionResult( + blockId = FlowId.of(blockIdBytes), + previousResultId = FlowId.of(previousResultIdBytes), + chunks = listOf(flowChunk), + serviceEvents = listOf(serviceEvent) + ) + + val result = flowExecutionResult.builder().build() + + assertEquals(FlowId.of(blockIdBytes).byteStringValue, result.blockId) + assertEquals(FlowId.of(previousResultIdBytes).byteStringValue, result.previousResultId) + assertEquals(1, result.chunksCount) + assertEquals(1, result.serviceEventsCount) + } + + @Test + fun `test equals() method`() { + val blockId1 = FlowId.of(ByteArray(32) { 0x01 }) + val previousResultId1 = FlowId.of(ByteArray(32) { 0x02 }) + val chunk1 = mock(FlowChunk::class.java) + val serviceEvent1 = mock(FlowServiceEvent::class.java) + + val blockId2 = FlowId.of(ByteArray(32) { 0x03 }) + val previousResultId2 = FlowId.of(ByteArray(32) { 0x04 }) + val chunk2 = mock(FlowChunk::class.java) + val serviceEvent2 = mock(FlowServiceEvent::class.java) + + val flowExecutionResult1 = FlowExecutionResult( + blockId = blockId1, + previousResultId = previousResultId1, + chunks = listOf(chunk1), + serviceEvents = listOf(serviceEvent1) + ) + + val flowExecutionResult2 = FlowExecutionResult( + blockId = blockId1, + previousResultId = previousResultId1, + chunks = listOf(chunk1), + serviceEvents = listOf(serviceEvent1) + ) + + val flowExecutionResult3 = FlowExecutionResult( + blockId = blockId2, + previousResultId = previousResultId2, + chunks = listOf(chunk2), + serviceEvents = listOf(serviceEvent2) + ) + + assertEquals(flowExecutionResult1, flowExecutionResult2) + assertNotEquals(flowExecutionResult1, flowExecutionResult3) + } + + @Test + fun `test hashCode() method`() { + val blockId1 = FlowId.of(ByteArray(32) { 0x01 }) + val previousResultId1 = FlowId.of(ByteArray(32) { 0x02 }) + val chunk1 = mock(FlowChunk::class.java) + val serviceEvent1 = mock(FlowServiceEvent::class.java) + + val blockId2 = FlowId.of(ByteArray(32) { 0x03 }) + val previousResultId2 = FlowId.of(ByteArray(32) { 0x04 }) + val chunk2 = mock(FlowChunk::class.java) + val serviceEvent2 = mock(FlowServiceEvent::class.java) + + val flowExecutionResult1 = FlowExecutionResult( + blockId = blockId1, + previousResultId = previousResultId1, + chunks = listOf(chunk1), + serviceEvents = listOf(serviceEvent1) + ) + + val flowExecutionResult2 = FlowExecutionResult( + blockId = blockId1, + previousResultId = previousResultId1, + chunks = listOf(chunk1), + serviceEvents = listOf(serviceEvent1) + ) + + val flowExecutionResult3 = FlowExecutionResult( + blockId = blockId2, + previousResultId = previousResultId2, + chunks = listOf(chunk2), + serviceEvents = listOf(serviceEvent2) + ) + + assertEquals(flowExecutionResult1.hashCode(), flowExecutionResult2.hashCode()) + assertNotEquals(flowExecutionResult1.hashCode(), flowExecutionResult3.hashCode()) + } +} diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowNodeVersionInfoTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowNodeVersionInfoTest.kt new file mode 100644 index 0000000..255fa48 --- /dev/null +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowNodeVersionInfoTest.kt @@ -0,0 +1,118 @@ +package org.onflow.flow.sdk.models + +import com.google.protobuf.UnsafeByteOperations +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.onflow.flow.sdk.FlowCompatibleRange +import org.onflow.flow.sdk.FlowNodeVersionInfo +import org.onflow.protobuf.entities.NodeVersionInfoOuterClass + +class FlowNodeVersionInfoTest { + @Test + fun `FlowNodeVersionInfo builder`() { + val sporkId = byteArrayOf(0x01, 0x02) + val versionInfo = FlowNodeVersionInfo( + semver = "1.0.0", + commit = "commit1", + sporkId = sporkId, + protocolVersion = 1L, + sporkRootBlockHeight = 100L, + nodeRootBlockHeight = 200L, + compatibleRange = null + ) + + // Test builder function + val builder = versionInfo.builder() + assertEquals(versionInfo.semver, builder.semver) + assertEquals(versionInfo.commit, builder.commit) + assertArrayEquals(versionInfo.sporkId, builder.sporkId.toByteArray()) + assertEquals(versionInfo.protocolVersion, builder.protocolVersion) + assertEquals(versionInfo.sporkRootBlockHeight, builder.sporkRootBlockHeight) + assertEquals(versionInfo.nodeRootBlockHeight, builder.nodeRootBlockHeight) + } + + @Test + fun `FlowNodeVersionInfo of method`() { + val sporkId = byteArrayOf(0x01, 0x02) + val protoVersionInfo = NodeVersionInfoOuterClass.NodeVersionInfo + .newBuilder() + .setSemver("1.0.0") + .setCommit("commit1") + .setSporkId(UnsafeByteOperations.unsafeWrap(sporkId)) + .setProtocolVersion(1L) + .setSporkRootBlockHeight(100L) + .setNodeRootBlockHeight(200L) + .build() + + // Test of() method + val versionInfo = FlowNodeVersionInfo.of(protoVersionInfo) + + assertEquals("1.0.0", versionInfo.semver) + assertEquals("commit1", versionInfo.commit) + assertArrayEquals(sporkId, versionInfo.sporkId) + assertEquals(1L, versionInfo.protocolVersion) + assertEquals(100L, versionInfo.sporkRootBlockHeight) + assertEquals(200L, versionInfo.nodeRootBlockHeight) + assertNull(versionInfo.compatibleRange) + } + + @Test + fun `FlowCompatibleRange equality and hashcode`() { + val range1 = FlowCompatibleRange(startHeight = 1L, endHeight = 10L) + val range2 = FlowCompatibleRange(startHeight = 1L, endHeight = 10L) + val range3 = FlowCompatibleRange(startHeight = 2L, endHeight = 15L) + + // Test equality + assertEquals(range1, range2) + assertNotEquals(range1, range3) + + // Test hashcode + assertEquals(range1.hashCode(), range2.hashCode()) + assertNotEquals(range1.hashCode(), range3.hashCode()) + } + + @Test + fun `FlowNodeVersionInfo equality and hashcode`() { + val sporkId1 = byteArrayOf(0x01, 0x02) + val sporkId2 = byteArrayOf(0x01, 0x02) + val sporkId3 = byteArrayOf(0x03, 0x04) + + val versionInfo1 = FlowNodeVersionInfo( + semver = "1.0.0", + commit = "commit1", + sporkId = sporkId1, + protocolVersion = 1L, + sporkRootBlockHeight = 100L, + nodeRootBlockHeight = 200L, + compatibleRange = FlowCompatibleRange(startHeight = 1L, endHeight = 10L) + ) + + val versionInfo2 = FlowNodeVersionInfo( + semver = "1.0.0", + commit = "commit1", + sporkId = sporkId2, + protocolVersion = 1L, + sporkRootBlockHeight = 100L, + nodeRootBlockHeight = 200L, + compatibleRange = FlowCompatibleRange(startHeight = 1L, endHeight = 10L) + ) + + val versionInfo3 = FlowNodeVersionInfo( + semver = "2.0.0", + commit = "commit2", + sporkId = sporkId3, + protocolVersion = 2L, + sporkRootBlockHeight = 150L, + nodeRootBlockHeight = 250L, + compatibleRange = null + ) + + // Test equality + assertEquals(versionInfo1, versionInfo2) + assertNotEquals(versionInfo1, versionInfo3) + + // Test hashcode + assertEquals(versionInfo1.hashCode(), versionInfo2.hashCode()) + assertNotEquals(versionInfo1.hashCode(), versionInfo3.hashCode()) + } +} diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowQuorumCertificateTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowQuorumCertificateTest.kt new file mode 100644 index 0000000..2fd4405 --- /dev/null +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowQuorumCertificateTest.kt @@ -0,0 +1,89 @@ +package org.onflow.flow.sdk.models + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.mockito.Mockito.* +import com.google.protobuf.ByteString +import org.onflow.flow.sdk.FlowId +import org.onflow.flow.sdk.FlowQuorumCertificate +import org.onflow.protobuf.entities.BlockHeaderOuterClass + +class FlowQuorumCertificateTest { + @Test + fun `test of() method`() { + val view = 123L + val blockIdBytes = ByteArray(32) { 0x01 } + val signerIndicesBytes = ByteArray(32) { 0x02 } + val sigDataBytes = ByteArray(32) { 0x03 } + + val grpcQuorumCertificate = mock(BlockHeaderOuterClass.QuorumCertificate::class.java) + `when`(grpcQuorumCertificate.view).thenReturn(view) + `when`(grpcQuorumCertificate.blockId).thenReturn(ByteString.copyFrom(blockIdBytes)) + `when`(grpcQuorumCertificate.signerIndices).thenReturn(ByteString.copyFrom(signerIndicesBytes)) + `when`(grpcQuorumCertificate.sigData).thenReturn(ByteString.copyFrom(sigDataBytes)) + + val flowQuorumCertificate = FlowQuorumCertificate.of(grpcQuorumCertificate) + + assertEquals(view, flowQuorumCertificate.view) + assertArrayEquals(blockIdBytes, flowQuorumCertificate.blockId.bytes) + assertArrayEquals(signerIndicesBytes, flowQuorumCertificate.signerIndices) + assertArrayEquals(sigDataBytes, flowQuorumCertificate.sigData) + } + + @Test + fun `test builder() method`() { + val view = 123L + val blockId = FlowId.of(ByteArray(32) { 0x01 }) + val signerIndices = ByteArray(32) { 0x02 } + val sigData = ByteArray(32) { 0x03 } + + val flowQuorumCertificate = FlowQuorumCertificate(view, blockId, signerIndices, sigData) + + val builder = flowQuorumCertificate.builder().build() + + assertEquals(view, builder.view) + assertArrayEquals(blockId.bytes, builder.blockId.toByteArray()) + assertArrayEquals(signerIndices, builder.signerIndices.toByteArray()) + assertArrayEquals(sigData, builder.sigData.toByteArray()) + } + + @Test + fun `test equals() method`() { + val view1 = 123L + val blockId1 = FlowId.of(ByteArray(32) { 0x01 }) + val signerIndices1 = ByteArray(32) { 0x02 } + val sigData1 = ByteArray(32) { 0x03 } + + val view2 = 124L + val blockId2 = FlowId.of(ByteArray(32) { 0x04 }) + val signerIndices2 = ByteArray(32) { 0x05 } + val sigData2 = ByteArray(32) { 0x06 } + + val flowQuorumCertificate1 = FlowQuorumCertificate(view1, blockId1, signerIndices1, sigData1) + val flowQuorumCertificate2 = FlowQuorumCertificate(view1, blockId1, signerIndices1, sigData1) + val flowQuorumCertificate3 = FlowQuorumCertificate(view2, blockId2, signerIndices2, sigData2) + + assertEquals(flowQuorumCertificate1, flowQuorumCertificate2) + assertNotEquals(flowQuorumCertificate1, flowQuorumCertificate3) + } + + @Test + fun `test hashCode() method`() { + val view1 = 123L + val blockId1 = FlowId.of(ByteArray(32) { 0x01 }) + val signerIndices1 = ByteArray(32) { 0x02 } + val sigData1 = ByteArray(32) { 0x03 } + + val view2 = 124L + val blockId2 = FlowId.of(ByteArray(32) { 0x04 }) + val signerIndices2 = ByteArray(32) { 0x05 } + val sigData2 = ByteArray(32) { 0x06 } + + val flowQuorumCertificate1 = FlowQuorumCertificate(view1, blockId1, signerIndices1, sigData1) + val flowQuorumCertificate2 = FlowQuorumCertificate(view1, blockId1, signerIndices1, sigData1) + val flowQuorumCertificate3 = FlowQuorumCertificate(view2, blockId2, signerIndices2, sigData2) + + assertEquals(flowQuorumCertificate1.hashCode(), flowQuorumCertificate2.hashCode()) + assertNotEquals(flowQuorumCertificate1.hashCode(), flowQuorumCertificate3.hashCode()) + } +} diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowTimeoutCertificateTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowTimeoutCertificateTest.kt new file mode 100644 index 0000000..8ffb319 --- /dev/null +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowTimeoutCertificateTest.kt @@ -0,0 +1,128 @@ +package org.onflow.flow.sdk.models + +import com.google.protobuf.ByteString +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.mockito.Mockito.* +import org.onflow.flow.sdk.FlowQuorumCertificate +import org.onflow.flow.sdk.FlowTimeoutCertificate +import org.onflow.protobuf.entities.BlockHeaderOuterClass + +class FlowTimeoutCertificateTest { + @Test + fun `test of() method`() { + val view = 123L + val highQcViews = listOf(456L, 789L) + val signerIndicesBytes = ByteArray(32) { 0x01 } + val sigDataBytes = ByteArray(32) { 0x02 } + + // mock QuorumCertificate + val grpcHighestQc = mock(BlockHeaderOuterClass.QuorumCertificate::class.java) + `when`(grpcHighestQc.view).thenReturn(view) + `when`(grpcHighestQc.blockId).thenReturn(ByteString.copyFrom(ByteArray(32) { 0x01 })) + `when`(grpcHighestQc.signerIndices).thenReturn(ByteString.copyFrom(ByteArray(32) { 0x02 })) + `when`(grpcHighestQc.sigData).thenReturn(ByteString.copyFrom(ByteArray(32) { 0x03 })) + + val highestQc = FlowQuorumCertificate.of(grpcHighestQc) + + // mock TimeoutCertificate + val grpcTimeoutCertificate = mock(BlockHeaderOuterClass.TimeoutCertificate::class.java) + `when`(grpcTimeoutCertificate.view).thenReturn(view) + `when`(grpcTimeoutCertificate.highQcViewsList).thenReturn(highQcViews) + `when`(grpcTimeoutCertificate.highestQc).thenReturn(grpcHighestQc) // Return the mocked QuorumCertificate + `when`(grpcTimeoutCertificate.signerIndices).thenReturn(ByteString.copyFrom(signerIndicesBytes)) + `when`(grpcTimeoutCertificate.sigData).thenReturn(ByteString.copyFrom(sigDataBytes)) + + val flowTimeoutCertificate = FlowTimeoutCertificate.of(grpcTimeoutCertificate) + + assertEquals(view, flowTimeoutCertificate.view) + assertEquals(highQcViews, flowTimeoutCertificate.highQcViews) + assertEquals(highestQc, flowTimeoutCertificate.highestQc) + assertArrayEquals(signerIndicesBytes, flowTimeoutCertificate.signerIndices) + assertArrayEquals(sigDataBytes, flowTimeoutCertificate.sigData) + } + + @Test + fun `test builder() method`() { + // Mock FlowQuorumCertificate + val view = 123L + val blockIdBytes = ByteArray(32) { 0x01 } + val signerIndicesBytes = ByteArray(32) { 0x02 } + val sigDataBytes = ByteArray(32) { 0x03 } + + val grpcQuorumCertificate = mock(BlockHeaderOuterClass.QuorumCertificate::class.java) + `when`(grpcQuorumCertificate.view).thenReturn(view) + `when`(grpcQuorumCertificate.blockId).thenReturn(ByteString.copyFrom(blockIdBytes)) + `when`(grpcQuorumCertificate.signerIndices).thenReturn(ByteString.copyFrom(signerIndicesBytes)) + `when`(grpcQuorumCertificate.sigData).thenReturn(ByteString.copyFrom(sigDataBytes)) + + val flowQuorumCertificate = FlowQuorumCertificate.of(grpcQuorumCertificate) + + // Mock FlowTimeoutCertificate + val highQcViews = listOf(1L, 2L) + val signerIndices = byteArrayOf(1, 2) + val sigData = byteArrayOf(3, 4) + + val timeoutCertificate = FlowTimeoutCertificate( + view = view, + highQcViews = highQcViews, + highestQc = flowQuorumCertificate, + signerIndices = signerIndices, + sigData = sigData + ) + + val result = timeoutCertificate.builder().build() + + assertEquals(view, result.view) + assertEquals(highQcViews, result.highQcViewsList) + assertEquals(ByteString.copyFrom(signerIndices), result.signerIndices) + assertEquals(ByteString.copyFrom(sigData), result.sigData) + assertEquals(flowQuorumCertificate.builder().build(), result.highestQc) + } + + @Test + fun `test equals() method`() { + val view1 = 123L + val highQcViews1 = listOf(456L, 789L) + val signerIndices1 = ByteArray(32) { 0x01 } + val sigData1 = ByteArray(32) { 0x02 } + + val view2 = 124L + val highQcViews2 = listOf(111L, 222L) + val signerIndices2 = ByteArray(32) { 0x03 } + val sigData2 = ByteArray(32) { 0x04 } + + val highestQc1 = mock(FlowQuorumCertificate::class.java) + val highestQc2 = mock(FlowQuorumCertificate::class.java) + + val flowTimeoutCertificate1 = FlowTimeoutCertificate(view1, highQcViews1, highestQc1, signerIndices1, sigData1) + val flowTimeoutCertificate2 = FlowTimeoutCertificate(view1, highQcViews1, highestQc1, signerIndices1, sigData1) + val flowTimeoutCertificate3 = FlowTimeoutCertificate(view2, highQcViews2, highestQc2, signerIndices2, sigData2) + + assertEquals(flowTimeoutCertificate1, flowTimeoutCertificate2) + assertNotEquals(flowTimeoutCertificate1, flowTimeoutCertificate3) + } + + @Test + fun `test hashCode() method`() { + val view1 = 123L + val highQcViews1 = listOf(456L, 789L) + val signerIndices1 = ByteArray(32) { 0x01 } + val sigData1 = ByteArray(32) { 0x02 } + + val view2 = 124L + val highQcViews2 = listOf(111L, 222L) + val signerIndices2 = ByteArray(32) { 0x03 } + val sigData2 = ByteArray(32) { 0x04 } + + val highestQc1 = mock(FlowQuorumCertificate::class.java) + val highestQc2 = mock(FlowQuorumCertificate::class.java) + + val flowTimeoutCertificate1 = FlowTimeoutCertificate(view1, highQcViews1, highestQc1, signerIndices1, sigData1) + val flowTimeoutCertificate2 = FlowTimeoutCertificate(view1, highQcViews1, highestQc1, signerIndices1, sigData1) + val flowTimeoutCertificate3 = FlowTimeoutCertificate(view2, highQcViews2, highestQc2, signerIndices2, sigData2) + + assertEquals(flowTimeoutCertificate1.hashCode(), flowTimeoutCertificate2.hashCode()) + assertNotEquals(flowTimeoutCertificate1.hashCode(), flowTimeoutCertificate3.hashCode()) + } +}