From 55ee2bedd3d8a855e4674281c73a3d4ca624ad5c Mon Sep 17 00:00:00 2001 From: Andrey Kostiuchenko Date: Wed, 31 May 2023 10:32:29 +0500 Subject: [PATCH 01/22] [feature] (#324): Support JSON genesis block for integration tests (#326) --- gradle.properties | 2 +- modules/client/build.gradle | 3 + .../kotlin/jp/co/soramitsu/iroha2/Utils.kt | 19 +++-- .../java/jp/co/soramitsu/iroha2/JavaTest.java | 4 +- .../jp/co/soramitsu/iroha2/GenesisTest.kt | 55 ++++++++++++ .../co/soramitsu/iroha2/InstructionsTest.kt | 48 ++++++----- .../kotlin/jp/co/soramitsu/iroha2/PeerTest.kt | 2 +- .../jp/co/soramitsu/iroha2/QueriesTest.kt | 2 +- .../jp/co/soramitsu/iroha2/TriggersTest.kt | 34 ++++---- .../co/soramitsu/iroha2/testengine/Genesis.kt | 2 +- .../client/src/test/resources/genesis.json | 83 +++++++++++++++++++ modules/test-tools/README.md | 5 ++ .../iroha2/testengine/IrohaConfig.kt | 8 +- .../iroha2/testengine/IrohaContainer.kt | 22 +++-- .../iroha2/testengine/IrohaRunnerExtension.kt | 11 +-- .../soramitsu/iroha2/testengine/IrohaTest.kt | 2 +- .../soramitsu/iroha2/testengine/WithIroha.kt | 1 + .../iroha2/testtools/IrohaContainerTest.kt | 49 ++++++----- 18 files changed, 263 insertions(+), 89 deletions(-) create mode 100644 modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/GenesisTest.kt create mode 100644 modules/client/src/test/resources/genesis.json diff --git a/gradle.properties b/gradle.properties index baae267c7..91e3a75ea 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,7 +13,7 @@ i2pCryptoEddsa=0.3.0 multihashVersion=1.3.0 googleTinkVer=1.6.1 # testing -testContainersVer=1.15.3 +testContainersVer=1.18.0 junitVersion=5.8.1 # logging logbackVer=1.2.3 diff --git a/modules/client/build.gradle b/modules/client/build.gradle index f3b1176e5..73b8113dc 100644 --- a/modules/client/build.gradle +++ b/modules/client/build.gradle @@ -5,6 +5,9 @@ plugins { dependencies { api project(":model") + // https://mvnrepository.com/artifact/com.github.docker-java/docker-java + implementation("com.github.docker-java:docker-java:3.3.0") + //kotlin support implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVer" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVer" diff --git a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Utils.kt b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Utils.kt index c628f2c98..d623d0f03 100644 --- a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Utils.kt +++ b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Utils.kt @@ -1,23 +1,30 @@ package jp.co.soramitsu.iroha2 +import com.github.dockerjava.api.DockerClient +import com.github.dockerjava.core.DockerClientBuilder import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import java.io.IOException import java.net.ServerSocket val mutex = Mutex() +val dockerClient: DockerClient = DockerClientBuilder.getInstance().build() suspend fun findFreePorts( amount: Int, lock: Boolean = true ): List { - fun find() = (0 until amount).map { - try { - val socket = ServerSocket(0) + fun find(): List { + val busyPorts = dockerClient.listContainersCmd().exec().map { container -> + container.ports.map { p -> p.publicPort } + }.flatten() + + return (0 until amount).map { + var socket = ServerSocket(0) + while (socket.localPort in busyPorts) { + socket = ServerSocket(0) + } socket.soTimeout = 300 // seconds socket.use { it.localPort } - } catch (e: IOException) { - throw e } } diff --git a/modules/client/src/test/java/jp/co/soramitsu/iroha2/JavaTest.java b/modules/client/src/test/java/jp/co/soramitsu/iroha2/JavaTest.java index d047099ef..c539efb5c 100644 --- a/modules/client/src/test/java/jp/co/soramitsu/iroha2/JavaTest.java +++ b/modules/client/src/test/java/jp/co/soramitsu/iroha2/JavaTest.java @@ -142,7 +142,7 @@ public void updateKeyValueInstructionCommitted() throws Exception { assetMetadataKey, assetMetadataValue2 ).buildSigned(ALICE_KEYPAIR); - client.sendTransactionAsync(keyValueTx).get(10, TimeUnit.SECONDS); + client.sendTransactionAsync(keyValueTx).get(30, TimeUnit.SECONDS); final QueryAndExtractor assetDefinitionValueQuery = QueryBuilder .findAssetKeyValueByIdAndKey(assetId, assetMetadataKey) @@ -150,7 +150,7 @@ public void updateKeyValueInstructionCommitted() throws Exception { .buildSigned(ALICE_KEYPAIR); final CompletableFuture future = client.sendQueryAsync(assetDefinitionValueQuery); - final Value value = future.get(10, TimeUnit.SECONDS); + final Value value = future.get(30, TimeUnit.SECONDS); Assertions.assertEquals( ((Value.String) value).getString(), assetMetadataValue2.getString() diff --git a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/GenesisTest.kt b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/GenesisTest.kt new file mode 100644 index 000000000..c14e6ee74 --- /dev/null +++ b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/GenesisTest.kt @@ -0,0 +1,55 @@ +package jp.co.soramitsu.iroha2 + +import jp.co.soramitsu.iroha2.client.Iroha2Client +import jp.co.soramitsu.iroha2.query.QueryBuilder +import jp.co.soramitsu.iroha2.testengine.ALICE_ACCOUNT_ID +import jp.co.soramitsu.iroha2.testengine.BOB_ACCOUNT_ID +import jp.co.soramitsu.iroha2.testengine.IrohaConfig +import jp.co.soramitsu.iroha2.testengine.IrohaContainer +import jp.co.soramitsu.iroha2.testengine.IrohaTest +import jp.co.soramitsu.iroha2.testengine.WithIroha +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Test + +class GenesisTest : IrohaTest( + account = ALICE_ACCOUNT_ID, + keyPair = ALICE_KEYPAIR +) { + companion object { + private val ALICE_KEYPAIR = keyPairFromHex( + "cc25624d62896d3a0bfd8940f928dc2abf27cc57cefeb442aa96d9081aae58a1", + "3bac34cda9e3763fa069c1198312d1ec73b53023b8180c822ac355435edc4a24" + ) + } + + @Test + @WithIroha(source = "src/test/resources/genesis.json") + fun `register asset instruction committed`(): Unit = runBlocking { + client.checkAliceAndBobExists() + } + + @Test + fun `manual IrohaContainer initialization`(): Unit = runBlocking { + val ports = findFreePorts(3) + val p2pPort = ports[IrohaConfig.P2P_PORT_IDX] + + val path = javaClass.getResource("../../genesis.json")!!.path + val container = IrohaContainer { + this.ports = ports + this.alias = "iroha$p2pPort" + this.genesisPath = path + }.also { it.start() } + + val client = Iroha2Client(container.getApiUrl(), true) + client.checkAliceAndBobExists() + } + + private suspend fun Iroha2Client.checkAliceAndBobExists() { + QueryBuilder.findAllAccounts() + .account(ALICE_ACCOUNT_ID) + .buildSigned(ALICE_KEYPAIR) + .let { query -> this.sendQuery(query) } + .also { accounts -> assert(accounts.any { it.id == ALICE_ACCOUNT_ID }) } + .also { accounts -> assert(accounts.any { it.id == BOB_ACCOUNT_ID }) } + } +} diff --git a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/InstructionsTest.kt b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/InstructionsTest.kt index cbd02ae3e..52f54a7ad 100644 --- a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/InstructionsTest.kt +++ b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/InstructionsTest.kt @@ -51,7 +51,7 @@ import kotlinx.coroutines.time.withTimeout import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import org.testcontainers.shaded.org.apache.commons.lang.RandomStringUtils +import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils import java.math.BigDecimal import java.math.MathContext import java.math.RoundingMode @@ -67,7 +67,7 @@ import kotlin.test.assertTrue @Sdk("Java/Kotlin") class InstructionsTest : IrohaTest( account = ALICE_ACCOUNT_ID, - keyPair = ALICE_KEYPAIR + keyPair = ALICE_KEYPAIR, ) { /** * Using for docs generation @@ -237,8 +237,8 @@ class InstructionsTest : IrohaTest( Pair(addressKey, addressValue), Pair(phoneKey, phoneValue), Pair(emailKey, emailValue), - Pair(cityKey, cityValue) - ) + Pair(cityKey, cityValue), + ), ) val encodedTx = TransactionBuilder { @@ -327,7 +327,7 @@ class InstructionsTest : IrohaTest( assertEquals(pair2.second.bool, value.metadata.map[pair2.first]?.cast()?.bool) assertEquals( pair3.second.numericValue, - value.metadata.map[pair3.first]?.cast()?.numericValue + value.metadata.map[pair3.first]?.cast()?.numericValue, ) } @@ -346,7 +346,7 @@ class InstructionsTest : IrohaTest( @Test @WithIroha( [DefaultGenesis::class], - configs = ["WSV_ACCOUNT_METADATA_LIMITS$IROHA_CONFIG_DELIMITER{\"max_entry_byte_size\": 65536, \"max_len\": 1048576}"] + configs = ["WSV_ACCOUNT_METADATA_LIMITS$IROHA_CONFIG_DELIMITER{\"max_entry_byte_size\": 65536, \"max_len\": 1048576}"], ) fun `account metadata limit increased`(): Unit = runBlocking { client.tx { @@ -637,7 +637,7 @@ class InstructionsTest : IrohaTest( account(ALICE_ACCOUNT_ID) pair( Instructions.burnAsset(DEFAULT_ASSET_ID, 10), - Instructions.burnAsset(DEFAULT_ASSET_ID, 20) + Instructions.burnAsset(DEFAULT_ASSET_ID, 20), ) buildSigned(ALICE_KEYPAIR) }.also { d -> @@ -659,7 +659,7 @@ class InstructionsTest : IrohaTest( sequence( Instructions.burnAsset(DEFAULT_ASSET_ID, 10), Instructions.burnAsset(DEFAULT_ASSET_ID, 20), - Instructions.burnAsset(DEFAULT_ASSET_ID, 30) + Instructions.burnAsset(DEFAULT_ASSET_ID, 30), ) buildSigned(ALICE_KEYPAIR) }.also { d -> @@ -700,7 +700,7 @@ class InstructionsTest : IrohaTest( val assetBefore = getAsset(assetId) assertEquals( StoreAssetWithMetadata.ASSET_VALUE, - assetBefore.value.cast().metadata.map[assetKey] + assetBefore.value.cast().metadata.map[assetKey], ) client.tx { removeKeyValue(assetId, assetKey) } @@ -748,7 +748,9 @@ class InstructionsTest : IrohaTest( .buildSigned(ALICE_KEYPAIR) .let { query -> client.sendQuery(query) } .let { account -> account.assets[DEFAULT_ASSET_ID]?.value } - .let { value -> value?.cast()?.fixed?.fixedPoint ?: BigDecimal.ZERO } + .let { value -> + value?.cast()?.fixed?.fixedPoint ?: BigDecimal.ZERO + } .also { actualBalance -> assertTrue("expected value `$expectedBalance`, but was `$actualBalance`") { expectedBalance.compareTo(actualBalance) == 0 @@ -790,7 +792,7 @@ class InstructionsTest : IrohaTest( .also { value -> Assertions.assertEquals( value.cast().string, - assetValue.string + assetValue.string, ) } } @@ -837,12 +839,12 @@ class InstructionsTest : IrohaTest( roleId, Token( TokenId(Permissions.CanSetKeyValueUserAssetsToken.type), - mapOf(IdKey.AssetId.type.asName() to assetId.toValueId()) + mapOf(IdKey.AssetId.type.asName() to assetId.toValueId()), ), Token( TokenId(Permissions.CanRemoveKeyValueInUserAssets.type), - mapOf(IdKey.AssetId.type.asName() to assetId.toValueId()) - ) + mapOf(IdKey.AssetId.type.asName() to assetId.toValueId()), + ), ) grantRole(roleId, ALICE_ACCOUNT_ID) setKeyValue(assetId, "key".asName(), "value".asValue()) @@ -856,7 +858,7 @@ class InstructionsTest : IrohaTest( assertTrue( asset.value.cast() .metadata.map - .containsValue("value".asValue()) + .containsValue("value".asValue()), ) } } @@ -873,8 +875,8 @@ class InstructionsTest : IrohaTest( XorAndValAssets::class, NewAccountWithMetadata::class, NewDomainWithMetadata::class, - RubbishToTestMultipleGenesis::class - ] + RubbishToTestMultipleGenesis::class, + ], ) @Feature("Configurations") @Permission("no_permission_required") @@ -885,7 +887,7 @@ class InstructionsTest : IrohaTest( val assetBefore = getAsset(assetId) assertEquals( StoreAssetWithMetadata.ASSET_VALUE, - assetBefore.value.cast().metadata.map[assetKey] + assetBefore.value.cast().metadata.map[assetKey], ) QueryBuilder.findAccountById(ALICE_ACCOUNT_ID) .account(ALICE_ACCOUNT_ID) @@ -894,7 +896,7 @@ class InstructionsTest : IrohaTest( .also { alice -> assertEquals( alice.metadata.map[RubbishToTestMultipleGenesis.ALICE_KEY_VALUE.asName()], - RubbishToTestMultipleGenesis.ALICE_KEY_VALUE.asValue() + RubbishToTestMultipleGenesis.ALICE_KEY_VALUE.asValue(), ) } QueryBuilder.findAccountById(BOB_ACCOUNT_ID) @@ -904,7 +906,7 @@ class InstructionsTest : IrohaTest( .also { bob -> assertEquals( bob.metadata.map[RubbishToTestMultipleGenesis.BOB_KEY_VALUE.asName()], - RubbishToTestMultipleGenesis.BOB_KEY_VALUE.asValue() + RubbishToTestMultipleGenesis.BOB_KEY_VALUE.asValue(), ) } QueryBuilder.findDomainById(DEFAULT_DOMAIN_ID) @@ -914,7 +916,7 @@ class InstructionsTest : IrohaTest( .also { domain -> assertEquals( domain.metadata.map[RubbishToTestMultipleGenesis.DOMAIN_KEY_VALUE.asName()], - RubbishToTestMultipleGenesis.DOMAIN_KEY_VALUE.asValue() + RubbishToTestMultipleGenesis.DOMAIN_KEY_VALUE.asValue(), ) } } @@ -931,7 +933,7 @@ class InstructionsTest : IrohaTest( private suspend fun getAccountAmount( accountId: AccountId = ALICE_ACCOUNT_ID, - assetId: AssetId = DEFAULT_ASSET_ID + assetId: AssetId = DEFAULT_ASSET_ID, ): Long { return QueryBuilder.findAccountById(accountId) .account(ALICE_ACCOUNT_ID) @@ -949,7 +951,7 @@ class InstructionsTest : IrohaTest( `if`( condition = condition, then = Instructions.burnAsset(assetId, toBurn), - otherwise = Instructions.burnAsset(assetId, 0) + otherwise = Instructions.burnAsset(assetId, 0), ) buildSigned(ALICE_KEYPAIR) }.also { d -> diff --git a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/PeerTest.kt b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/PeerTest.kt index a7e93e389..f5163f10e 100644 --- a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/PeerTest.kt +++ b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/PeerTest.kt @@ -113,7 +113,7 @@ class PeerTest : IrohaTest() { this.keyPair = keyPair this.ports = ports this.alias = alias - this.networkToJoin = containers.first().network + this.networkToJoin = containers.first().network ?: throw IrohaSdkException("Container network not found") this.genesis = DefaultGenesis::class.createInstance() this.trustedPeers = containers.map { it.extractPeerId() } }.also { it.start() } diff --git a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/QueriesTest.kt b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/QueriesTest.kt index e04a913ff..6bde81177 100644 --- a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/QueriesTest.kt +++ b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/QueriesTest.kt @@ -46,7 +46,7 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.time.withTimeout import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test -import org.testcontainers.shaded.org.apache.commons.lang.RandomStringUtils +import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils import java.time.Instant import kotlin.test.assertContains import kotlin.test.assertContentEquals diff --git a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/TriggersTest.kt b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/TriggersTest.kt index 998a9c80e..7b1d753c9 100644 --- a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/TriggersTest.kt +++ b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/TriggersTest.kt @@ -63,8 +63,8 @@ class TriggersTest : IrohaTest() { val filter = Filters.data( EntityFilters.byAssetDefinition( - eventFilter = AssetDefinitionEventFilter.ByCreated() - ) + eventFilter = AssetDefinitionEventFilter.ByCreated(), + ), ) client.sendTransaction { accountId = ALICE_ACCOUNT_ID @@ -74,7 +74,7 @@ class TriggersTest : IrohaTest() { Repeats.Indefinitely(), ALICE_ACCOUNT_ID, Metadata(mapOf()), - filter + filter, ) buildSigned(ALICE_KEYPAIR) }.also { d -> @@ -114,7 +114,7 @@ class TriggersTest : IrohaTest() { triggerId, listOf(Instructions.mintAsset(DEFAULT_ASSET_ID, 10)), Repeats.Indefinitely(), - ALICE_ACCOUNT_ID + ALICE_ACCOUNT_ID, ) buildSigned(ALICE_KEYPAIR) }.also { d -> @@ -159,7 +159,7 @@ class TriggersTest : IrohaTest() { triggerId, listOf(Instructions.mintAsset(DEFAULT_ASSET_ID, 1)), Repeats.Exactly(1L), - ALICE_ACCOUNT_ID + ALICE_ACCOUNT_ID, ) executeTrigger(triggerId) buildSigned(ALICE_KEYPAIR) @@ -178,7 +178,7 @@ class TriggersTest : IrohaTest() { sendAndAwaitTimeTrigger( triggerId, Repeats.Indefinitely(), - Instructions.burnAsset(DEFAULT_ASSET_ID, 1) + Instructions.burnAsset(DEFAULT_ASSET_ID, 1), ) sendAndWait10Txs() @@ -194,7 +194,7 @@ class TriggersTest : IrohaTest() { sendAndAwaitTimeTrigger( triggerId, Repeats.Exactly(5L), - Instructions.burnAsset(DEFAULT_ASSET_ID, 1) + Instructions.burnAsset(DEFAULT_ASSET_ID, 1), ) sendAndWait10Txs() @@ -211,8 +211,8 @@ class TriggersTest : IrohaTest() { val filter = Filters.time( EventFilters.timeEventFilter( Duration(BigInteger.valueOf(currentTime), 0), - Duration(BigInteger.valueOf(1L), 0) - ) + Duration(BigInteger.valueOf(1L), 0), + ), ) val wasm = this.javaClass.classLoader .getResource("create_nft_for_every_user_smartcontract.wasm") @@ -226,7 +226,7 @@ class TriggersTest : IrohaTest() { Repeats.Indefinitely(), ALICE_ACCOUNT_ID, Metadata(mapOf()), - filter + filter, ) buildSigned(ALICE_KEYPAIR) }.also { d -> @@ -255,9 +255,9 @@ class TriggersTest : IrohaTest() { assets.any { it.id.definitionId == AssetDefinitionId( "nft_number_1_for_alice".asName(), - DEFAULT_DOMAIN_ID + DEFAULT_DOMAIN_ID, ) - } + }, ) } } @@ -274,7 +274,7 @@ class TriggersTest : IrohaTest() { triggerId, listOf(Instructions.mintAsset(DEFAULT_ASSET_ID, 1)), Repeats.Exactly(1L), - ALICE_ACCOUNT_ID + ALICE_ACCOUNT_ID, ) buildSigned(ALICE_KEYPAIR) }.also { d -> @@ -315,7 +315,7 @@ class TriggersTest : IrohaTest() { private suspend fun readQuantity( assetId: AssetId = DEFAULT_ASSET_ID, accountId: AccountId = ALICE_ACCOUNT_ID, - keyPair: KeyPair = ALICE_KEYPAIR + keyPair: KeyPair = ALICE_KEYPAIR, ): Long { return QueryBuilder.findAssetById(assetId) .account(accountId) @@ -328,7 +328,7 @@ class TriggersTest : IrohaTest() { triggerId: TriggerId, repeats: Repeats, instruction: Instruction, - accountId: AccountId = ALICE_ACCOUNT_ID + accountId: AccountId = ALICE_ACCOUNT_ID, ) { client.sendTransaction { this.accountId = accountId @@ -339,8 +339,8 @@ class TriggersTest : IrohaTest() { accountId, EventFilters.timeEventFilter( Duration(BigInteger.valueOf(Instant.now().epochSecond), 0L), - Duration(BigInteger.valueOf(1L), 0L) - ) + Duration(BigInteger.valueOf(1L), 0L), + ), ) buildSigned(ALICE_KEYPAIR) }.also { d -> diff --git a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/testengine/Genesis.kt b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/testengine/Genesis.kt index 72870edd8..a900dd08a 100644 --- a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/testengine/Genesis.kt +++ b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/testengine/Genesis.kt @@ -23,7 +23,7 @@ import jp.co.soramitsu.iroha2.generated.datamodel.trigger.TriggerId import jp.co.soramitsu.iroha2.generated.datamodel.trigger.action.Repeats import jp.co.soramitsu.iroha2.toIrohaPublicKey import jp.co.soramitsu.iroha2.transaction.Instructions -import org.testcontainers.shaded.org.apache.commons.lang.RandomStringUtils +import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils /** * Create a default genesis where there is just one domain with only Alice and Bob in it diff --git a/modules/client/src/test/resources/genesis.json b/modules/client/src/test/resources/genesis.json new file mode 100644 index 000000000..8d756cb88 --- /dev/null +++ b/modules/client/src/test/resources/genesis.json @@ -0,0 +1,83 @@ +{ + "transactions": [ + { + "isi": [ + { + "Register": { + "NewDomain": { + "id": "wonderland", + "logo": null, + "metadata": {} + } + } + }, + { + "Register": { + "NewAccount": { + "id": "alice@wonderland", + "signatories": [ + "ed0120cc25624d62896d3a0bfd8940f928dc2abf27cc57cefeb442aa96d9081aae58a1" + ], + "metadata": {} + } + } + }, + { + "Register": { + "NewAccount": { + "id": "bob@wonderland", + "signatories": [ + "ed01207233bfc89dcbd68c19fde6ce6158225298ec1131b6a130d1aeb454c1ab5183c0" + ], + "metadata": {} + } + } + }, + { + "Register": { + "PermissionTokenDefinition": { + "id": "can_set_key_value_in_user_metadata", + "params": { + "account_id": "Id" + } + } + } + }, + { + "Grant": { + "PermissionToken": { + "definition_id": "can_set_key_value_in_user_metadata", + "params": { + "account_id": { + "AccountId": "alice@wonderland" + } + } + }, + "destination_id": { + "AccountId": "bob@wonderland" + } + } + }, + { + "Register": { + "PermissionTokenDefinition": { + "id": "can_register_domains", + "params": {} + } + } + }, + { + "Grant": { + "PermissionToken": { + "definition_id": "can_register_domains", + "params": {} + }, + "destination_id": { + "AccountId": "bob@wonderland" + } + } + } + ] + } + ] +} \ No newline at end of file diff --git a/modules/test-tools/README.md b/modules/test-tools/README.md index 0e229f3f3..0812e95e0 100644 --- a/modules/test-tools/README.md +++ b/modules/test-tools/README.md @@ -34,6 +34,11 @@ class Test : IrohaTest() { // unique instructions of SomeGenesis and OtherGenesis } + @Test + @WithIroha(source = "src/test/resources/genesis.json") + fun `genesis path test`(): Unit = runBlocking { + } + @Test @WithIroha fun `empty genesis test`(): Unit = runBlocking { diff --git a/modules/test-tools/src/main/kotlin/jp/co/soramitsu/iroha2/testengine/IrohaConfig.kt b/modules/test-tools/src/main/kotlin/jp/co/soramitsu/iroha2/testengine/IrohaConfig.kt index 22238bd44..4fbf5e356 100644 --- a/modules/test-tools/src/main/kotlin/jp/co/soramitsu/iroha2/testengine/IrohaConfig.kt +++ b/modules/test-tools/src/main/kotlin/jp/co/soramitsu/iroha2/testengine/IrohaConfig.kt @@ -24,17 +24,15 @@ import java.util.function.Consumer class IrohaConfig( var networkToJoin: Network = newNetwork(), var logConsumer: Consumer = Slf4jLogConsumer(getLogger(IrohaContainer::class.java)), - var genesis: Genesis = Genesis.getEmpty(), + var genesisPath: String? = null, // first option + var genesis: Genesis? = null, // second option var imageTag: String = IrohaContainer.DEFAULT_IMAGE_TAG, var imageName: String = IrohaContainer.DEFAULT_IMAGE_NAME, var pullPolicy: ImagePullPolicy = PullPolicy.ageBased(Duration.ofMinutes(10)), var alias: String = IrohaContainer.NETWORK_ALIAS + DEFAULT_P2P_PORT, var keyPair: KeyPair = generateKeyPair(), var trustedPeers: List = listOf( - PeerId( - "$alias:$DEFAULT_P2P_PORT", - keyPair.public.toIrohaPublicKey() - ) + PeerId("$alias:$DEFAULT_P2P_PORT", keyPair.public.toIrohaPublicKey()) ), var ports: List = listOf(DEFAULT_P2P_PORT, DEFAULT_API_PORT, DEFAULT_TELEMETRY_PORT), var shouldCloseNetwork: Boolean = true, diff --git a/modules/test-tools/src/main/kotlin/jp/co/soramitsu/iroha2/testengine/IrohaContainer.kt b/modules/test-tools/src/main/kotlin/jp/co/soramitsu/iroha2/testengine/IrohaContainer.kt index 5eb25a4ca..470af66b4 100644 --- a/modules/test-tools/src/main/kotlin/jp/co/soramitsu/iroha2/testengine/IrohaContainer.kt +++ b/modules/test-tools/src/main/kotlin/jp/co/soramitsu/iroha2/testengine/IrohaContainer.kt @@ -12,11 +12,14 @@ import org.testcontainers.containers.wait.strategy.HttpWaitStrategy import org.testcontainers.shaded.com.google.common.io.Resources.getResource import org.testcontainers.utility.DockerImageName import org.testcontainers.utility.MountableFile.forHostPath +import java.io.File import java.io.IOException import java.net.URL +import java.nio.file.Files import java.nio.file.Path import java.time.Duration import java.util.UUID.randomUUID +import kotlin.io.path.Path import kotlin.io.path.absolute /** @@ -68,7 +71,9 @@ open class IrohaContainer : GenericContainer { forHostPath(configDirLocation.value), "/$DEFAULT_CONFIG_DIR" ).also { - config.genesis.writeToFile(genesisFileLocation.value) + config.genesis?.writeToFile(genesisFileLocation.value) + config.genesisPath?.also { path -> Files.copy(Path(path).toAbsolutePath(), genesisFileLocation.value) } + getResource(DEFAULT_CONFIG_FILE_NAME).readBytes().let { content -> configFileLocation.value.toFile().writeBytes(content) } @@ -102,11 +107,11 @@ open class IrohaContainer : GenericContainer { private val telemetryPort: Int private val genesisFileLocation: Lazy = lazy { - kotlin.io.path.Path("${configDirLocation.value}/$DEFAULT_GENESIS_FILE_NAME") + Path("${configDirLocation.value}/$DEFAULT_GENESIS_FILE_NAME") } private val configFileLocation: Lazy = lazy { - kotlin.io.path.Path("${configDirLocation.value}/$DEFAULT_CONFIG_FILE_NAME") + Path("${configDirLocation.value}/$DEFAULT_CONFIG_FILE_NAME") } private val configDirLocation: Lazy = lazy { @@ -116,8 +121,13 @@ open class IrohaContainer : GenericContainer { override fun start() { logger().debug("Starting Iroha container") if (logger().isDebugEnabled) { - val genesisAsJson = config.genesis.asJson() - logger().debug("Serialized genesis block: {}", genesisAsJson) + config.genesis?.asJson()?.also { json -> + logger().debug("Serialized genesis block: {}", json) + } + config.genesisPath?.also { path -> + val content = File(path).readText() + logger().debug("Serialized genesis block: {}", content) + } } super.start() logger().debug("Iroha container started") @@ -127,7 +137,7 @@ open class IrohaContainer : GenericContainer { logger().debug("Stopping Iroha container") super.stop() if (config.shouldCloseNetwork) { - network.close() + network!!.close() } try { configDirLocation.value.toFile().deleteRecursively() diff --git a/modules/test-tools/src/main/kotlin/jp/co/soramitsu/iroha2/testengine/IrohaRunnerExtension.kt b/modules/test-tools/src/main/kotlin/jp/co/soramitsu/iroha2/testengine/IrohaRunnerExtension.kt index 227d7bfb7..827a14b13 100644 --- a/modules/test-tools/src/main/kotlin/jp/co/soramitsu/iroha2/testengine/IrohaRunnerExtension.kt +++ b/modules/test-tools/src/main/kotlin/jp/co/soramitsu/iroha2/testengine/IrohaRunnerExtension.kt @@ -11,7 +11,6 @@ import jp.co.soramitsu.iroha2.generateKeyPair import jp.co.soramitsu.iroha2.generated.datamodel.peer.PeerId import jp.co.soramitsu.iroha2.toIrohaPublicKey import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.runBlocking @@ -136,24 +135,26 @@ class IrohaRunnerExtension : InvocationInterceptor, BeforeEachCallback { ): List = coroutineScope { val keyPairs = mutableListOf() val portsList = mutableListOf>() + repeat(withIroha.amount) { keyPairs.add(generateKeyPair()) portsList.add(findFreePorts(3)) // P2P + API + TELEMETRY } - val peerIds = keyPairs.mapIndexed { i: Int, kp: KeyPair -> val p2pPort = portsList[i][IrohaConfig.P2P_PORT_IDX] kp.toPeerId(IrohaContainer.NETWORK_ALIAS + p2pPort, p2pPort) } - val deferredSet = mutableSetOf>() val containers = Collections.synchronizedList(ArrayList(withIroha.amount)) repeat(withIroha.amount) { n -> - async(Dispatchers.IO) { + async { val p2pPort = portsList[n][IrohaConfig.P2P_PORT_IDX] val container = IrohaContainer { networkToJoin = network - genesis = withIroha.sources.map { it.createInstance() }.toSingle() + when { + withIroha.source.isNotEmpty() -> genesisPath = withIroha.source + else -> genesis = withIroha.sources.map { it.createInstance() }.toSingle() + } alias = IrohaContainer.NETWORK_ALIAS + p2pPort keyPair = keyPairs[n] trustedPeers = peerIds diff --git a/modules/test-tools/src/main/kotlin/jp/co/soramitsu/iroha2/testengine/IrohaTest.kt b/modules/test-tools/src/main/kotlin/jp/co/soramitsu/iroha2/testengine/IrohaTest.kt index 6df7797ae..9ac881d7c 100644 --- a/modules/test-tools/src/main/kotlin/jp/co/soramitsu/iroha2/testengine/IrohaTest.kt +++ b/modules/test-tools/src/main/kotlin/jp/co/soramitsu/iroha2/testengine/IrohaTest.kt @@ -20,7 +20,7 @@ import java.time.Duration @ExtendWith(IrohaRunnerExtension::class) @Timeout(120) abstract class IrohaTest( - val txTimeout: Duration = Duration.ofSeconds(10), + val txTimeout: Duration = Duration.ofSeconds(20), val network: Network = Network.newNetwork(), private val account: AccountId? = null, private val keyPair: KeyPair? = null diff --git a/modules/test-tools/src/main/kotlin/jp/co/soramitsu/iroha2/testengine/WithIroha.kt b/modules/test-tools/src/main/kotlin/jp/co/soramitsu/iroha2/testengine/WithIroha.kt index 77a0e4925..17b042280 100644 --- a/modules/test-tools/src/main/kotlin/jp/co/soramitsu/iroha2/testengine/WithIroha.kt +++ b/modules/test-tools/src/main/kotlin/jp/co/soramitsu/iroha2/testengine/WithIroha.kt @@ -21,6 +21,7 @@ import kotlin.reflect.KClass annotation class WithIroha( val sources: Array> = [EmptyGenesis::class], val configs: Array = [], + val source: String = "", val amount: Int = 1 ) diff --git a/modules/test-tools/src/test/kotlin/jp/co/soramitsu/iroha2/testtools/IrohaContainerTest.kt b/modules/test-tools/src/test/kotlin/jp/co/soramitsu/iroha2/testtools/IrohaContainerTest.kt index 9df3660af..8a58fae9a 100644 --- a/modules/test-tools/src/test/kotlin/jp/co/soramitsu/iroha2/testtools/IrohaContainerTest.kt +++ b/modules/test-tools/src/test/kotlin/jp/co/soramitsu/iroha2/testtools/IrohaContainerTest.kt @@ -1,29 +1,38 @@ package jp.co.soramitsu.iroha2.testtools -import jp.co.soramitsu.iroha2.testengine.IrohaContainer +import jp.co.soramitsu.iroha2.findFreePorts +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout -import kotlin.test.Test -import kotlin.test.assertFalse -import kotlin.test.assertTrue +import kotlin.random.Random +import kotlin.test.assertEquals -@Timeout(30) +@Timeout(60) internal class IrohaContainerTest { @Test - fun `check container can run and stop`() { - val sut = IrohaContainer() - - assertFalse { sut.isCreated } - assertFalse { sut.isRunning } - - sut.start() - - assertTrue { sut.isCreated } - assertTrue { sut.isRunning } - - sut.stop() - - assertFalse { sut.isCreated } - assertFalse { sut.isRunning } + @Disabled + // https://app.zenhub.com/workspaces/iroha-v2-60ddb820813b9100181fc060/issues/gh/hyperledger/iroha-java/338 + fun `findFreePorts returns unique free ports`(): Unit = runBlocking { + val dList = mutableListOf>>() + repeat(10) { + async { + val all = mutableListOf() + repeat(100) { + delay(Random.nextLong(10, 20)) + all.addAll(findFreePorts(3, false)) + } + all + }.let { dList.add(it) } + } + + val all = mutableListOf() + dList.forEach { all.addAll(it.await()) } + + assertEquals(all.size, all.toSet().size) } } From d899a517b4f360bfd4315d27137e326d63ca24a7 Mon Sep 17 00:00:00 2001 From: Timur Guskov Date: Mon, 5 Jun 2023 08:55:29 +0300 Subject: [PATCH 02/22] feature (issue 268): fixed deserializer test Signed-off-by: Timur Guskov --- .../kotlin/jp/co/soramitsu/iroha2/Serde.kt | 154 ++++++++++++++---- .../co/soramitsu/iroha2/DeserializerTest.kt | 9 +- .../jp/co/soramitsu/iroha2/Extensions.kt | 28 ++++ 3 files changed, 152 insertions(+), 39 deletions(-) diff --git a/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt b/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt index 09054b646..a6ba7d52b 100644 --- a/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt +++ b/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt @@ -21,13 +21,20 @@ import jp.co.soramitsu.iroha2.generated.crypto.PublicKey import jp.co.soramitsu.iroha2.generated.datamodel.IdBox import jp.co.soramitsu.iroha2.generated.datamodel.IdentifiableBox import jp.co.soramitsu.iroha2.generated.datamodel.NumericValue +import jp.co.soramitsu.iroha2.generated.datamodel.RegistrableBox import jp.co.soramitsu.iroha2.generated.datamodel.Value +import jp.co.soramitsu.iroha2.generated.datamodel.ValueKind import jp.co.soramitsu.iroha2.generated.datamodel.account.AccountId +import jp.co.soramitsu.iroha2.generated.datamodel.account.NewAccount +import jp.co.soramitsu.iroha2.generated.datamodel.asset.Asset import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetDefinitionId import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetId import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetValueType import jp.co.soramitsu.iroha2.generated.datamodel.asset.Mintable +import jp.co.soramitsu.iroha2.generated.datamodel.asset.NewAssetDefinition import jp.co.soramitsu.iroha2.generated.datamodel.domain.DomainId +import jp.co.soramitsu.iroha2.generated.datamodel.domain.NewDomain +import jp.co.soramitsu.iroha2.generated.datamodel.events.EventsFilterBox import jp.co.soramitsu.iroha2.generated.datamodel.expression.EvaluatesTo import jp.co.soramitsu.iroha2.generated.datamodel.expression.Expression import jp.co.soramitsu.iroha2.generated.datamodel.isi.BurnBox @@ -38,8 +45,13 @@ import jp.co.soramitsu.iroha2.generated.datamodel.isi.RegisterBox import jp.co.soramitsu.iroha2.generated.datamodel.isi.SetKeyValueBox import jp.co.soramitsu.iroha2.generated.datamodel.metadata.Metadata import jp.co.soramitsu.iroha2.generated.datamodel.name.Name +import jp.co.soramitsu.iroha2.generated.datamodel.peer.Peer +import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.Definition import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.TokenId +import jp.co.soramitsu.iroha2.generated.datamodel.permission.validator.Validator +import jp.co.soramitsu.iroha2.generated.datamodel.role.NewRole import jp.co.soramitsu.iroha2.generated.datamodel.role.RoleId +import jp.co.soramitsu.iroha2.generated.datamodel.trigger.Trigger import jp.co.soramitsu.iroha2.generated.datamodel.trigger.TriggerId import java.io.ByteArrayOutputStream import kotlin.reflect.full.createInstance @@ -55,9 +67,9 @@ val JSON_SERDE by lazy { // deserializers module.addDeserializer(Instruction::class.java, InstructionDeserializer) - module.addDeserializer(Expression::class.java, ExpressionDeserializer) + module.addDeserializer(GrantBox::class.java, GrantBoxDeserializer) module.addDeserializer(Value::class.java, ValueDeserializer) - module.addDeserializer(IdentifiableBox::class.java, IdentifiableBoxDeserializer) + module.addDeserializer(ValueKind::class.java, ValueKindDeserializer) module.addDeserializer(PublicKey::class.java, PublicKeyDeserializer) module.addDeserializer(IdBox::class.java, IdBoxDeserializer) module.addDeserializer(AssetValueType::class.java, AssetValueTypeDeserializer) @@ -67,6 +79,8 @@ val JSON_SERDE by lazy { module.addDeserializer(AccountId::class.java, AccountIdDeserializer) module.addDeserializer(AssetDefinitionId::class.java, AssetDefinitionIdDeserializer) module.addDeserializer(AssetId::class.java, AssetIdDeserializer) + module.addDeserializer(RegisterBox::class.java, RegisterBoxDeserializer) + module.addDeserializer(TokenId::class.java, TokenIdDeserializer) module.addKeyDeserializer(AssetDefinitionId::class.java, AssetDefinitionIdKeyDeserializer) module.addKeyDeserializer(AccountId::class.java, AccountIdKeyDeserializer) module.addKeyDeserializer(AssetId::class.java, AssetIdKeyDeserializer) @@ -103,11 +117,11 @@ val JSON_SERDE by lazy { } } -private inline fun sealedDeserialize(p: JsonParser, mapper: ObjectMapper): T { +private fun sealedDeserializeInstruction(p: JsonParser, mapper: ObjectMapper): Instruction { val node = p.readValueAsTree().fields().next() val param = node.key - val subtype = T::class.nestedClasses.find { clazz -> + val subtype = Instruction::class.nestedClasses.find { clazz -> !clazz.isCompanion && clazz.simpleName == param } ?: throw DeserializationException("Class with constructor($param) not found") @@ -115,26 +129,88 @@ private inline fun sealedDeserialize(p: JsonParser, mapp ?.firstOrNull()?.type?.toString() ?: throw DeserializationException("Subtype parameter not found by $param") - val toConvert: JsonNode - if (T::class.java.isAssignableFrom(Instruction::class.java)) { - var jsonNode: ObjectNode = node.value.deepCopy() - jsonNode.fields().forEach { field -> - val key = field.key - val child = mapper.createObjectNode() - child.set("expression", node.value.get(key)) - val value: ObjectNode = jsonNode.deepCopy() - value.set(key, child) - - jsonNode = value - } + val toConvert: JsonNode = node.value + + val arg = mapper.convertValue(toConvert, argTypeName.asClass()) + return subtype.primaryConstructor?.call(arg) as Instruction +} - toConvert = jsonNode - } else { - toConvert = node.value as JsonNode +private fun sealedDeserializeGrantBox(p: JsonParser, mapper: ObjectMapper): GrantBox { + val jsonNode = p.readValueAsTree() + + val iter = jsonNode.iterator() + val nodes = mutableListOf() + + while (iter.hasNext()) { + val node = iter.next() + nodes.add(node) } + val node = jsonNode.fields().next() + val destination = nodes[1] - val arg = mapper.convertValue(toConvert, argTypeName.asClass()) - return subtype.primaryConstructor?.call(arg) as T + val param = node.key + + val subtype = Value::class.nestedClasses.find { clazz -> + !clazz.isCompanion && clazz.simpleName?.contains(param) ?: false + } ?: throw DeserializationException("Class with constructor($param) not found") + + val argTypeName = subtype.primaryConstructor?.parameters + ?.firstOrNull()?.type?.toString() + ?: throw DeserializationException("Subtype parameter not found by $param") + + val grantObject = mapper.convertValue(node.value, argTypeName.asClass()) + val destinationId = mapper.convertValue(destination, "jp.co.soramitsu.iroha2.generated.datamodel.IdBox".asClass()) + return GrantBox( + `object` = grantObject.evaluatesTo().cast(), + destinationId = destinationId.evaluatesTo().cast() + ) +} + +private fun sealedDeserializeValue(p: JsonParser): Value { + val node = p.readValueAsTree().fields().next() + return node.value.asText().asValue() +} + +private fun sealedDeserializeIdBox(p: JsonParser, mapper: ObjectMapper): IdBox { + val node = p.readValueAsTree().fields().next() + val param = node.key + + val subtype = IdBox::class.nestedClasses.find { clazz -> + !clazz.isCompanion && clazz.simpleName == param + } ?: throw DeserializationException("Class with constructor($param) not found") + + val argTypeName = subtype.primaryConstructor?.parameters + ?.firstOrNull()?.type?.toString() + ?: throw DeserializationException("Subtype parameter not found by $param") + + val arg = mapper.convertValue(node.value, argTypeName.asClass()) + return subtype.primaryConstructor?.call(arg) as IdBox +} + +private fun sealedDeserializeRegisterBox(p: JsonParser, mapper: ObjectMapper): RegisterBox { + val node = p.readValueAsTree().fields().next() + + val param = node.key.removePrefix("New") + val subtype = RegistrableBox::class.nestedClasses.find { clazz -> + !clazz.isCompanion && clazz.simpleName == param + } + val argTypeName = subtype?.primaryConstructor?.parameters + ?.firstOrNull()?.type?.toString() + ?: throw DeserializationException("Subtype parameter not found by $param") + + val arg = mapper.convertValue(node.value, argTypeName.asClass()) + return when (arg) { + is NewDomain -> RegisterBox(RegistrableBox.Domain(arg).evaluatesTo()) + is NewAccount -> RegisterBox(RegistrableBox.Account(arg).evaluatesTo()) + is Definition -> RegisterBox(RegistrableBox.PermissionTokenDefinition(arg).evaluatesTo()) + is Peer -> RegisterBox(RegistrableBox.Peer(arg).evaluatesTo()) + is NewAssetDefinition -> RegisterBox(RegistrableBox.AssetDefinition(arg).evaluatesTo()) + is Asset -> RegisterBox(RegistrableBox.Asset(arg).evaluatesTo()) + is Trigger<*> -> RegisterBox(RegistrableBox.Trigger(arg as Trigger).evaluatesTo()) + is NewRole -> RegisterBox(RegistrableBox.Role(arg).evaluatesTo()) + is Validator -> RegisterBox(RegistrableBox.Validator(arg).evaluatesTo()) + else -> throw DeserializationException("Register box `$arg` not found") + } } /** @@ -142,16 +218,13 @@ private inline fun sealedDeserialize(p: JsonParser, mapp */ object InstructionDeserializer : JsonDeserializer() { override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Instruction { - return sealedDeserialize(p, JSON_SERDE) + return sealedDeserializeInstruction(p, JSON_SERDE) } } -/** - * Deserializer for [Expression] - */ -object ExpressionDeserializer : JsonDeserializer() { - override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Expression { - return sealedDeserialize(p, JSON_SERDE) +object GrantBoxDeserializer : JsonDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): GrantBox { + return sealedDeserializeGrantBox(p, JSON_SERDE) } } @@ -160,16 +233,13 @@ object ExpressionDeserializer : JsonDeserializer() { */ object ValueDeserializer : JsonDeserializer() { override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Value { - return sealedDeserialize(p, JSON_SERDE) + return sealedDeserializeValue(p) } } -/** - * Deserializer for [IdentifiableBox] - */ -object IdentifiableBoxDeserializer : JsonDeserializer() { - override fun deserialize(p: JsonParser, ctxt: DeserializationContext): IdentifiableBox { - return sealedDeserialize(p, JSON_SERDE) +object ValueKindDeserializer : JsonDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): ValueKind { + return p.readValueAs(String::class.java).asValueKind() } } @@ -178,7 +248,19 @@ object IdentifiableBoxDeserializer : JsonDeserializer() { */ object IdBoxDeserializer : JsonDeserializer() { override fun deserialize(p: JsonParser, ctxt: DeserializationContext): IdBox { - return sealedDeserialize(p, JSON_SERDE) + return sealedDeserializeIdBox(p, JSON_SERDE) + } +} + +object RegisterBoxDeserializer : JsonDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): RegisterBox { + return sealedDeserializeRegisterBox(p, JSON_SERDE) + } +} + +object TokenIdDeserializer : JsonDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): TokenId { + return p.readValueAs(String::class.java).asTokenId() } } diff --git a/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt b/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt index 81a84b86e..215685b46 100644 --- a/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt +++ b/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt @@ -1,11 +1,10 @@ package jp.co.soramitsu.iroha2 import jp.co.soramitsu.iroha2.generated.core.genesis.RawGenesisBlock -import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import java.io.File +import kotlin.test.assertNotNull -@Disabled // https://app.zenhub.com/workspaces/iroha-v2-60ddb820813b9100181fc060/issues/hyperledger/iroha-java/268 class DeserializerTest { @Test fun `should deserialize genesis block`() { @@ -14,6 +13,10 @@ class DeserializerTest { val block = JSON_SERDE.convertValue(node, RawGenesisBlock::class.java) assert(block.transactions.isNotEmpty()) - assert(block.transactions.first().isi.size == 4) + assert(block.transactions.first().isi.size == 7) + + val genesis = Genesis(block) + val newJson = genesis.asJson() + assertNotNull(newJson) } } diff --git a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Extensions.kt b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Extensions.kt index bb2fc852c..c27ab3a37 100644 --- a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Extensions.kt +++ b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Extensions.kt @@ -9,6 +9,7 @@ import jp.co.soramitsu.iroha2.generated.datamodel.IdentifiableBox import jp.co.soramitsu.iroha2.generated.datamodel.NumericValue import jp.co.soramitsu.iroha2.generated.datamodel.RegistrableBox import jp.co.soramitsu.iroha2.generated.datamodel.Value +import jp.co.soramitsu.iroha2.generated.datamodel.ValueKind import jp.co.soramitsu.iroha2.generated.datamodel.account.AccountId import jp.co.soramitsu.iroha2.generated.datamodel.asset.Asset import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetDefinitionId @@ -73,12 +74,39 @@ fun String.asAssetId() = this.split(ASSET_ID_DELIMITER).takeIf { } } ?: throw IllegalArgumentException("Incorrect asset ID: $this") +fun String.asTokenId() = TokenId(Name(this)) + fun String.asDomainId() = DomainId(Name(this)) fun String.asName() = Name(this) fun String.asValue() = Value.String(this) +fun String.asValueKind() = when (this) { + "Id" -> ValueKind.Id() + "Bool" -> ValueKind.Bool() + "String" -> ValueKind.String() + "Name" -> ValueKind.Name() + "Vec" -> ValueKind.Vec() + "LimitedMetadata" -> ValueKind.LimitedMetadata() + "MetadataLimits" -> ValueKind.MetadataLimits() + "TransactionLimits" -> ValueKind.TransactionLimits() + "LengthLimits" -> ValueKind.LengthLimits() + "Identifiable" -> ValueKind.Identifiable() + "PublicKey" -> ValueKind.PublicKey() + "SignatureCheckCondition" -> ValueKind.SignatureCheckCondition() + "TransactionValue" -> ValueKind.TransactionValue() + "TransactionQueryResult" -> ValueKind.TransactionQueryResult() + "PermissionToken" -> ValueKind.PermissionToken() + "Hash" -> ValueKind.Hash() + "Block" -> ValueKind.Block() + "BlockHeader" -> ValueKind.BlockHeader() + "Ipv4Addr" -> ValueKind.Ipv4Addr() + "Ipv6Addr" -> ValueKind.Ipv6Addr() + "Numeric" -> ValueKind.Numeric() + else -> throw IllegalArgumentException("Unsupported value kind type: $this") +} + fun Int.asValue() = Value.Numeric(NumericValue.U32(this.toLong())) fun Long.asValue() = Value.Numeric(NumericValue.U128(BigInteger.valueOf(this))) From 7968d78c5873bc0b7cdf41a0a24d849cecc9fa8c Mon Sep 17 00:00:00 2001 From: Timur Guskov Date: Mon, 5 Jun 2023 11:26:10 +0300 Subject: [PATCH 03/22] feature (issue 268): fix format Signed-off-by: Timur Guskov --- modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt b/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt index a6ba7d52b..843ccfef1 100644 --- a/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt +++ b/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt @@ -12,7 +12,6 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.databind.SerializerProvider import com.fasterxml.jackson.databind.module.SimpleModule -import com.fasterxml.jackson.databind.node.ObjectNode import com.fasterxml.jackson.module.kotlin.KotlinFeature import com.fasterxml.jackson.module.kotlin.KotlinModule import io.ipfs.multihash.Multihash From 17af280a6b23cadde1c04ae503035d6dd8432f98 Mon Sep 17 00:00:00 2001 From: Timur Guskov Date: Thu, 8 Jun 2023 11:19:49 +0300 Subject: [PATCH 04/22] feature (issue 268): fix deserializers Signed-off-by: Timur Guskov --- .../kotlin/jp/co/soramitsu/iroha2/Serde.kt | 193 +++++++++- .../co/soramitsu/iroha2/DeserializerTest.kt | 34 ++ .../src/main/resources/genesis.json | 86 +++-- .../src/main/resources/genesis2.json | 330 ++++++++++++++++++ 4 files changed, 603 insertions(+), 40 deletions(-) create mode 100644 modules/test-tools/src/main/resources/genesis2.json diff --git a/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt b/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt index 843ccfef1..a7d541c4e 100644 --- a/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt +++ b/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt @@ -17,20 +17,26 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule import io.ipfs.multihash.Multihash import jp.co.soramitsu.iroha2.DigestFunction.Ed25519 import jp.co.soramitsu.iroha2.generated.crypto.PublicKey +import jp.co.soramitsu.iroha2.generated.crypto.hash.Hash import jp.co.soramitsu.iroha2.generated.datamodel.IdBox import jp.co.soramitsu.iroha2.generated.datamodel.IdentifiableBox +import jp.co.soramitsu.iroha2.generated.datamodel.LengthLimits import jp.co.soramitsu.iroha2.generated.datamodel.NumericValue import jp.co.soramitsu.iroha2.generated.datamodel.RegistrableBox import jp.co.soramitsu.iroha2.generated.datamodel.Value import jp.co.soramitsu.iroha2.generated.datamodel.ValueKind import jp.co.soramitsu.iroha2.generated.datamodel.account.AccountId import jp.co.soramitsu.iroha2.generated.datamodel.account.NewAccount +import jp.co.soramitsu.iroha2.generated.datamodel.account.SignatureCheckCondition import jp.co.soramitsu.iroha2.generated.datamodel.asset.Asset import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetDefinitionId import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetId +import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetValue import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetValueType import jp.co.soramitsu.iroha2.generated.datamodel.asset.Mintable import jp.co.soramitsu.iroha2.generated.datamodel.asset.NewAssetDefinition +import jp.co.soramitsu.iroha2.generated.datamodel.blockvalue.BlockHeaderValue +import jp.co.soramitsu.iroha2.generated.datamodel.blockvalue.BlockValue import jp.co.soramitsu.iroha2.generated.datamodel.domain.DomainId import jp.co.soramitsu.iroha2.generated.datamodel.domain.NewDomain import jp.co.soramitsu.iroha2.generated.datamodel.events.EventsFilterBox @@ -42,16 +48,23 @@ import jp.co.soramitsu.iroha2.generated.datamodel.isi.Instruction import jp.co.soramitsu.iroha2.generated.datamodel.isi.MintBox import jp.co.soramitsu.iroha2.generated.datamodel.isi.RegisterBox import jp.co.soramitsu.iroha2.generated.datamodel.isi.SetKeyValueBox +import jp.co.soramitsu.iroha2.generated.datamodel.metadata.Limits import jp.co.soramitsu.iroha2.generated.datamodel.metadata.Metadata import jp.co.soramitsu.iroha2.generated.datamodel.name.Name import jp.co.soramitsu.iroha2.generated.datamodel.peer.Peer import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.Definition +import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.Token import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.TokenId import jp.co.soramitsu.iroha2.generated.datamodel.permission.validator.Validator import jp.co.soramitsu.iroha2.generated.datamodel.role.NewRole import jp.co.soramitsu.iroha2.generated.datamodel.role.RoleId +import jp.co.soramitsu.iroha2.generated.datamodel.transaction.TransactionLimits +import jp.co.soramitsu.iroha2.generated.datamodel.transaction.TransactionQueryResult +import jp.co.soramitsu.iroha2.generated.datamodel.transaction.TransactionValue import jp.co.soramitsu.iroha2.generated.datamodel.trigger.Trigger import jp.co.soramitsu.iroha2.generated.datamodel.trigger.TriggerId +import jp.co.soramitsu.iroha2.generated.primitives.addr.Ipv4Addr +import jp.co.soramitsu.iroha2.generated.primitives.addr.Ipv6Addr import java.io.ByteArrayOutputStream import kotlin.reflect.full.createInstance import kotlin.reflect.full.memberProperties @@ -69,6 +82,7 @@ val JSON_SERDE by lazy { module.addDeserializer(GrantBox::class.java, GrantBoxDeserializer) module.addDeserializer(Value::class.java, ValueDeserializer) module.addDeserializer(ValueKind::class.java, ValueKindDeserializer) + module.addDeserializer(AssetValue::class.java, AssetValueDeserializer) module.addDeserializer(PublicKey::class.java, PublicKeyDeserializer) module.addDeserializer(IdBox::class.java, IdBoxDeserializer) module.addDeserializer(AssetValueType::class.java, AssetValueTypeDeserializer) @@ -79,6 +93,8 @@ val JSON_SERDE by lazy { module.addDeserializer(AssetDefinitionId::class.java, AssetDefinitionIdDeserializer) module.addDeserializer(AssetId::class.java, AssetIdDeserializer) module.addDeserializer(RegisterBox::class.java, RegisterBoxDeserializer) + module.addDeserializer(SetKeyValueBox::class.java, SetKeyValueBoxDeserializer) + module.addDeserializer(Metadata::class.java, MetadataDeserializer) module.addDeserializer(TokenId::class.java, TokenIdDeserializer) module.addKeyDeserializer(AssetDefinitionId::class.java, AssetDefinitionIdKeyDeserializer) module.addKeyDeserializer(AccountId::class.java, AccountIdKeyDeserializer) @@ -139,12 +155,12 @@ private fun sealedDeserializeGrantBox(p: JsonParser, mapper: ObjectMapper): Gran val iter = jsonNode.iterator() val nodes = mutableListOf() - while (iter.hasNext()) { val node = iter.next() nodes.add(node) } - val node = jsonNode.fields().next() + + val node = nodes[0].fields().next() val destination = nodes[1] val param = node.key @@ -165,13 +181,81 @@ private fun sealedDeserializeGrantBox(p: JsonParser, mapper: ObjectMapper): Gran ) } -private fun sealedDeserializeValue(p: JsonParser): Value { +private fun sealedDeserializeValue(p: JsonParser, mapper: ObjectMapper): Value { val node = p.readValueAsTree().fields().next() - return node.value.asText().asValue() + val param = if (node.key.contains("Id")) "Id" else node.key + + val clazz = when (param) { + "Bool" -> Boolean::class + "String" -> String::class + "Name" -> Name::class + "Vec" -> Value::class + "LimitedMetadata" -> Metadata::class + "MetadataLimits" -> Limits::class + "TransactionLimits" -> TransactionLimits::class + "LengthLimits" -> LengthLimits::class + "Id" -> IdBox::class + "Identifiable" -> IdentifiableBox::class + "PublicKey" -> PublicKey::class + "SignatureCheckCondition" -> SignatureCheckCondition::class + "TransactionValue" -> TransactionValue::class + "TransactionQueryResult" -> TransactionQueryResult::class + "PermissionToken" -> Token::class + "Hash" -> Hash::class + "Block" -> BlockValue::class + "BlockHeader" -> BlockHeaderValue::class + "Ipv4Addr" -> Ipv4Addr::class + "Ipv6Addr" -> Ipv6Addr::class + "U32" -> NumericValue::class + "U64" -> NumericValue::class + "U128" -> NumericValue::class + "Fixed" -> NumericValue::class + else -> throw DeserializationException("Value key $param not found") + } + + if (param == "Bool") { + return Value.Bool(node.value.booleanValue()) + } else if (param == "String") { + return Value.String(node.value.asText()) + } else { + val name = if (node.key == "Id") node.value.fields().next().key else node.key + val value = if (node.key == "Id") node.value.fields().next().value else node.value + val subtype = clazz.nestedClasses.find { clazz -> + !clazz.isCompanion && clazz.simpleName == name + } ?: throw DeserializationException("Class with constructor($param) not found") + + val argTypeName = subtype.primaryConstructor?.parameters + ?.firstOrNull()?.type?.toString() + ?: throw DeserializationException("Subtype parameter not found by $param") + + val arg = mapper.convertValue(value, argTypeName.asClass()) + return when (clazz) { + Name::class -> Value.Name(subtype.primaryConstructor?.call(arg) as Name) + Value::class -> throw DeserializationException("Value type $clazz not supported") + Metadata::class -> Value.LimitedMetadata(subtype.primaryConstructor?.call(arg) as Metadata) + Limits::class -> Value.MetadataLimits(subtype.primaryConstructor?.call(arg) as Limits) + TransactionLimits::class -> Value.TransactionLimits(subtype.primaryConstructor?.call(arg) as TransactionLimits) + LengthLimits::class -> Value.LengthLimits(subtype.primaryConstructor?.call(arg) as LengthLimits) + IdBox::class -> Value.Id(subtype.primaryConstructor?.call(arg) as IdBox) + IdentifiableBox::class -> Value.Identifiable(subtype.primaryConstructor?.call(arg) as IdentifiableBox) + PublicKey::class -> Value.PublicKey(subtype.primaryConstructor?.call(arg) as PublicKey) + SignatureCheckCondition::class -> Value.SignatureCheckCondition(subtype.primaryConstructor?.call(arg) as SignatureCheckCondition) + TransactionValue::class -> Value.TransactionValue(subtype.primaryConstructor?.call(arg) as TransactionValue) + TransactionQueryResult::class -> Value.TransactionQueryResult(subtype.primaryConstructor?.call(arg) as TransactionQueryResult) + Token::class -> Value.PermissionToken(subtype.primaryConstructor?.call(arg) as Token) + Hash::class -> Value.Hash(subtype.primaryConstructor?.call(arg) as Hash) + BlockValue::class -> Value.Block(subtype.primaryConstructor?.call(arg) as BlockValue) + BlockHeaderValue::class -> Value.BlockHeader(subtype.primaryConstructor?.call(arg) as BlockHeaderValue) + Ipv4Addr::class -> Value.Ipv4Addr(subtype.primaryConstructor?.call(arg) as Ipv4Addr) + Ipv6Addr::class -> Value.Ipv6Addr(subtype.primaryConstructor?.call(arg) as Ipv6Addr) + NumericValue::class -> Value.Numeric(subtype.primaryConstructor?.call(arg) as NumericValue) + else -> throw DeserializationException("Value type $clazz not found") + } + } } private fun sealedDeserializeIdBox(p: JsonParser, mapper: ObjectMapper): IdBox { - val node = p.readValueAsTree().fields().next() + val node = p.readValueAsTree().fields().next().value.fields().next() val param = node.key val subtype = IdBox::class.nestedClasses.find { clazz -> @@ -187,7 +271,7 @@ private fun sealedDeserializeIdBox(p: JsonParser, mapper: ObjectMapper): IdBox { } private fun sealedDeserializeRegisterBox(p: JsonParser, mapper: ObjectMapper): RegisterBox { - val node = p.readValueAsTree().fields().next() + val node = p.readValueAsTree().fields().next().value.fields().next() val param = node.key.removePrefix("New") val subtype = RegistrableBox::class.nestedClasses.find { clazz -> @@ -212,6 +296,73 @@ private fun sealedDeserializeRegisterBox(p: JsonParser, mapper: ObjectMapper): R } } +private fun sealedDeserializeSetKeyValueBox(p: JsonParser, mapper: ObjectMapper): SetKeyValueBox { + val iter = p.readValueAsTree().iterator() + val nodes = mutableListOf() + while (iter.hasNext()) { + val node = iter.next() + nodes.add(node) + } + + val objectId = nodes[0].fields().next().value.fields().next() + val key = nodes[1].fields().next() + val subtype = IdBox::class.nestedClasses.find { clazz -> + !clazz.isCompanion && clazz.simpleName == objectId.key + } + val argTypeName = subtype?.primaryConstructor?.parameters + ?.firstOrNull()?.type?.toString() + ?: throw DeserializationException("Subtype parameter not found by $objectId") + + val objectIdArg = mapper.convertValue(objectId.value, argTypeName.asClass()) + val keyArg = mapper.convertValue(key.value, "jp.co.soramitsu.iroha2.generated.datamodel.name.Name".asClass()) + val valueArg = mapper.convertValue(nodes[2], "jp.co.soramitsu.iroha2.generated.datamodel.Value".asClass()) + return SetKeyValueBox( + objectId = objectIdArg.evaluatesTo().cast(), + key = keyArg.evaluatesTo().cast(), + value = valueArg.evaluatesTo().cast() + ) +} + +private fun sealedDeserializeMetadata(p: JsonParser, mapper: ObjectMapper): Metadata { + val nodeMetadata = p.readValueAsTree().fields() + if (!nodeMetadata.hasNext()) { + return Metadata(mapOf()) + } + val node = nodeMetadata.next() + val key = node.key.asName() + val valueNode = node.value.fields().next() + val value = valueNode.value.asText().asValue() + return Metadata(mapOf(Pair(key, value))) +} + +private fun sealedDeserializeToken(p: JsonParser, mapper: ObjectMapper): Token { + val jsonNode = p.readValueAsTree() + + val iter = jsonNode.iterator() + val nodes = mutableListOf() + + while (iter.hasNext()) { + val node = iter.next() + nodes.add(node) + } + val definitionId = jsonNode.fields().next() + val params = nodes[1].fields() + val tokenMap = mutableMapOf() + + val arg = mapper.convertValue(definitionId.value, "jp.co.soramitsu.iroha2.generated.datamodel.permission.token.TokenId".asClass()) as TokenId + if (params.hasNext()) { + val params = params.next().value.fields().next() + val arg2 = mapper.convertValue(params.key, "jp.co.soramitsu.iroha2.generated.datamodel.name.Name".asClass()) as Name + val arg3 = mapper.convertValue(params.value, "jp.co.soramitsu.iroha2.generated.datamodel.Value".asClass()) as Value + tokenMap[arg2] = arg3 + } + + return Token( + arg, + tokenMap + ) +} + /** * Deserializer for [Iroha Special Instructions][Instruction] */ @@ -232,7 +383,7 @@ object GrantBoxDeserializer : JsonDeserializer() { */ object ValueDeserializer : JsonDeserializer() { override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Value { - return sealedDeserializeValue(p) + return sealedDeserializeValue(p, JSON_SERDE) } } @@ -242,6 +393,16 @@ object ValueKindDeserializer : JsonDeserializer() { } } +object AssetValueDeserializer : JsonDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): AssetValue { + val node = p.readValueAsTree().fields().next() + return when (node.key) { + "Store" -> AssetValue.Store(Metadata(mapOf())) + else -> throw DeserializationException("AssetValue ${node.key} not found") + } + } +} + /** * Deserializer for [IdBox] */ @@ -257,12 +418,30 @@ object RegisterBoxDeserializer : JsonDeserializer() { } } +object SetKeyValueBoxDeserializer : JsonDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): SetKeyValueBox { + return sealedDeserializeSetKeyValueBox(p, JSON_SERDE) + } +} + +object MetadataDeserializer : JsonDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Metadata { + return sealedDeserializeMetadata(p, JSON_SERDE) + } +} + object TokenIdDeserializer : JsonDeserializer() { override fun deserialize(p: JsonParser, ctxt: DeserializationContext): TokenId { return p.readValueAs(String::class.java).asTokenId() } } +object TokenDeserializer : JsonDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Token { + return sealedDeserializeToken(p, JSON_SERDE) + } +} + /** * Deserializer for [AssetValueType] */ diff --git a/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt b/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt index 215685b46..3693d8eca 100644 --- a/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt +++ b/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt @@ -8,6 +8,7 @@ import kotlin.test.assertNotNull class DeserializerTest { @Test fun `should deserialize genesis block`() { + val regex = "(\"ed01)+\\w+\"".toRegex() val json = File("../test-tools/src/main/resources/genesis.json") val node = JSON_SERDE.readTree(json) val block = JSON_SERDE.convertValue(node, RawGenesisBlock::class.java) @@ -17,6 +18,39 @@ class DeserializerTest { val genesis = Genesis(block) val newJson = genesis.asJson() + .replace("\r\n", "") + .replace(" ", "") + .replace(regex, "publicKey") + val initialJson = json.readText() + .replace("\r\n", "") + .replace(" ", "") + .replace(regex, "publicKey") +// assertEquals(initialJson, newJson) assertNotNull(newJson) + assertNotNull(initialJson) + } + + @Test + fun `should deserialize genesis block2`() { + val regex = "(\"ed01)+\\w+\"".toRegex() + val json = File("../test-tools/src/main/resources/genesis2.json") + val node = JSON_SERDE.readTree(json) + val block = JSON_SERDE.convertValue(node, RawGenesisBlock::class.java) + + assert(block.transactions.isNotEmpty()) + assert(block.transactions.first().isi.size == 27) + + val genesis = Genesis(block) + val newJson = genesis.asJson() + .replace("\r\n", "") + .replace(" ", "") + .replace(regex, "publicKey") + val initialJson = json.readText() + .replace("\r\n", "") + .replace(" ", "") + .replace(regex, "publicKey") +// assertEquals(initialJson, newJson) + assertNotNull(newJson) + assertNotNull(initialJson) } } diff --git a/modules/test-tools/src/main/resources/genesis.json b/modules/test-tools/src/main/resources/genesis.json index 8d756cb88..37a53a11a 100644 --- a/modules/test-tools/src/main/resources/genesis.json +++ b/modules/test-tools/src/main/resources/genesis.json @@ -4,76 +4,96 @@ "isi": [ { "Register": { - "NewDomain": { - "id": "wonderland", - "logo": null, - "metadata": {} + "Identifiable" : { + "NewDomain": { + "id": "wonderland", + "logo": null, + "metadata": {} + } } } }, { "Register": { - "NewAccount": { - "id": "alice@wonderland", - "signatories": [ - "ed0120cc25624d62896d3a0bfd8940f928dc2abf27cc57cefeb442aa96d9081aae58a1" - ], - "metadata": {} + "Identifiable" : { + "NewAccount": { + "id": "alice@wonderland", + "signatories": [ + "ed0120cc25624d62896d3a0bfd8940f928dc2abf27cc57cefeb442aa96d9081aae58a1" + ], + "metadata": {} + } } } }, { "Register": { - "NewAccount": { - "id": "bob@wonderland", - "signatories": [ - "ed01207233bfc89dcbd68c19fde6ce6158225298ec1131b6a130d1aeb454c1ab5183c0" - ], - "metadata": {} + "Identifiable" : { + "NewAccount": { + "id": "bob@wonderland", + "signatories": [ + "ed01207233bfc89dcbd68c19fde6ce6158225298ec1131b6a130d1aeb454c1ab5183c0" + ], + "metadata": {} + } } } }, { "Register": { - "PermissionTokenDefinition": { - "id": "can_set_key_value_in_user_metadata", - "params": { - "account_id": "Id" + "Identifiable" : { + "PermissionTokenDefinition": { + "id": "can_set_key_value_in_user_metadata", + "params": { + "account_id": "Id" + } } } } }, { "Grant": { - "PermissionToken": { - "definition_id": "can_set_key_value_in_user_metadata", - "params": { - "account_id": { - "AccountId": "alice@wonderland" + "object" : { + "PermissionToken": { + "definition_id": "can_set_key_value_in_user_metadata", + "params": { + "account_id": { + "Id" : { + "AccountId": "alice@wonderland" + } + } } } }, "destination_id": { - "AccountId": "bob@wonderland" + "Id" : { + "AccountId": "bob@wonderland" + } } } }, { "Register": { - "PermissionTokenDefinition": { - "id": "can_register_domains", - "params": {} + "Identifiable" : { + "PermissionTokenDefinition": { + "id": "can_register_domains", + "params": {} + } } } }, { "Grant": { - "PermissionToken": { - "definition_id": "can_register_domains", - "params": {} + "object" : { + "PermissionToken": { + "definition_id": "can_register_domains", + "params": {} + } }, "destination_id": { - "AccountId": "bob@wonderland" + "Id" : { + "AccountId": "bob@wonderland" + } } } } diff --git a/modules/test-tools/src/main/resources/genesis2.json b/modules/test-tools/src/main/resources/genesis2.json new file mode 100644 index 000000000..f55fb5535 --- /dev/null +++ b/modules/test-tools/src/main/resources/genesis2.json @@ -0,0 +1,330 @@ +{ + "transactions" : [ { + "isi" : [ { + "Register" : { + "Identifiable" : { + "NewDomain" : { + "id" : "wonderland", + "logo" : null, + "metadata" : { } + } + } + } + }, { + "Register" : { + "Identifiable" : { + "NewDomain" : { + "id" : "dnalrednow", + "logo" : null, + "metadata" : { } + } + } + } + }, { + "Register" : { + "Identifiable" : { + "NewDomain" : { + "id" : "test", + "logo" : null, + "metadata" : { } + } + } + } + }, { + "Register" : { + "Identifiable" : { + "NewDomain" : { + "id" : "admin", + "logo" : null, + "metadata" : { } + } + } + } + }, { + "Register" : { + "Identifiable" : { + "NewAccount" : { + "id" : "alice@wonderland", + "signatories" : [ "ed01200851a1fa7e3f04a657299263e119975be4ab0d33631ec6ad4bd5b5e77594310e" ], + "metadata" : { } + } + } + } + }, { + "Register" : { + "Identifiable" : { + "NewAccount" : { + "id" : "admin@wonderland", + "signatories" : [ "ed01203161f2c70db3252505b90c0cce012c4cf4cb098da2a3b1475319ca5b3a1263f5" ], + "metadata" : { } + } + } + } + }, { + "Register" : { + "Identifiable" : { + "NewAccount" : { + "id" : "admin@dnalrednow", + "signatories" : [ "ed0120d24280aadc7167e002b263620fe936e4103e13d59a02286190a6af6a45c7c2f7" ], + "metadata" : { } + } + } + } + }, { + "Register" : { + "Identifiable" : { + "NewAccount" : { + "id" : "admin2@wonderland", + "signatories" : [ "ed01202611be069b48f6313d86a4e5c99deedaccc3046b4cb294849e11dadb27a701ca" ], + "metadata" : { } + } + } + } + }, { + "Register" : { + "Identifiable" : { + "NewAccount" : { + "id" : "bob@admin", + "signatories" : [ "ed01207f7137560b2c1fefa36834f609953f8ad78903a7f21d3c84a69949991885ba1c" ], + "metadata" : { + "authentication" : { + "String" : "9321c6560bdbe28df7808e4d03b0ef625c4e8f8e9935907763477ac601421066" + } + } + } + } + } + }, { + "Register" : { + "Identifiable" : { + "NewAssetDefinition" : { + "id" : "test_asset#admin", + "value_type" : "Store", + "mintable" : "Once", + "metadata" : { } + } + } + } + }, { + "Register" : { + "Identifiable" : { + "NewAssetDefinition" : { + "id" : "wonderland_asset#admin", + "value_type" : "Store", + "mintable" : "Once", + "metadata" : { } + } + } + } + }, { + "Register" : { + "Identifiable" : { + "NewAssetDefinition" : { + "id" : "123#test", + "value_type" : "Store", + "mintable" : "Infinitely", + "metadata" : { } + } + } + } + }, { + "Register" : { + "Identifiable" : { + "PermissionTokenDefinition" : { + "id" : "can_set_key_value_in_user_assets", + "params" : { + "asset_id" : "Id" + } + } + } + } + }, { + "Register" : { + "Identifiable" : { + "PermissionTokenDefinition" : { + "id" : "can_mint_user_asset_definitions", + "params" : { + "asset_definition_id" : "Id" + } + } + } + } + }, { + "Register" : { + "Identifiable" : { + "PermissionTokenDefinition" : { + "id" : "can_burn_asset_with_definition", + "params" : { + "asset_definition_id" : "Id" + } + } + } + } + }, { + "Register" : { + "Identifiable" : { + "PermissionTokenDefinition" : { + "id" : "can_burn_user_assets", + "params" : { + "asset_id" : "Id" + } + } + } + } + }, { + "Register" : { + "Identifiable" : { + "Asset" : { + "id" : "wonderland_asset#admin#bob@admin", + "value" : { + "Store" : { } + } + } + } + } + }, { + "Register" : { + "Identifiable" : { + "Asset" : { + "id" : "test_asset#admin#bob@admin", + "value" : { + "Store" : { } + } + } + } + } + }, { + "SetKeyValue" : { + "object_id" : { + "Id" : { + "AssetId" : "123#test#alice@wonderland" + } + }, + "key" : { + "Name" : "id" + }, + "value" : { + "String" : "123" + } + } + }, { + "SetKeyValue" : { + "object_id" : { + "Id" : { + "AssetId" : "123#test#alice@wonderland" + } + }, + "key" : { + "Name" : "wt" + }, + "value" : { + "U32" : 123 + } + } + }, { + "SetKeyValue" : { + "object_id" : { + "Id" : { + "AssetId" : "123#test#alice@wonderland" + } + }, + "key" : { + "Name" : "fg" + }, + "value" : { + "String" : "test" + } + } + }, { + "SetKeyValue" : { + "object_id" : { + "Id" : { + "AssetId" : "123#test#alice@wonderland" + } + }, + "key" : { + "Name" : "ij" + }, + "value" : { + "String" : "test" + } + } + }, { + "SetKeyValue" : { + "object_id" : { + "Id" : { + "AssetId" : "123#test#alice@wonderland" + } + }, + "key" : { + "Name" : "gh" + }, + "value" : { + "U32" : 123 + } + } + }, { + "SetKeyValue" : { + "object_id" : { + "Id" : { + "AssetId" : "123#test#alice@wonderland" + } + }, + "key" : { + "Name" : "ef" + }, + "value" : { + "U32" : 1234 + } + } + }, { + "SetKeyValue" : { + "object_id" : { + "Id" : { + "AssetId" : "123#test#alice@wonderland" + } + }, + "key" : { + "Name" : "cd" + }, + "value" : { + "U128" : 123 + } + } + }, { + "SetKeyValue" : { + "object_id" : { + "Id" : { + "AssetId" : "123#test#alice@wonderland" + } + }, + "key" : { + "Name" : "ab" + }, + "value" : { + "Bool" : false + } + } + }, { + "Grant" : { + "object" : { + "PermissionToken" : { + "definition_id" : "can_set_key_value_in_user_assets", + "params" : { + "asset_id" : { + "Id" : { + "AssetId" : "123#test#alice@wonderland" + } + } + } + } + }, + "destination_id" : { + "Id" : { + "AccountId" : "bob@admin" + } + } + } + } ] + } ] +} \ No newline at end of file From 6934c948ba8de5898999a76fe141907ab45644f2 Mon Sep 17 00:00:00 2001 From: Timur Guskov Date: Fri, 9 Jun 2023 16:19:20 +0300 Subject: [PATCH 05/22] feature (issue 268): fix deserializers Signed-off-by: Timur Guskov --- .../kotlin/jp/co/soramitsu/iroha2/Serde.kt | 127 +++++--- .../co/soramitsu/iroha2/DeserializerTest.kt | 32 ++- .../src/main/resources/genesis.json | 86 +++--- .../src/main/resources/genesis2.json | 272 +++++++----------- .../src/main/resources/genesis3.json | 215 ++++++++++++++ 5 files changed, 463 insertions(+), 269 deletions(-) create mode 100644 modules/test-tools/src/main/resources/genesis3.json diff --git a/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt b/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt index a7d541c4e..53d46463b 100644 --- a/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt +++ b/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt @@ -12,6 +12,7 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.databind.SerializerProvider import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.databind.node.ObjectNode import com.fasterxml.jackson.module.kotlin.KotlinFeature import com.fasterxml.jackson.module.kotlin.KotlinModule import io.ipfs.multihash.Multihash @@ -57,6 +58,7 @@ import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.Token import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.TokenId import jp.co.soramitsu.iroha2.generated.datamodel.permission.validator.Validator import jp.co.soramitsu.iroha2.generated.datamodel.role.NewRole +import jp.co.soramitsu.iroha2.generated.datamodel.role.Role import jp.co.soramitsu.iroha2.generated.datamodel.role.RoleId import jp.co.soramitsu.iroha2.generated.datamodel.transaction.TransactionLimits import jp.co.soramitsu.iroha2.generated.datamodel.transaction.TransactionQueryResult @@ -93,9 +95,11 @@ val JSON_SERDE by lazy { module.addDeserializer(AssetDefinitionId::class.java, AssetDefinitionIdDeserializer) module.addDeserializer(AssetId::class.java, AssetIdDeserializer) module.addDeserializer(RegisterBox::class.java, RegisterBoxDeserializer) + module.addDeserializer(MintBox::class.java, MintBoxDeserializer) module.addDeserializer(SetKeyValueBox::class.java, SetKeyValueBoxDeserializer) module.addDeserializer(Metadata::class.java, MetadataDeserializer) module.addDeserializer(TokenId::class.java, TokenIdDeserializer) + module.addDeserializer(NewRole::class.java, NewRoleDeserializer) module.addKeyDeserializer(AssetDefinitionId::class.java, AssetDefinitionIdKeyDeserializer) module.addKeyDeserializer(AccountId::class.java, AccountIdKeyDeserializer) module.addKeyDeserializer(AssetId::class.java, AssetIdKeyDeserializer) @@ -160,7 +164,7 @@ private fun sealedDeserializeGrantBox(p: JsonParser, mapper: ObjectMapper): Gran nodes.add(node) } - val node = nodes[0].fields().next() + val node = jsonNode.fields().next() val destination = nodes[1] val param = node.key @@ -183,7 +187,13 @@ private fun sealedDeserializeGrantBox(p: JsonParser, mapper: ObjectMapper): Gran private fun sealedDeserializeValue(p: JsonParser, mapper: ObjectMapper): Value { val node = p.readValueAsTree().fields().next() - val param = if (node.key.contains("Id")) "Id" else node.key + val param = if (node.key.contains("Id")) { + "Id" + } else if ("Numeric" == node.key) { + node.value.fields().next().key + } else { + node.key + } val clazz = when (param) { "Bool" -> Boolean::class @@ -218,8 +228,8 @@ private fun sealedDeserializeValue(p: JsonParser, mapper: ObjectMapper): Value { } else if (param == "String") { return Value.String(node.value.asText()) } else { - val name = if (node.key == "Id") node.value.fields().next().key else node.key - val value = if (node.key == "Id") node.value.fields().next().value else node.value + val name = if (node.key == "Id" || node.key == "Numeric") node.value.fields().next().key else node.key + val value = if (node.key == "Id" || node.key == "Numeric") node.value.fields().next().value else node.value val subtype = clazz.nestedClasses.find { clazz -> !clazz.isCompanion && clazz.simpleName == name } ?: throw DeserializationException("Class with constructor($param) not found") @@ -248,14 +258,22 @@ private fun sealedDeserializeValue(p: JsonParser, mapper: ObjectMapper): Value { BlockHeaderValue::class -> Value.BlockHeader(subtype.primaryConstructor?.call(arg) as BlockHeaderValue) Ipv4Addr::class -> Value.Ipv4Addr(subtype.primaryConstructor?.call(arg) as Ipv4Addr) Ipv6Addr::class -> Value.Ipv6Addr(subtype.primaryConstructor?.call(arg) as Ipv6Addr) - NumericValue::class -> Value.Numeric(subtype.primaryConstructor?.call(arg) as NumericValue) + NumericValue::class -> { + when (name) { + "U32" -> Value.Numeric(subtype.primaryConstructor?.call(arg) as NumericValue.U32) + "U64" -> Value.Numeric(subtype.primaryConstructor?.call(arg) as NumericValue.U64) + "U128" -> Value.Numeric(subtype.primaryConstructor?.call(arg) as NumericValue.U128) + "Fixed" -> Value.Numeric(subtype.primaryConstructor?.call(arg) as NumericValue.Fixed) + else -> throw DeserializationException("Numeric value $param not found") + } + } else -> throw DeserializationException("Value type $clazz not found") } } } private fun sealedDeserializeIdBox(p: JsonParser, mapper: ObjectMapper): IdBox { - val node = p.readValueAsTree().fields().next().value.fields().next() + val node = p.readValueAsTree().fields().next() val param = node.key val subtype = IdBox::class.nestedClasses.find { clazz -> @@ -271,7 +289,7 @@ private fun sealedDeserializeIdBox(p: JsonParser, mapper: ObjectMapper): IdBox { } private fun sealedDeserializeRegisterBox(p: JsonParser, mapper: ObjectMapper): RegisterBox { - val node = p.readValueAsTree().fields().next().value.fields().next() + val node = p.readValueAsTree().fields().next() val param = node.key.removePrefix("New") val subtype = RegistrableBox::class.nestedClasses.find { clazz -> @@ -296,15 +314,43 @@ private fun sealedDeserializeRegisterBox(p: JsonParser, mapper: ObjectMapper): R } } +private fun sealedDeserializeMintBox(p: JsonParser, mapper: ObjectMapper): MintBox { + val jsonNode = p.readValueAsTree() + val iter = jsonNode.iterator() + val nodes = mutableListOf() + while (iter.hasNext()) { + val node = iter.next() + nodes.add(node) + } + + val newNode = mapper.createObjectNode().set( + jsonNode.fields().next().key, + jsonNode.fields().next().value + ) + val objectId = mapper.convertValue( + newNode, + "jp.co.soramitsu.iroha2.generated.datamodel.Value".asClass() + ) as Value + val destination = mapper.convertValue( + nodes[1], + "jp.co.soramitsu.iroha2.generated.datamodel.IdBox".asClass() + ) as IdBox + return MintBox( + `object` = objectId.evaluatesTo().cast(), + destinationId = destination.evaluatesTo().cast() + ) +} + private fun sealedDeserializeSetKeyValueBox(p: JsonParser, mapper: ObjectMapper): SetKeyValueBox { - val iter = p.readValueAsTree().iterator() + val jsonNode = p.readValueAsTree() + val iter = jsonNode.iterator() val nodes = mutableListOf() while (iter.hasNext()) { val node = iter.next() nodes.add(node) } - val objectId = nodes[0].fields().next().value.fields().next() + val objectId = jsonNode.fields().next() val key = nodes[1].fields().next() val subtype = IdBox::class.nestedClasses.find { clazz -> !clazz.isCompanion && clazz.simpleName == objectId.key @@ -323,6 +369,29 @@ private fun sealedDeserializeSetKeyValueBox(p: JsonParser, mapper: ObjectMapper) ) } +private fun sealedDeserializeNewRole(p: JsonParser, mapper: ObjectMapper): NewRole { + val iter = p.readValueAsTree().iterator() + val nodes = mutableListOf() + while (iter.hasNext()) { + val node = iter.next() + nodes.add(node) + } + + val tokens = nodes[1].map { + mapper.convertValue( + it, + "jp.co.soramitsu.iroha2.generated.datamodel.permission.token.Token".asClass() + ) as Token + } + val roleId = RoleId(nodes[0].asText().asName()) + return NewRole( + inner = Role( + id = roleId, + permissions = tokens + ) + ) +} + private fun sealedDeserializeMetadata(p: JsonParser, mapper: ObjectMapper): Metadata { val nodeMetadata = p.readValueAsTree().fields() if (!nodeMetadata.hasNext()) { @@ -335,34 +404,6 @@ private fun sealedDeserializeMetadata(p: JsonParser, mapper: ObjectMapper): Meta return Metadata(mapOf(Pair(key, value))) } -private fun sealedDeserializeToken(p: JsonParser, mapper: ObjectMapper): Token { - val jsonNode = p.readValueAsTree() - - val iter = jsonNode.iterator() - val nodes = mutableListOf() - - while (iter.hasNext()) { - val node = iter.next() - nodes.add(node) - } - val definitionId = jsonNode.fields().next() - val params = nodes[1].fields() - val tokenMap = mutableMapOf() - - val arg = mapper.convertValue(definitionId.value, "jp.co.soramitsu.iroha2.generated.datamodel.permission.token.TokenId".asClass()) as TokenId - if (params.hasNext()) { - val params = params.next().value.fields().next() - val arg2 = mapper.convertValue(params.key, "jp.co.soramitsu.iroha2.generated.datamodel.name.Name".asClass()) as Name - val arg3 = mapper.convertValue(params.value, "jp.co.soramitsu.iroha2.generated.datamodel.Value".asClass()) as Value - tokenMap[arg2] = arg3 - } - - return Token( - arg, - tokenMap - ) -} - /** * Deserializer for [Iroha Special Instructions][Instruction] */ @@ -418,6 +459,12 @@ object RegisterBoxDeserializer : JsonDeserializer() { } } +object MintBoxDeserializer : JsonDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): MintBox { + return sealedDeserializeMintBox(p, JSON_SERDE) + } +} + object SetKeyValueBoxDeserializer : JsonDeserializer() { override fun deserialize(p: JsonParser, ctxt: DeserializationContext): SetKeyValueBox { return sealedDeserializeSetKeyValueBox(p, JSON_SERDE) @@ -436,9 +483,9 @@ object TokenIdDeserializer : JsonDeserializer() { } } -object TokenDeserializer : JsonDeserializer() { - override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Token { - return sealedDeserializeToken(p, JSON_SERDE) +object NewRoleDeserializer : JsonDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): NewRole { + return sealedDeserializeNewRole(p, JSON_SERDE) } } diff --git a/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt b/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt index 3693d8eca..4573c1691 100644 --- a/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt +++ b/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt @@ -3,7 +3,7 @@ package jp.co.soramitsu.iroha2 import jp.co.soramitsu.iroha2.generated.core.genesis.RawGenesisBlock import org.junit.jupiter.api.Test import java.io.File -import kotlin.test.assertNotNull +import kotlin.test.assertEquals class DeserializerTest { @Test @@ -25,9 +25,7 @@ class DeserializerTest { .replace("\r\n", "") .replace(" ", "") .replace(regex, "publicKey") -// assertEquals(initialJson, newJson) - assertNotNull(newJson) - assertNotNull(initialJson) + assertEquals(initialJson, newJson) } @Test @@ -49,8 +47,28 @@ class DeserializerTest { .replace("\r\n", "") .replace(" ", "") .replace(regex, "publicKey") -// assertEquals(initialJson, newJson) - assertNotNull(newJson) - assertNotNull(initialJson) + assertEquals(initialJson, newJson) + } + + @Test + fun `should deserialize genesis block3`() { + val regex = "(\"ed01)+\\w+\"".toRegex() + val json = File("../test-tools/src/main/resources/genesis3.json") + val node = JSON_SERDE.readTree(json) + val block = JSON_SERDE.convertValue(node, RawGenesisBlock::class.java) + + assert(block.transactions.isNotEmpty()) + assert(block.transactions.first().isi.size == 20) + + val genesis = Genesis(block) + val newJson = genesis.asJson() + .replace("\r\n", "") + .replace(" ", "") + .replace(regex, "publicKey") + val initialJson = json.readText() + .replace("\r\n", "") + .replace(" ", "") + .replace(regex, "publicKey") + assertEquals(initialJson, newJson) } } diff --git a/modules/test-tools/src/main/resources/genesis.json b/modules/test-tools/src/main/resources/genesis.json index 37a53a11a..8d756cb88 100644 --- a/modules/test-tools/src/main/resources/genesis.json +++ b/modules/test-tools/src/main/resources/genesis.json @@ -4,96 +4,76 @@ "isi": [ { "Register": { - "Identifiable" : { - "NewDomain": { - "id": "wonderland", - "logo": null, - "metadata": {} - } + "NewDomain": { + "id": "wonderland", + "logo": null, + "metadata": {} } } }, { "Register": { - "Identifiable" : { - "NewAccount": { - "id": "alice@wonderland", - "signatories": [ - "ed0120cc25624d62896d3a0bfd8940f928dc2abf27cc57cefeb442aa96d9081aae58a1" - ], - "metadata": {} - } + "NewAccount": { + "id": "alice@wonderland", + "signatories": [ + "ed0120cc25624d62896d3a0bfd8940f928dc2abf27cc57cefeb442aa96d9081aae58a1" + ], + "metadata": {} } } }, { "Register": { - "Identifiable" : { - "NewAccount": { - "id": "bob@wonderland", - "signatories": [ - "ed01207233bfc89dcbd68c19fde6ce6158225298ec1131b6a130d1aeb454c1ab5183c0" - ], - "metadata": {} - } + "NewAccount": { + "id": "bob@wonderland", + "signatories": [ + "ed01207233bfc89dcbd68c19fde6ce6158225298ec1131b6a130d1aeb454c1ab5183c0" + ], + "metadata": {} } } }, { "Register": { - "Identifiable" : { - "PermissionTokenDefinition": { - "id": "can_set_key_value_in_user_metadata", - "params": { - "account_id": "Id" - } + "PermissionTokenDefinition": { + "id": "can_set_key_value_in_user_metadata", + "params": { + "account_id": "Id" } } } }, { "Grant": { - "object" : { - "PermissionToken": { - "definition_id": "can_set_key_value_in_user_metadata", - "params": { - "account_id": { - "Id" : { - "AccountId": "alice@wonderland" - } - } + "PermissionToken": { + "definition_id": "can_set_key_value_in_user_metadata", + "params": { + "account_id": { + "AccountId": "alice@wonderland" } } }, "destination_id": { - "Id" : { - "AccountId": "bob@wonderland" - } + "AccountId": "bob@wonderland" } } }, { "Register": { - "Identifiable" : { - "PermissionTokenDefinition": { - "id": "can_register_domains", - "params": {} - } + "PermissionTokenDefinition": { + "id": "can_register_domains", + "params": {} } } }, { "Grant": { - "object" : { - "PermissionToken": { - "definition_id": "can_register_domains", - "params": {} - } + "PermissionToken": { + "definition_id": "can_register_domains", + "params": {} }, "destination_id": { - "Id" : { - "AccountId": "bob@wonderland" - } + "AccountId": "bob@wonderland" } } } diff --git a/modules/test-tools/src/main/resources/genesis2.json b/modules/test-tools/src/main/resources/genesis2.json index f55fb5535..9704d5733 100644 --- a/modules/test-tools/src/main/resources/genesis2.json +++ b/modules/test-tools/src/main/resources/genesis2.json @@ -2,204 +2,164 @@ "transactions" : [ { "isi" : [ { "Register" : { - "Identifiable" : { - "NewDomain" : { - "id" : "wonderland", - "logo" : null, - "metadata" : { } - } + "NewDomain" : { + "id" : "wonderland", + "logo" : null, + "metadata" : { } } } }, { "Register" : { - "Identifiable" : { - "NewDomain" : { - "id" : "dnalrednow", - "logo" : null, - "metadata" : { } - } + "NewDomain" : { + "id" : "dnalrednow", + "logo" : null, + "metadata" : { } } } }, { "Register" : { - "Identifiable" : { - "NewDomain" : { - "id" : "test", - "logo" : null, - "metadata" : { } - } + "NewDomain" : { + "id" : "test", + "logo" : null, + "metadata" : { } } } }, { "Register" : { - "Identifiable" : { - "NewDomain" : { - "id" : "admin", - "logo" : null, - "metadata" : { } - } + "NewDomain" : { + "id" : "admin", + "logo" : null, + "metadata" : { } } } }, { "Register" : { - "Identifiable" : { - "NewAccount" : { - "id" : "alice@wonderland", - "signatories" : [ "ed01200851a1fa7e3f04a657299263e119975be4ab0d33631ec6ad4bd5b5e77594310e" ], - "metadata" : { } - } + "NewAccount" : { + "id" : "alice@wonderland", + "signatories" : [ "ed01200851a1fa7e3f04a657299263e119975be4ab0d33631ec6ad4bd5b5e77594310e" ], + "metadata" : { } } } }, { "Register" : { - "Identifiable" : { - "NewAccount" : { - "id" : "admin@wonderland", - "signatories" : [ "ed01203161f2c70db3252505b90c0cce012c4cf4cb098da2a3b1475319ca5b3a1263f5" ], - "metadata" : { } - } + "NewAccount" : { + "id" : "admin@wonderland", + "signatories" : [ "ed01203161f2c70db3252505b90c0cce012c4cf4cb098da2a3b1475319ca5b3a1263f5" ], + "metadata" : { } } } }, { "Register" : { - "Identifiable" : { - "NewAccount" : { - "id" : "admin@dnalrednow", - "signatories" : [ "ed0120d24280aadc7167e002b263620fe936e4103e13d59a02286190a6af6a45c7c2f7" ], - "metadata" : { } - } + "NewAccount" : { + "id" : "admin@dnalrednow", + "signatories" : [ "ed0120d24280aadc7167e002b263620fe936e4103e13d59a02286190a6af6a45c7c2f7" ], + "metadata" : { } } } }, { "Register" : { - "Identifiable" : { - "NewAccount" : { - "id" : "admin2@wonderland", - "signatories" : [ "ed01202611be069b48f6313d86a4e5c99deedaccc3046b4cb294849e11dadb27a701ca" ], - "metadata" : { } - } + "NewAccount" : { + "id" : "admin2@wonderland", + "signatories" : [ "ed01202611be069b48f6313d86a4e5c99deedaccc3046b4cb294849e11dadb27a701ca" ], + "metadata" : { } } } }, { "Register" : { - "Identifiable" : { - "NewAccount" : { - "id" : "bob@admin", - "signatories" : [ "ed01207f7137560b2c1fefa36834f609953f8ad78903a7f21d3c84a69949991885ba1c" ], - "metadata" : { - "authentication" : { - "String" : "9321c6560bdbe28df7808e4d03b0ef625c4e8f8e9935907763477ac601421066" - } + "NewAccount" : { + "id" : "bob@admin", + "signatories" : [ "ed01207f7137560b2c1fefa36834f609953f8ad78903a7f21d3c84a69949991885ba1c" ], + "metadata" : { + "authentication" : { + "String" : "9321c6560bdbe28df7808e4d03b0ef625c4e8f8e9935907763477ac601421066" } } } } }, { "Register" : { - "Identifiable" : { - "NewAssetDefinition" : { - "id" : "test_asset#admin", - "value_type" : "Store", - "mintable" : "Once", - "metadata" : { } - } + "NewAssetDefinition" : { + "id" : "test_asset#admin", + "value_type" : "Store", + "mintable" : "Once", + "metadata" : { } } } }, { "Register" : { - "Identifiable" : { - "NewAssetDefinition" : { - "id" : "wonderland_asset#admin", - "value_type" : "Store", - "mintable" : "Once", - "metadata" : { } - } + "NewAssetDefinition" : { + "id" : "wonderland_asset#admin", + "value_type" : "Store", + "mintable" : "Once", + "metadata" : { } } } }, { "Register" : { - "Identifiable" : { - "NewAssetDefinition" : { - "id" : "123#test", - "value_type" : "Store", - "mintable" : "Infinitely", - "metadata" : { } - } + "NewAssetDefinition" : { + "id" : "123#test", + "value_type" : "Store", + "mintable" : "Infinitely", + "metadata" : { } } } }, { "Register" : { - "Identifiable" : { - "PermissionTokenDefinition" : { - "id" : "can_set_key_value_in_user_assets", - "params" : { - "asset_id" : "Id" - } + "PermissionTokenDefinition" : { + "id" : "can_set_key_value_in_user_assets", + "params" : { + "asset_id" : "Id" } } } }, { "Register" : { - "Identifiable" : { - "PermissionTokenDefinition" : { - "id" : "can_mint_user_asset_definitions", - "params" : { - "asset_definition_id" : "Id" - } + "PermissionTokenDefinition" : { + "id" : "can_mint_user_asset_definitions", + "params" : { + "asset_definition_id" : "Id" } } } }, { "Register" : { - "Identifiable" : { - "PermissionTokenDefinition" : { - "id" : "can_burn_asset_with_definition", - "params" : { - "asset_definition_id" : "Id" - } + "PermissionTokenDefinition" : { + "id" : "can_burn_asset_with_definition", + "params" : { + "asset_definition_id" : "Id" } } } }, { "Register" : { - "Identifiable" : { - "PermissionTokenDefinition" : { - "id" : "can_burn_user_assets", - "params" : { - "asset_id" : "Id" - } + "PermissionTokenDefinition" : { + "id" : "can_burn_user_assets", + "params" : { + "asset_id" : "Id" } } } }, { "Register" : { - "Identifiable" : { - "Asset" : { - "id" : "wonderland_asset#admin#bob@admin", - "value" : { - "Store" : { } - } + "Asset" : { + "id" : "wonderland_asset#admin#bob@admin", + "value" : { + "Store" : { } } } } }, { "Register" : { - "Identifiable" : { - "Asset" : { - "id" : "test_asset#admin#bob@admin", - "value" : { - "Store" : { } - } + "Asset" : { + "id" : "test_asset#admin#bob@admin", + "value" : { + "Store" : { } } } } }, { "SetKeyValue" : { - "object_id" : { - "Id" : { - "AssetId" : "123#test#alice@wonderland" - } - }, + "AssetId" : "123#test#alice@wonderland", "key" : { "Name" : "id" }, @@ -209,25 +169,19 @@ } }, { "SetKeyValue" : { - "object_id" : { - "Id" : { - "AssetId" : "123#test#alice@wonderland" - } - }, + "AssetId" : "123#test#alice@wonderland", "key" : { "Name" : "wt" }, "value" : { - "U32" : 123 + "Numeric" : { + "U32" : 123 + } } } }, { "SetKeyValue" : { - "object_id" : { - "Id" : { - "AssetId" : "123#test#alice@wonderland" - } - }, + "AssetId" : "123#test#alice@wonderland", "key" : { "Name" : "fg" }, @@ -237,11 +191,7 @@ } }, { "SetKeyValue" : { - "object_id" : { - "Id" : { - "AssetId" : "123#test#alice@wonderland" - } - }, + "AssetId" : "123#test#alice@wonderland", "key" : { "Name" : "ij" }, @@ -251,53 +201,43 @@ } }, { "SetKeyValue" : { - "object_id" : { - "Id" : { - "AssetId" : "123#test#alice@wonderland" - } - }, + "AssetId" : "123#test#alice@wonderland", "key" : { "Name" : "gh" }, "value" : { - "U32" : 123 + "Numeric" : { + "U32" : 123 + } } } }, { "SetKeyValue" : { - "object_id" : { - "Id" : { - "AssetId" : "123#test#alice@wonderland" - } - }, + "AssetId" : "123#test#alice@wonderland", "key" : { "Name" : "ef" }, "value" : { - "U32" : 1234 + "Numeric" : { + "U32" : 1234 + } } } }, { "SetKeyValue" : { - "object_id" : { - "Id" : { - "AssetId" : "123#test#alice@wonderland" - } - }, + "AssetId" : "123#test#alice@wonderland", "key" : { "Name" : "cd" }, "value" : { - "U128" : 123 + "Numeric" : { + "U128" : 123 + } } } }, { "SetKeyValue" : { - "object_id" : { - "Id" : { - "AssetId" : "123#test#alice@wonderland" - } - }, + "AssetId" : "123#test#alice@wonderland", "key" : { "Name" : "ab" }, @@ -307,22 +247,16 @@ } }, { "Grant" : { - "object" : { - "PermissionToken" : { - "definition_id" : "can_set_key_value_in_user_assets", - "params" : { - "asset_id" : { - "Id" : { - "AssetId" : "123#test#alice@wonderland" - } - } + "PermissionToken" : { + "definition_id" : "can_set_key_value_in_user_assets", + "params" : { + "asset_id" : { + "AssetId" : "123#test#alice@wonderland" } } }, "destination_id" : { - "Id" : { - "AccountId" : "bob@admin" - } + "AccountId" : "bob@admin" } } } ] diff --git a/modules/test-tools/src/main/resources/genesis3.json b/modules/test-tools/src/main/resources/genesis3.json new file mode 100644 index 000000000..df3bf538a --- /dev/null +++ b/modules/test-tools/src/main/resources/genesis3.json @@ -0,0 +1,215 @@ +{ + "transactions" : [ { + "isi" : [ { + "Register" : { + "NewDomain" : { + "id" : "wonderland", + "logo" : null, + "metadata" : { + "OhOXbyHMGo" : { + "String" : "OhOXbyHMGo" + } + } + } + } + }, { + "Register" : { + "NewAccount" : { + "id" : "alice@wonderland", + "signatories" : [ "ed01201dd28d2ed1e708998990982007dcf2e1582d73a62fcb40460152ff8114c217e2" ], + "metadata" : { + "xnPrkBHMaF" : { + "String" : "xnPrkBHMaF" + } + } + } + } + }, { + "Register" : { + "NewAccount" : { + "id" : "bob@wonderland", + "signatories" : [ "ed01206bd8c135e5f490b5f66b79b0e366af1b7560c251e5fffe37e7e5c5b9f5565dc4" ], + "metadata" : { + "kGwJtCQaew" : { + "String" : "kGwJtCQaew" + } + } + } + } + }, { + "Register" : { + "NewAccount" : { + "id" : "foo@wonderland", + "signatories" : [ "ed0120d52923932164a87280eda508d321e252b04e9fe5fc82a36adac8ccb5df257338" ], + "metadata" : { + "key" : { + "String" : "value" + } + } + } + } + }, { + "Register" : { + "NewAssetDefinition" : { + "id" : "xor#wonderland", + "value_type" : "Quantity", + "mintable" : "Infinitely", + "metadata" : { } + } + } + }, { + "Register" : { + "NewAssetDefinition" : { + "id" : "test#wonderland", + "value_type" : "Store", + "mintable" : "Infinitely", + "metadata" : { } + } + } + }, { + "Register" : { + "NewAssetDefinition" : { + "id" : "test2#wonderland", + "value_type" : "Store", + "mintable" : "Infinitely", + "metadata" : { } + } + } + }, { + "Register" : { + "NewAssetDefinition" : { + "id" : "val#wonderland", + "value_type" : "Quantity", + "mintable" : "Infinitely", + "metadata" : { } + } + } + }, { + "Register" : { + "NewAssetDefinition" : { + "id" : "foo#wonderland", + "value_type" : "Store", + "mintable" : "Infinitely", + "metadata" : { + "key" : { + "String" : "sqhghUGohQkHtMWdynXQvcFhzSBCOzDBuXbeIRiZKgxPMLifiJ" + } + } + } + } + }, { + "Register" : { + "PermissionTokenDefinition" : { + "id" : "can_burn_asset_with_definition", + "params" : { + "asset_definition_id" : "Id" + } + } + } + }, { + "Register" : { + "PermissionTokenDefinition" : { + "id" : "can_set_key_value_in_user_metadata", + "params" : { + "account_id" : "Id" + } + } + } + }, { + "Register" : { + "PermissionTokenDefinition" : { + "id" : "can_remove_key_value_in_user_metadata", + "params" : { + "account_id" : "Id" + } + } + } + }, { + "Register" : { + "NewRole" : { + "id" : "USER_METADATA_ACCESS", + "permissions" : [ { + "definition_id" : "can_set_key_value_in_user_metadata", + "params" : { + "account_id" : { + "AccountId" : "alice@wonderland" + } + } + }, { + "definition_id" : "can_remove_key_value_in_user_metadata", + "params" : { + "account_id" : { + "AccountId" : "alice@wonderland" + } + } + } ] + } + } + }, { + "Mint" : { + "U32" : 100, + "destination_id" : { + "AssetId" : "xor#wonderland#alice@wonderland" + } + } + }, { + "Grant" : { + "PermissionToken" : { + "definition_id" : "can_burn_asset_with_definition", + "params" : { + "asset_definition_id" : { + "AssetDefinitionId" : "xor#wonderland" + } + } + }, + "destination_id" : { + "AccountId" : "alice@wonderland" + } + } + }, { + "SetKeyValue" : { + "AssetId" : "foo#wonderland#alice@wonderland", + "key" : { + "Name" : "key" + }, + "value" : { + "String" : "sqhghUGohQkHtMWdynXQvcFhzSBCOzDBuXbeIRiZKgxPMLifiJ" + } + } + }, + { + "SetKeyValue" : { + "AssetId" : "foo#wonderland#alice@wonderland", + "key" : { + "Name" : "key2" + }, + "value" : { + "Numeric" : { + "U32" : 123 + } + } + } + },{ + "Mint" : { + "U32" : 100, + "destination_id" : { + "AssetId" : "xor#wonderland#bob@wonderland" + } + } + }, { + "Mint" : { + "U32" : 1, + "destination_id" : { + "AssetId" : "xor#wonderland#alice@wonderland" + } + } + }, { + "Mint" : { + "U32" : 1, + "destination_id" : { + "AssetId" : "val#wonderland#alice@wonderland" + } + } + } ] + } ] +} \ No newline at end of file From 713cc73963a68417f923b77ed7160acc34e27fae Mon Sep 17 00:00:00 2001 From: Timur Guskov Date: Fri, 9 Jun 2023 16:38:12 +0300 Subject: [PATCH 06/22] feature (issue 268): PR comments fix Signed-off-by: Timur Guskov --- .../kotlin/jp/co/soramitsu/iroha2/Serde.kt | 111 ++++++++++-------- .../co/soramitsu/iroha2/DeserializerTest.kt | 95 +++++++++++---- 2 files changed, 131 insertions(+), 75 deletions(-) diff --git a/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt b/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt index 53d46463b..e0834cf02 100644 --- a/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt +++ b/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt @@ -68,6 +68,7 @@ import jp.co.soramitsu.iroha2.generated.datamodel.trigger.TriggerId import jp.co.soramitsu.iroha2.generated.primitives.addr.Ipv4Addr import jp.co.soramitsu.iroha2.generated.primitives.addr.Ipv6Addr import java.io.ByteArrayOutputStream +import kotlin.reflect.KClass import kotlin.reflect.full.createInstance import kotlin.reflect.full.memberProperties import kotlin.reflect.full.primaryConstructor @@ -195,7 +196,63 @@ private fun sealedDeserializeValue(p: JsonParser, mapper: ObjectMapper): Value { node.key } - val clazz = when (param) { + val clazz = getClazzByParam(param) + + if (param == "Bool") { + return Value.Bool(node.value.booleanValue()) + } else if (param == "String") { + return Value.String(node.value.asText()) + } else { + val name = if (node.key == "Id" || node.key == "Numeric") node.value.fields().next().key else node.key + val value = if (node.key == "Id" || node.key == "Numeric") node.value.fields().next().value else node.value + val subtype = clazz.nestedClasses.find { clazz -> + !clazz.isCompanion && clazz.simpleName == name + } ?: throw DeserializationException("Class with constructor($param) not found") + + val argTypeName = subtype.primaryConstructor?.parameters + ?.firstOrNull()?.type?.toString() + ?: throw DeserializationException("Subtype parameter not found by $param") + + val arg = mapper.convertValue(value, argTypeName.asClass()) + return getValueByClazz(clazz, subtype, arg, name, param) + } +} + +private fun getValueByClazz(clazz: KClass, subtype: KClass<*>, arg: Any, name: String, param: String): Value { + return when (clazz) { + Name::class -> Value.Name(subtype.primaryConstructor?.call(arg) as Name) + Value::class -> throw DeserializationException("Value type $clazz not supported") + Metadata::class -> Value.LimitedMetadata(subtype.primaryConstructor?.call(arg) as Metadata) + Limits::class -> Value.MetadataLimits(subtype.primaryConstructor?.call(arg) as Limits) + TransactionLimits::class -> Value.TransactionLimits(subtype.primaryConstructor?.call(arg) as TransactionLimits) + LengthLimits::class -> Value.LengthLimits(subtype.primaryConstructor?.call(arg) as LengthLimits) + IdBox::class -> Value.Id(subtype.primaryConstructor?.call(arg) as IdBox) + IdentifiableBox::class -> Value.Identifiable(subtype.primaryConstructor?.call(arg) as IdentifiableBox) + PublicKey::class -> Value.PublicKey(subtype.primaryConstructor?.call(arg) as PublicKey) + SignatureCheckCondition::class -> Value.SignatureCheckCondition(subtype.primaryConstructor?.call(arg) as SignatureCheckCondition) + TransactionValue::class -> Value.TransactionValue(subtype.primaryConstructor?.call(arg) as TransactionValue) + TransactionQueryResult::class -> Value.TransactionQueryResult(subtype.primaryConstructor?.call(arg) as TransactionQueryResult) + Token::class -> Value.PermissionToken(subtype.primaryConstructor?.call(arg) as Token) + Hash::class -> Value.Hash(subtype.primaryConstructor?.call(arg) as Hash) + BlockValue::class -> Value.Block(subtype.primaryConstructor?.call(arg) as BlockValue) + BlockHeaderValue::class -> Value.BlockHeader(subtype.primaryConstructor?.call(arg) as BlockHeaderValue) + Ipv4Addr::class -> Value.Ipv4Addr(subtype.primaryConstructor?.call(arg) as Ipv4Addr) + Ipv6Addr::class -> Value.Ipv6Addr(subtype.primaryConstructor?.call(arg) as Ipv6Addr) + NumericValue::class -> { + when (name) { + "U32" -> Value.Numeric(subtype.primaryConstructor?.call(arg) as NumericValue.U32) + "U64" -> Value.Numeric(subtype.primaryConstructor?.call(arg) as NumericValue.U64) + "U128" -> Value.Numeric(subtype.primaryConstructor?.call(arg) as NumericValue.U128) + "Fixed" -> Value.Numeric(subtype.primaryConstructor?.call(arg) as NumericValue.Fixed) + else -> throw DeserializationException("Numeric value $param not found") + } + } + else -> throw DeserializationException("Value type $clazz not found") + } +} + +private fun getClazzByParam(param: String): KClass { + return when (param) { "Bool" -> Boolean::class "String" -> String::class "Name" -> Name::class @@ -222,54 +279,6 @@ private fun sealedDeserializeValue(p: JsonParser, mapper: ObjectMapper): Value { "Fixed" -> NumericValue::class else -> throw DeserializationException("Value key $param not found") } - - if (param == "Bool") { - return Value.Bool(node.value.booleanValue()) - } else if (param == "String") { - return Value.String(node.value.asText()) - } else { - val name = if (node.key == "Id" || node.key == "Numeric") node.value.fields().next().key else node.key - val value = if (node.key == "Id" || node.key == "Numeric") node.value.fields().next().value else node.value - val subtype = clazz.nestedClasses.find { clazz -> - !clazz.isCompanion && clazz.simpleName == name - } ?: throw DeserializationException("Class with constructor($param) not found") - - val argTypeName = subtype.primaryConstructor?.parameters - ?.firstOrNull()?.type?.toString() - ?: throw DeserializationException("Subtype parameter not found by $param") - - val arg = mapper.convertValue(value, argTypeName.asClass()) - return when (clazz) { - Name::class -> Value.Name(subtype.primaryConstructor?.call(arg) as Name) - Value::class -> throw DeserializationException("Value type $clazz not supported") - Metadata::class -> Value.LimitedMetadata(subtype.primaryConstructor?.call(arg) as Metadata) - Limits::class -> Value.MetadataLimits(subtype.primaryConstructor?.call(arg) as Limits) - TransactionLimits::class -> Value.TransactionLimits(subtype.primaryConstructor?.call(arg) as TransactionLimits) - LengthLimits::class -> Value.LengthLimits(subtype.primaryConstructor?.call(arg) as LengthLimits) - IdBox::class -> Value.Id(subtype.primaryConstructor?.call(arg) as IdBox) - IdentifiableBox::class -> Value.Identifiable(subtype.primaryConstructor?.call(arg) as IdentifiableBox) - PublicKey::class -> Value.PublicKey(subtype.primaryConstructor?.call(arg) as PublicKey) - SignatureCheckCondition::class -> Value.SignatureCheckCondition(subtype.primaryConstructor?.call(arg) as SignatureCheckCondition) - TransactionValue::class -> Value.TransactionValue(subtype.primaryConstructor?.call(arg) as TransactionValue) - TransactionQueryResult::class -> Value.TransactionQueryResult(subtype.primaryConstructor?.call(arg) as TransactionQueryResult) - Token::class -> Value.PermissionToken(subtype.primaryConstructor?.call(arg) as Token) - Hash::class -> Value.Hash(subtype.primaryConstructor?.call(arg) as Hash) - BlockValue::class -> Value.Block(subtype.primaryConstructor?.call(arg) as BlockValue) - BlockHeaderValue::class -> Value.BlockHeader(subtype.primaryConstructor?.call(arg) as BlockHeaderValue) - Ipv4Addr::class -> Value.Ipv4Addr(subtype.primaryConstructor?.call(arg) as Ipv4Addr) - Ipv6Addr::class -> Value.Ipv6Addr(subtype.primaryConstructor?.call(arg) as Ipv6Addr) - NumericValue::class -> { - when (name) { - "U32" -> Value.Numeric(subtype.primaryConstructor?.call(arg) as NumericValue.U32) - "U64" -> Value.Numeric(subtype.primaryConstructor?.call(arg) as NumericValue.U64) - "U128" -> Value.Numeric(subtype.primaryConstructor?.call(arg) as NumericValue.U128) - "Fixed" -> Value.Numeric(subtype.primaryConstructor?.call(arg) as NumericValue.Fixed) - else -> throw DeserializationException("Numeric value $param not found") - } - } - else -> throw DeserializationException("Value type $clazz not found") - } - } } private fun sealedDeserializeIdBox(p: JsonParser, mapper: ObjectMapper): IdBox { @@ -300,6 +309,10 @@ private fun sealedDeserializeRegisterBox(p: JsonParser, mapper: ObjectMapper): R ?: throw DeserializationException("Subtype parameter not found by $param") val arg = mapper.convertValue(node.value, argTypeName.asClass()) + return getRegisterBox(arg) +} + +private fun getRegisterBox(arg: Any): RegisterBox { return when (arg) { is NewDomain -> RegisterBox(RegistrableBox.Domain(arg).evaluatesTo()) is NewAccount -> RegisterBox(RegistrableBox.Account(arg).evaluatesTo()) diff --git a/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt b/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt index 4573c1691..87ae252c6 100644 --- a/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt +++ b/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt @@ -8,67 +8,110 @@ import kotlin.test.assertEquals class DeserializerTest { @Test fun `should deserialize genesis block`() { - val regex = "(\"ed01)+\\w+\"".toRegex() val json = File("../test-tools/src/main/resources/genesis.json") val node = JSON_SERDE.readTree(json) val block = JSON_SERDE.convertValue(node, RawGenesisBlock::class.java) assert(block.transactions.isNotEmpty()) + // genesis.json has 7 instructions ("isi") + // Register -> NewDomain + // Register -> NewAccount + // Register -> NewAccount + // Register -> PermissionTokenDefinition + // Grant -> PermissionToken + // Register -> PermissionTokenDefinition + // Grant -> PermissionToken assert(block.transactions.first().isi.size == 7) val genesis = Genesis(block) - val newJson = genesis.asJson() - .replace("\r\n", "") - .replace(" ", "") - .replace(regex, "publicKey") - val initialJson = json.readText() - .replace("\r\n", "") - .replace(" ", "") - .replace(regex, "publicKey") + val newJson = removeWhiteSpaceAndReplacePubKey(genesis.asJson()) + val initialJson = removeWhiteSpaceAndReplacePubKey(json.readText()) assertEquals(initialJson, newJson) } @Test fun `should deserialize genesis block2`() { - val regex = "(\"ed01)+\\w+\"".toRegex() val json = File("../test-tools/src/main/resources/genesis2.json") val node = JSON_SERDE.readTree(json) val block = JSON_SERDE.convertValue(node, RawGenesisBlock::class.java) assert(block.transactions.isNotEmpty()) + // genesis.json has 27 instructions ("isi") + // Register -> NewDomain + // Register -> NewDomain + // Register -> NewDomain + // Register -> NewDomain + // Register -> NewAccount + // Register -> NewAccount + // Register -> NewAccount + // Register -> NewAccount + // Register -> NewAccount + // Register -> NewAssetDefinition + // Register -> NewAssetDefinition + // Register -> NewAssetDefinition + // Register -> PermissionTokenDefinition + // Register -> PermissionTokenDefinition + // Register -> PermissionTokenDefinition + // Register -> PermissionTokenDefinition + // Register -> Asset + // Register -> Asset + // SetKeyValue -> AssetId + // SetKeyValue -> AssetId + // SetKeyValue -> AssetId + // SetKeyValue -> AssetId + // SetKeyValue -> AssetId + // SetKeyValue -> AssetId + // SetKeyValue -> AssetId + // SetKeyValue -> AssetId + // Grant -> PermissionToken assert(block.transactions.first().isi.size == 27) val genesis = Genesis(block) - val newJson = genesis.asJson() - .replace("\r\n", "") - .replace(" ", "") - .replace(regex, "publicKey") - val initialJson = json.readText() - .replace("\r\n", "") - .replace(" ", "") - .replace(regex, "publicKey") + val newJson = removeWhiteSpaceAndReplacePubKey(genesis.asJson()) + val initialJson = removeWhiteSpaceAndReplacePubKey(json.readText()) assertEquals(initialJson, newJson) } @Test fun `should deserialize genesis block3`() { - val regex = "(\"ed01)+\\w+\"".toRegex() val json = File("../test-tools/src/main/resources/genesis3.json") val node = JSON_SERDE.readTree(json) val block = JSON_SERDE.convertValue(node, RawGenesisBlock::class.java) assert(block.transactions.isNotEmpty()) + // genesis.json has 20 instructions ("isi") + // Register -> NewDomain + // Register -> NewAccount + // Register -> NewAccount + // Register -> NewAccount + // Register -> NewAssetDefinition + // Register -> NewAssetDefinition + // Register -> NewAssetDefinition + // Register -> NewAssetDefinition + // Register -> NewAssetDefinition + // Register -> PermissionTokenDefinition + // Register -> PermissionTokenDefinition + // Register -> PermissionTokenDefinition + // Register -> NewRole + // Mint -> AssetId + // Grant -> PermissionToken + // SetKeyValue -> AssetId + // SetKeyValue -> AssetId + // Mint -> AssetId + // Mint -> AssetId + // Mint -> AssetId assert(block.transactions.first().isi.size == 20) val genesis = Genesis(block) - val newJson = genesis.asJson() - .replace("\r\n", "") - .replace(" ", "") - .replace(regex, "publicKey") - val initialJson = json.readText() - .replace("\r\n", "") + val newJson = removeWhiteSpaceAndReplacePubKey(genesis.asJson()) + val initialJson = removeWhiteSpaceAndReplacePubKey(json.readText()) + assertEquals(initialJson, newJson) + } + + private fun removeWhiteSpaceAndReplacePubKey(json: String): String { + val regex = "(\"ed01)+\\w+\"".toRegex() + return json.replace("\r\n", "") .replace(" ", "") .replace(regex, "publicKey") - assertEquals(initialJson, newJson) } } From a1cd609a6ab116ef4cfc377de435b7d1b6deb0ed Mon Sep 17 00:00:00 2001 From: Timur Guskov Date: Tue, 13 Jun 2023 10:22:45 +0300 Subject: [PATCH 07/22] feature (issue 268): fix deserializers Signed-off-by: Timur Guskov --- .../src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt b/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt index 87ae252c6..db3ebbfb8 100644 --- a/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt +++ b/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt @@ -110,7 +110,7 @@ class DeserializerTest { private fun removeWhiteSpaceAndReplacePubKey(json: String): String { val regex = "(\"ed01)+\\w+\"".toRegex() - return json.replace("\r\n", "") + return json.replace(System.getProperty("line.separator"), "") .replace(" ", "") .replace(regex, "publicKey") } From e6bde2093cfbe610f8f61f8e9d4687ec9bd8ac31 Mon Sep 17 00:00:00 2001 From: BAStos525 Date: Fri, 16 Jun 2023 13:48:36 +0300 Subject: [PATCH 08/22] [ci]: Update Actions versions Signed-off-by: BAStos525 --- .github/workflows/iroha2-ci.yml | 10 +++++----- .github/workflows/iroha2-pr.yml | 11 ++++++----- .github/workflows/iroha2.yml | 11 ++++++----- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/.github/workflows/iroha2-ci.yml b/.github/workflows/iroha2-ci.yml index eb0053ba7..a8e2f0cde 100644 --- a/.github/workflows/iroha2-ci.yml +++ b/.github/workflows/iroha2-ci.yml @@ -14,7 +14,7 @@ jobs: prepare: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Generate libraries list id: generate-matrix run: echo "::set-output name=matrix::$(ls ./modules | jq --raw-input . | jq --slurp . | jq -c .)" @@ -27,11 +27,11 @@ jobs: matrix: library: ${{ fromJson(needs.prepare.outputs.matrix) }} steps: - - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v2 + - uses: actions/checkout@v3 + - name: Set up JDK 16 + uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '16' distribution: 'temurin' - name: Build with Gradle uses: gradle/gradle-build-action@4137be6a8bf7d7133955359dbd952c0ca73b1021 diff --git a/.github/workflows/iroha2-pr.yml b/.github/workflows/iroha2-pr.yml index 04c107895..f151a16e3 100644 --- a/.github/workflows/iroha2-pr.yml +++ b/.github/workflows/iroha2-pr.yml @@ -8,13 +8,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - uses: actions/checkout@v3 + - name: Set up JDK 16 + uses: actions/setup-java@v3 with: - java-version: 1.11 + java-version: '16' + distribution: 'temurin' - name: Cache Gradle packages - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: | ~/.gradle/caches diff --git a/.github/workflows/iroha2.yml b/.github/workflows/iroha2.yml index 3b7f86197..445ece282 100644 --- a/.github/workflows/iroha2.yml +++ b/.github/workflows/iroha2.yml @@ -8,13 +8,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - uses: actions/checkout@v3 + - name: Set up JDK 16 + uses: actions/setup-java@v3 with: - java-version: 1.11 + java-version: '16' + distribution: 'temurin' - name: Cache Gradle packages - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: | ~/.gradle/caches From e798fc3e5cb293953f7978a67852f4a3b462579f Mon Sep 17 00:00:00 2001 From: BAStos525 Date: Fri, 16 Jun 2023 14:16:18 +0300 Subject: [PATCH 09/22] fetch iroha2-ci workflow Signed-off-by: BAStos525 --- .github/workflows/iroha2-ci.yml | 104 ++++---------------------------- 1 file changed, 12 insertions(+), 92 deletions(-) diff --git a/.github/workflows/iroha2-ci.yml b/.github/workflows/iroha2-ci.yml index a8e2f0cde..f653803e0 100644 --- a/.github/workflows/iroha2-ci.yml +++ b/.github/workflows/iroha2-ci.yml @@ -10,104 +10,24 @@ name: Iroha2-java main branch workflow on: push: branches: [ iroha2-dev, iroha2-main ] + tags: + - '**' jobs: - prepare: + publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Generate libraries list - id: generate-matrix - run: echo "::set-output name=matrix::$(ls ./modules | jq --raw-input . | jq --slurp . | jq -c .)" - outputs: - matrix: ${{ steps.generate-matrix.outputs.matrix }} - build: - needs: prepare - runs-on: ubuntu-latest - strategy: - matrix: - library: ${{ fromJson(needs.prepare.outputs.matrix) }} - steps: - - uses: actions/checkout@v3 - name: Set up JDK 16 uses: actions/setup-java@v3 with: java-version: '16' distribution: 'temurin' - - name: Build with Gradle - uses: gradle/gradle-build-action@4137be6a8bf7d7133955359dbd952c0ca73b1021 - with: - arguments: build - - name: Set lib name - run: echo "lib_name=$(find ./modules/${{ matrix.library }}/build/libs/ -name *.jar -type f -print | awk -F/ '{print length($NF)" "$NF}' | sort -nk1 | head -1 | sed 's/[^ ]* //' | sed 's/\.[^.]*$//')" >> $GITHUB_ENV - - name: Publish lib to nexus for branch - if: ${{ github.ref == 'refs/heads/iroha2-dev' }} - uses: sonatype-nexus-community/nexus-repo-github-action@master - with: - serverUrl: https://nexus.iroha.tech/ - username: '${{ secrets.NEXUS_USER }}' - password: '${{ secrets.NEXUS_PASS }}' - format: maven2 - repository: maven-soramitsu - coordinates: groupId=jp.co.soramitsu.iroha2-java artifactId=${{ matrix.library }} version=${{ github.sha }} - assets: extension=jar - filename: ./modules/${{ matrix.library }}/build/libs/${{ env.lib_name }}.jar - - name: Publish sources lib to nexus for branch - if: ${{ github.ref == 'refs/heads/iroha2-dev' }} - uses: sonatype-nexus-community/nexus-repo-github-action@master - with: - serverUrl: https://nexus.iroha.tech/ - username: '${{ secrets.NEXUS_USER }}' - password: '${{ secrets.NEXUS_PASS }}' - format: maven2 - repository: maven-soramitsu - coordinates: groupId=jp.co.soramitsu.iroha2-java artifactId=${{ matrix.library }} version=${{ github.sha }} - assets: extension=jar classifier=sources - filename: ./modules/${{ matrix.library }}/build/libs/${{ env.lib_name }}-sources.jar - - name: Publish javadoc lib to nexus for branch - if: ${{ github.ref == 'refs/heads/iroha2-dev' }} - uses: sonatype-nexus-community/nexus-repo-github-action@master - with: - serverUrl: https://nexus.iroha.tech/ - username: '${{ secrets.NEXUS_USER }}' - password: '${{ secrets.NEXUS_PASS }}' - format: maven2 - repository: maven-soramitsu - coordinates: groupId=jp.co.soramitsu.iroha2-java artifactId=${{ matrix.library }} version=${{ github.sha }} - assets: extension=jar classifier=javadoc - filename: ./modules/${{ matrix.library }}/build/libs/${{ env.lib_name }}-javadoc.jar - - name: Publish lib to nexus for tag - if: ${{ github.ref_type == 'tag' }} - uses: sonatype-nexus-community/nexus-repo-github-action@master - with: - serverUrl: https://nexus.iroha.tech/ - username: '${{ secrets.NEXUS_USER }}' - password: '${{ secrets.NEXUS_PASS }}' - format: maven2 - repository: maven-soramitsu - coordinates: groupId=jp.co.soramitsu.iroha2-java artifactId=${{ matrix.library }} version=${{ github.sha }} - assets: extension=jar - filename: ./modules/${{ matrix.library }}/build/libs/${{ env.lib_name }} - - name: Publish sources lib to nexus for tag - if: ${{ github.ref_type == 'tag' }} - uses: sonatype-nexus-community/nexus-repo-github-action@master - with: - serverUrl: https://nexus.iroha.tech/ - username: '${{ secrets.NEXUS_USER }}' - password: '${{ secrets.NEXUS_PASS }}' - format: maven2 - repository: maven-soramitsu - coordinates: groupId=jp.co.soramitsu.iroha2-java artifactId=${{ matrix.library }}-sources version=${{ github.sha }} - assets: extension=jar classifier=sources - filename: ./modules/${{ matrix.library }}/build/libs/${{ env.lib_name }}-sources.jar - - name: Publish javadoc lib to nexus for tag - if: ${{ github.ref_type == 'tag' }} - uses: sonatype-nexus-community/nexus-repo-github-action@master - with: - serverUrl: https://nexus.iroha.tech/ - username: '${{ secrets.NEXUS_USER }}' - password: '${{ secrets.NEXUS_PASS }}' - format: maven2 - repository: maven-soramitsu - coordinates: groupId=jp.co.soramitsu.iroha2-java artifactId=${{ matrix.library }}-javadoc version=${{ github.sha }} - assets: extension=jar classifier=javadoc - filename: ./modules/${{ matrix.library }}/build/libs/${{ env.lib_name }}-javadoc.jar + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@v1 + - name: Publish package + uses: gradle/gradle-build-action@v2.3.3 + with: + arguments: publish + env: + NEXUS_USER: ${{ secrets.NEXUS_USER }} + NEXUS_PASS: ${{ secrets.NEXUS_PASS }} From eb33cd7fe05c6dbbcdee153291c28b6adc4e386e Mon Sep 17 00:00:00 2001 From: Andrey Kostiuchenko Date: Thu, 22 Jun 2023 15:42:26 +0500 Subject: [PATCH 10/22] Update to RC16 (#340) --- docker-compose/docker-compose.yaml | 8 +- gradle.properties | 4 +- .../src/test/resources/logback-test.xml | 15 + .../kotlin/jp/co/soramitsu/iroha2/Genesis.kt | 61 +- .../kotlin/jp/co/soramitsu/iroha2/Serde.kt | 212 +- .../co/soramitsu/iroha2/DeserializerTest.kt | 11 +- .../kotlin/jp/co/soramitsu/iroha2/Enums.kt | 48 +- .../jp/co/soramitsu/iroha2/Extensions.kt | 212 +- .../jp/co/soramitsu/iroha2/Extractors.kt | 84 +- .../kotlin/jp/co/soramitsu/iroha2/Page.kt | 2 +- .../iroha2/client/Iroha2AsyncClient.kt | 2 +- .../soramitsu/iroha2/client/Iroha2Client.kt | 66 +- .../jp/co/soramitsu/iroha2/query/Queries.kt | 98 +- .../co/soramitsu/iroha2/query/QueryBuilder.kt | 117 +- .../soramitsu/iroha2/transaction/Filters.kt | 388 +- .../iroha2/transaction/Instructions.kt | 773 +- .../iroha2/transaction/TransactionBuilder.kt | 140 +- .../java/jp/co/soramitsu/iroha2/JavaTest.java | 22 +- .../jp/co/soramitsu/iroha2/BlockStreamTest.kt | 44 +- .../co/soramitsu/iroha2/InstructionsTest.kt | 181 +- .../kotlin/jp/co/soramitsu/iroha2/PeerTest.kt | 9 +- .../jp/co/soramitsu/iroha2/QueriesTest.kt | 45 +- .../jp/co/soramitsu/iroha2/TriggersTest.kt | 27 +- .../co/soramitsu/iroha2/testengine/Genesis.kt | 140 +- .../soramitsu/iroha2/testengine/TestConsts.kt | 6 +- ...eate_nft_for_every_user_smartcontract.wasm | Bin 281395 -> 425366 bytes .../client/src/test/resources/genesis.json | 137 +- .../src/test/resources/logback-test.xml | 27 + .../client/src/test/resources/validator.wasm | Bin 0 -> 597151 bytes .../kotlin/jp/co/soramitsu/iroha2/Regex.kt | 8 + .../codegen/blueprint/StructBlueprint.kt | 15 +- .../codegen/blueprint/TupleStructBlueprint.kt | 26 +- .../codegen/generator/AbstractGenerator.kt | 4 +- .../codegen/generator/GeneratorEntryPoint.kt | 14 +- .../iroha2/codegen/generator/Scale.kt | 19 +- .../soramitsu/iroha2/parse/ExtractGeneric.kt | 5 +- .../co/soramitsu/iroha2/parse/SchemaParser.kt | 7 +- .../co/soramitsu/iroha2/parse/SchemaReader.kt | 121 +- .../co/soramitsu/iroha2/parse/TypeResolver.kt | 132 +- .../jp/co/soramitsu/iroha2/type/Composite.kt | 2 +- .../codegen/src/main/resources/schema.json | 10309 ++++++++-------- .../iroha2/parse/SchemaParserTest.kt | 501 - .../jp/co/soramitsu/iroha2/Comparators.kt | 25 +- .../{datamodel/account => }/Account.kt | 13 +- .../data/events/account => }/AccountEvent.kt | 26 +- .../events/account => }/AccountEventFilter.kt | 41 +- .../data/events/account => }/AccountFilter.kt | 18 +- .../{datamodel/account => }/AccountId.kt | 6 +- .../account => }/AccountPermissionChanged.kt | 13 +- .../events/account => }/AccountRoleChanged.kt | 7 +- .../ActionOfFilterBoxAndExecutable.kt | 50 + ...ActionOfFilterBoxAndOptimizedExecutable.kt | 53 + .../{datamodel/expression => }/Add.kt | 5 +- .../validator/Type.kt => Algorithm.kt} | 113 +- .../{datamodel/expression => }/And.kt | 4 +- .../generated/{datamodel/asset => }/Asset.kt | 4 +- .../data/events/asset => }/AssetChanged.kt | 6 +- .../{datamodel/asset => }/AssetDefinition.kt | 13 +- .../events/asset => }/AssetDefinitionEvent.kt | 31 +- .../asset => }/AssetDefinitionEventFilter.kt | 21 +- .../asset => }/AssetDefinitionFilter.kt | 19 +- .../asset => }/AssetDefinitionId.kt | 6 +- .../asset => }/AssetDefinitionOwnerChanged.kt | 7 +- .../AssetDefinitionTotalQuantityChanged.kt | 8 +- .../data/events/asset => }/AssetEvent.kt | 19 +- .../events/asset => }/AssetEventFilter.kt | 22 +- .../data/events/asset => }/AssetFilter.kt | 18 +- .../{datamodel/asset => }/AssetId.kt | 5 +- .../{datamodel/asset => }/AssetValue.kt | 11 +- .../{datamodel/asset => }/AssetValueType.kt | 12 +- .../predicate/value => }/AtIndex.kt | 4 +- ...aryOpIncompatibleNumericValueTypesError.kt | 44 + .../generated/{core/block => }/BlockHeader.kt | 16 +- .../{core/block/stream => }/BlockMessage.kt | 5 +- .../transaction => }/BlockRejectionReason.kt | 6 +- .../stream => }/BlockSubscriptionRequest.kt | 4 +- .../generated/{datamodel/isi => }/BurnBox.kt | 7 +- .../{core/block => }/CommittedBlock.kt | 14 +- .../{datamodel/isi/If.kt => Conditional.kt} | 29 +- .../events/config => }/ConfigurationEvent.kt | 23 +- .../predicate/value => }/Container.kt | 23 +- .../{datamodel/expression => }/Contains.kt | 5 +- .../{datamodel/expression => }/ContainsAll.kt | 5 +- .../{datamodel/expression => }/ContainsAny.kt | 5 +- .../expression => }/ContextValue.kt | 5 +- .../EntityFilter.kt => DataEntityFilter.kt} | 70 +- .../data/events/Event.kt => DataEvent.kt} | 88 +- .../{datamodel/expression => }/Divide.kt | 5 +- .../DoesAccountHavePermissionToken.kt | 41 + .../{datamodel/domain => }/Domain.kt | 22 +- .../data/events/domain => }/DomainEvent.kt | 27 +- .../events/domain => }/DomainEventFilter.kt | 30 +- .../data/events/domain => }/DomainFilter.kt | 18 +- .../{datamodel/domain => }/DomainId.kt | 5 +- .../{datamodel/expression => }/Equal.kt | 5 +- .../{datamodel/expression => }/EvaluatesTo.kt | 5 +- .../iroha2/generated/EvaluationError.kt | 157 + .../generated/{datamodel/events => }/Event.kt | 16 +- .../{datamodel/events => }/EventMessage.kt | 4 +- .../events => }/EventSubscriptionRequest.kt | 10 +- .../{datamodel/transaction => }/Executable.kt | 11 +- .../{datamodel/isi => }/ExecuteTriggerBox.kt | 11 +- .../ExecuteTriggerEvent.kt | 6 +- .../ExecuteTriggerEventFilter.kt | 7 +- .../events/time => }/ExecutionTime.kt | 16 +- .../{datamodel/expression => }/Expression.kt | 138 +- .../generated/{datamodel/isi => }/FailBox.kt | 4 +- .../EventsFilterBox.kt => FilterBox.kt} | 32 +- ...er.kt => FilterOptOfAccountEventFilter.kt} | 24 +- ...tFilter.kt => FilterOptOfAccountFilter.kt} | 24 +- ... FilterOptOfAssetDefinitionEventFilter.kt} | 24 +- ...kt => FilterOptOfAssetDefinitionFilter.kt} | 24 +- ...lter.kt => FilterOptOfAssetEventFilter.kt} | 24 +- ...setFilter.kt => FilterOptOfAssetFilter.kt} | 26 +- ...lter.kt => FilterOptOfDataEntityFilter.kt} | 29 +- ...ter.kt => FilterOptOfDomainEventFilter.kt} | 24 +- ...inFilter.kt => FilterOptOfDomainFilter.kt} | 24 +- ... FilterOptOfOriginFilterOfAccountEvent.kt} | 29 +- ...ptOfOriginFilterOfAssetDefinitionEvent.kt} | 35 +- ...=> FilterOptOfOriginFilterOfAssetEvent.kt} | 40 +- .../FilterOptOfOriginFilterOfDomainEvent.kt | 116 + .../FilterOptOfOriginFilterOfPeerEvent.kt | 116 + .../FilterOptOfOriginFilterOfRoleEvent.kt | 116 + .../FilterOptOfOriginFilterOfTriggerEvent.kt | 116 + ...ilter.kt => FilterOptOfPeerEventFilter.kt} | 24 +- ...PeerFilter.kt => FilterOptOfPeerFilter.kt} | 26 +- ...ilter.kt => FilterOptOfRoleEventFilter.kt} | 24 +- ...RoleFilter.kt => FilterOptOfRoleFilter.kt} | 26 +- ...er.kt => FilterOptOfTriggerEventFilter.kt} | 24 +- ...rFilter.kt => FilterOptOfTriggerFilter.kt} | 24 +- .../query/account => }/FindAccountById.kt | 6 +- .../FindAccountKeyValueByIdAndKey.kt | 8 +- .../account => }/FindAccountsByDomainId.kt | 6 +- .../query/account => }/FindAccountsByName.kt | 6 +- .../account => }/FindAccountsWithAsset.kt | 6 +- .../query/account => }/FindAllAccounts.kt | 6 +- .../trigger => }/FindAllActiveTriggerIds.kt | 7 +- .../query/asset => }/FindAllAssets.kt | 6 +- .../asset => }/FindAllAssetsDefinitions.kt | 7 +- .../query/block => }/FindAllBlockHeaders.kt | 6 +- .../query/block => }/FindAllBlocks.kt | 6 +- .../query/domain => }/FindAllDomains.kt | 6 +- .../query/peer => }/FindAllParameters.kt | 6 +- .../query/peer => }/FindAllPeers.kt | 6 +- .../FindAllPermissionTokenDefinitions.kt | 8 +- .../query/role => }/FindAllRoleIds.kt | 6 +- .../query/role => }/FindAllRoles.kt | 6 +- .../transaction => }/FindAllTransactions.kt | 7 +- .../query/asset => }/FindAssetById.kt | 6 +- .../asset => }/FindAssetDefinitionById.kt | 6 +- .../FindAssetDefinitionKeyValueByIdAndKey.kt | 8 +- .../asset => }/FindAssetKeyValueByIdAndKey.kt | 7 +- .../query/asset => }/FindAssetQuantityById.kt | 6 +- .../query/asset => }/FindAssetsByAccountId.kt | 6 +- .../FindAssetsByAssetDefinitionId.kt | 6 +- .../query/asset => }/FindAssetsByDomainId.kt | 6 +- ...indAssetsByDomainIdAndAssetDefinitionId.kt | 8 +- .../query/asset => }/FindAssetsByName.kt | 6 +- .../query/block => }/FindBlockHeaderByHash.kt | 6 +- .../query/domain => }/FindDomainById.kt | 6 +- .../FindDomainKeyValueByIdAndKey.kt | 7 +- .../isi/error => }/FindError.kt | 142 +- .../FindPermissionTokensByAccountId.kt | 7 +- .../query/role => }/FindRoleByRoleId.kt | 6 +- .../query/role => }/FindRolesByAccountId.kt | 6 +- ...ndTotalAssetQuantityByAssetDefinitionId.kt | 7 +- .../transaction => }/FindTransactionByHash.kt | 6 +- .../FindTransactionsByAccountId.kt | 7 +- .../query/trigger => }/FindTriggerById.kt | 6 +- .../FindTriggerKeyValueByIdAndKey.kt | 8 +- .../trigger => }/FindTriggersByDomainId.kt | 6 +- .../generated/{primitives/fixed => }/Fixed.kt | 8 +- ...PredicateBox.kt => GenericPredicateBox.kt} | 46 +- .../generated/{datamodel/isi => }/GrantBox.kt | 7 +- .../{datamodel/expression => }/Greater.kt | 5 +- .../generated/{crypto/hash => }/Hash.kt | 8 +- .../generated/{crypto/hash => }/HashOf.kt | 6 +- .../iroha2/generated/{datamodel => }/IdBox.kt | 107 +- .../{datamodel => }/IdentifiableBox.kt | 132 +- .../{datamodel/expression => }/If.kt | 13 +- .../isi/Instruction.kt => InstructionBox.kt} | 185 +- .../generated/InstructionEvaluationError.kt | 159 + .../InstructionExecutionFail.kt | 11 +- .../generated/InstructionExecutionFailure.kt | 362 + .../iroha2/generated/InstructionType.kt | 611 + .../iroha2/generated/IntervalOfu16.kt | 39 + .../numerical/Interval.kt => IntervalOfu8.kt} | 17 +- ...datorEvent.kt => InvalidParameterError.kt} | 68 +- .../{datamodel/domain => }/IpfsPath.kt | 4 +- .../{primitives/addr => }/Ipv4Addr.kt | 8 +- .../predicate/ipaddr => }/Ipv4Predicate.kt | 14 +- .../{primitives/addr => }/Ipv6Addr.kt | 8 +- .../predicate/ipaddr => }/Ipv6Predicate.kt | 14 +- .../generated/IsAssetDefinitionOwner.kt | 38 + .../generated/{datamodel => }/LengthLimits.kt | 4 +- .../{datamodel/expression => }/Less.kt | 5 +- .../{datamodel/metadata => }/Limits.kt | 4 +- .../soramitsu/iroha2/generated/MathError.kt | 321 + .../{datamodel/metadata => }/Metadata.kt | 6 +- .../generated/MetadataChangedOfAccountId.kt | 43 + .../MetadataChangedOfAssetDefinitionId.kt | 46 + .../generated/MetadataChangedOfAssetId.kt | 43 + ...hanged.kt => MetadataChangedOfDomainId.kt} | 25 +- .../iroha2/generated/MetadataError.kt | 199 + .../generated/{datamodel/isi => }/MintBox.kt | 7 +- .../iroha2/generated/MintabilityError.kt | 116 + .../{datamodel/asset => }/Mintable.kt | 10 +- .../generated/MismatchOfAssetDefinitionId.kt | 40 + .../generated/MismatchOfAssetValueType.kt | 40 + .../iroha2/generated/MismatchOfValue.kt | 38 + .../{datamodel/expression => }/Mod.kt | 5 +- .../{datamodel/expression => }/Multiply.kt | 5 +- .../generated/{datamodel/name => }/Name.kt | 4 +- .../{datamodel/account => }/NewAccount.kt | 6 +- .../asset => }/NewAssetDefinition.kt | 8 +- .../{datamodel/domain => }/NewDomain.kt | 5 +- .../{datamodel/isi => }/NewParameterBox.kt | 12 +- .../generated/{datamodel/role => }/NewRole.kt | 4 +- .../predicate/nontrivial => }/NonTrivial.kt | 20 +- .../{datamodel/expression => }/Not.kt | 4 +- .../generated/{datamodel => }/NumericValue.kt | 10 +- .../iroha2/generated/OptimizedExecutable.kt | 102 + .../{datamodel/expression => }/Or.kt | 4 +- ...Event.kt => OriginFilterOfAccountEvent.kt} | 20 +- .../OriginFilterOfAssetDefinitionEvent.kt | 40 + ...etEvent.kt => OriginFilterOfAssetEvent.kt} | 19 +- ...nEvent.kt => OriginFilterOfDomainEvent.kt} | 19 +- ...eerEvent.kt => OriginFilterOfPeerEvent.kt} | 19 +- ...oleEvent.kt => OriginFilterOfRoleEvent.kt} | 19 +- ...Event.kt => OriginFilterOfTriggerEvent.kt} | 20 +- .../query => }/PaginatedQueryResult.kt | 14 +- .../{datamodel/pagination => }/Pagination.kt | 4 +- .../generated/{datamodel/isi => }/Pair.kt | 16 +- .../generated/{datamodel => }/Parameter.kt | 10 +- .../{datamodel/Id.kt => ParameterId.kt} | 17 +- .../generated/{datamodel/peer => }/Peer.kt | 4 +- .../events/data/events/peer => }/PeerEvent.kt | 5 +- .../data/events/peer => }/PeerEventFilter.kt | 10 +- .../data/events/peer => }/PeerFilter.kt | 18 +- .../generated/{datamodel/peer => }/PeerId.kt | 12 +- .../iroha2/generated/PendingTransactions.kt | 39 + .../events/role => }/PermissionRemoved.kt | 13 +- .../token/Token.kt => PermissionToken.kt} | 24 +- ...nition.kt => PermissionTokenDefinition.kt} | 26 +- .../permission => }/PermissionTokenEvent.kt | 17 +- .../generated/PermissionTokenFindError.kt | 40 + .../token/TokenId.kt => PermissionTokenId.kt} | 17 +- .../EntityKind.kt => PipelineEntityKind.kt} | 23 +- .../events/pipeline => }/PipelineEvent.kt | 17 +- .../pipeline => }/PipelineEventFilter.kt | 17 +- ...onReason.kt => PipelineRejectionReason.kt} | 24 +- .../pipeline/Status.kt => PipelineStatus.kt} | 31 +- .../StatusKind.kt => PipelineStatusKind.kt} | 29 +- .../generated/{crypto => }/PublicKey.kt | 11 +- .../{datamodel/query => }/QueryBox.kt | 543 +- .../Error.kt => QueryExecutionFailure.kt} | 120 +- .../query/Payload.kt => QueryPayload.kt} | 25 +- .../{datamodel/query => }/QueryResult.kt | 5 +- .../{datamodel/expression => }/RaiseTo.kt | 5 +- .../iroha2/generated/RangeInclusive.kt | 43 + .../{core/genesis => }/RawGenesisBlock.kt | 16 +- .../{datamodel/isi => }/RegisterBox.kt | 6 +- .../{datamodel => }/RegistrableBox.kt | 66 +- .../transaction => }/RejectedTransaction.kt | 17 +- .../{datamodel/isi => }/RemoveKeyValueBox.kt | 7 +- .../{datamodel/trigger/action => }/Repeats.kt | 7 +- .../iroha2/generated/RepetitionError.kt | 38 + .../{datamodel/isi => }/RevokeBox.kt | 7 +- .../generated/{datamodel/role => }/Role.kt | 13 +- .../events/data/events/role => }/RoleEvent.kt | 22 +- .../data/events/role => }/RoleEventFilter.kt | 13 +- .../data/events/role => }/RoleFilter.kt | 18 +- .../generated/{datamodel/role => }/RoleId.kt | 5 +- .../{datamodel/events/time => }/Schedule.kt | 5 +- .../iroha2/generated/SemiIntervalOfFixed.kt | 38 + .../iroha2/generated/SemiIntervalOfu128.kt | 39 + .../SemiInterval.kt => SemiIntervalOfu32.kt} | 17 +- .../predicate/numerical => }/SemiRange.kt | 25 +- .../{datamodel/isi => }/SequenceBox.kt | 10 +- .../{datamodel/isi => }/SetKeyValueBox.kt | 8 +- .../{datamodel/isi => }/SetParameterBox.kt | 12 +- .../{crypto/signature => }/Signature.kt | 5 +- .../account => }/SignatureCheckCondition.kt | 9 +- .../{crypto/signature => }/SignatureOf.kt | 5 +- .../generated/SignaturesOfOfCommittedBlock.kt | 48 + .../SignaturesOfOfTransactionPayload.kt | 48 + .../soramitsu/iroha2/generated/SignedQuery.kt | 38 + .../transaction => }/SignedTransaction.kt | 24 +- .../soramitsu/iroha2/generated/SizeError.kt | 39 + .../soramitsu/iroha2/generated/SocketAddr.kt | 127 + .../NotPermittedFail.kt => SocketAddrHost.kt} | 24 +- .../iroha2/generated/SocketAddrV4.kt | 39 + .../iroha2/generated/SocketAddrV6.kt | 39 + .../{datamodel/sorting => }/Sorting.kt | 5 +- .../predicate/string => }/StringPredicate.kt | 4 +- .../{datamodel/expression => }/Subtract.kt | 5 +- .../{datamodel/events/time => }/TimeEvent.kt | 16 +- .../events/time => }/TimeEventFilter.kt | 4 +- .../time/Interval.kt => TimeInterval.kt} | 17 +- .../iroha2/generated/TransactionExpired.kt | 36 + .../transaction => }/TransactionLimitError.kt | 8 +- .../transaction => }/TransactionLimits.kt | 4 +- .../Payload.kt => TransactionPayload.kt} | 19 +- .../TransactionQueryResult.kt | 5 +- .../TransactionRejectionReason.kt | 160 +- .../transaction => }/TransactionValue.kt | 4 +- .../{datamodel/isi => }/TransferBox.kt | 7 +- .../soramitsu/iroha2/generated/TriggerBox.kt | 101 + .../data/events/trigger => }/TriggerEvent.kt | 5 +- .../events/trigger => }/TriggerEventFilter.kt | 16 +- .../data/events/trigger => }/TriggerFilter.kt | 18 +- .../{datamodel/trigger => }/TriggerId.kt | 6 +- .../TriggerNumberOfExecutionsChanged.kt | 7 +- .../TriggerOfFilterBoxAndExecutable.kt | 41 + ...riggerOfFilterBoxAndOptimizedExecutable.kt | 44 + .../soramitsu/iroha2/generated/TypeError.kt | 127 + .../{datamodel/isi => }/UnregisterBox.kt | 6 +- .../UnsatisfiedSignatureConditionFail.kt | 5 +- .../iroha2/generated/UpgradableBox.kt | 69 + .../soramitsu/iroha2/generated/UpgradeBox.kt | 35 + .../transaction => }/ValidTransaction.kt | 17 +- .../iroha2/generated/ValidationFail.kt | 204 + .../permission/validator => }/Validator.kt | 11 +- .../iroha2/generated/ValidatorEvent.kt | 83 + .../RawVersioned.kt => ValidatorMode.kt} | 57 +- .../iroha2/generated/{datamodel => }/Value.kt | 126 +- .../generated/{datamodel => }/ValueKind.kt | 79 +- .../predicate/value => }/ValueOfKey.kt | 5 +- .../predicate/value => }/ValuePredicate.kt | 28 +- .../stream => }/VersionedBlockMessage.kt | 4 +- .../VersionedBlockSubscriptionRequest.kt | 4 +- .../block => }/VersionedCommittedBlock.kt | 4 +- .../events => }/VersionedEventMessage.kt | 4 +- .../VersionedEventSubscriptionRequest.kt | 4 +- .../VersionedPaginatedQueryResult.kt | 4 +- ...ock.kt => VersionedPendingTransactions.kt} | 24 +- .../VersionedRejectedTransaction.kt | 4 +- ...ueryRequest.kt => VersionedSignedQuery.kt} | 28 +- .../VersionedSignedTransaction.kt | 4 +- .../VersionedValidTransaction.kt | 4 +- .../transaction => }/WasmExecutionFail.kt | 4 +- .../iroha2/generated/WasmInternalRepr.kt | 39 + .../transaction => }/WasmSmartContract.kt | 8 +- .../{datamodel/expression => }/Where.kt | 6 +- .../generated/core/block/CandidateBlock.kt | 60 - .../core/genesis/GenesisTransaction.kt | 40 - .../core/sumeragi/networktopology/Topology.kt | 40 - .../crypto/signature/SignaturesOf.kt | 45 - .../datamodel/asset/AssetDefinitionEntry.kt | 39 - .../datamodel/blockvalue/BlockHeaderValue.kt | 61 - .../datamodel/blockvalue/BlockValue.kt | 57 - .../FilterOptOriginFilterAssetEvent.kt | 115 - .../FilterOptOriginFilterDomainEvent.kt | 115 - .../filters/FilterOptOriginFilterPeerEvent.kt | 115 - .../filters/FilterOptOriginFilterRoleEvent.kt | 115 - .../OriginFilterAssetDefinitionEvent.kt | 40 - .../permission/validator/ValidatorId.kt | 40 - .../datamodel/query/SignedQueryRequest.kt | 39 - .../generated/datamodel/trigger/Trigger.kt | 42 - .../datamodel/trigger/action/Action.kt | 54 - .../generated/version/UnsupportedVersion.kt | 39 - .../iroha2/generated/version/error/Error.kt | 345 - .../iroha2/testengine/IrohaConfig.kt | 6 +- .../iroha2/testengine/IrohaContainer.kt | 42 +- .../iroha2/testengine/IrohaRunnerExtension.kt | 6 +- .../soramitsu/iroha2/testengine/IrohaTest.kt | 4 +- .../soramitsu/iroha2/testengine/WithIroha.kt | 7 +- .../test-tools/src/main/resources/config.json | 32 +- .../src/main/resources/genesis.json | 622 +- .../src/main/resources/validator.wasm | Bin 0 -> 597151 bytes .../src/test/resources/logback-test.xml | 27 + test-smart-contract/Cargo.toml | 3 +- test-smart-contract/src/lib.rs | 29 +- 373 files changed, 13812 insertions(+), 11951 deletions(-) create mode 100644 modules/admin-client/src/test/resources/logback-test.xml create mode 100644 modules/client/src/test/resources/logback-test.xml create mode 100644 modules/client/src/test/resources/validator.wasm create mode 100644 modules/codegen/src/main/kotlin/jp/co/soramitsu/iroha2/Regex.kt delete mode 100644 modules/codegen/src/test/kotlin/jp/co/soramitsu/iroha2/parse/SchemaParserTest.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/account => }/Account.kt (79%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/account => }/AccountEvent.kt (92%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/account => }/AccountEventFilter.kt (87%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/account => }/AccountFilter.kt (51%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/account => }/AccountId.kt (79%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/account => }/AccountPermissionChanged.kt (69%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/account => }/AccountRoleChanged.kt (77%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/ActionOfFilterBoxAndExecutable.kt create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/ActionOfFilterBoxAndOptimizedExecutable.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/expression => }/Add.kt (84%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/permission/validator/Type.kt => Algorithm.kt} (54%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/expression => }/And.kt (89%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/asset => }/Asset.kt (88%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/asset => }/AssetChanged.kt (77%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/asset => }/AssetDefinition.kt (76%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/asset => }/AssetDefinitionEvent.kt (88%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/asset => }/AssetDefinitionEventFilter.kt (91%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/asset => }/AssetDefinitionFilter.kt (50%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/asset => }/AssetDefinitionId.kt (80%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/asset => }/AssetDefinitionOwnerChanged.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/asset => }/AssetDefinitionTotalQuantityChanged.kt (79%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/asset => }/AssetEvent.kt (89%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/asset => }/AssetEventFilter.kt (90%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/asset => }/AssetFilter.kt (51%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/asset => }/AssetId.kt (84%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/asset => }/AssetValue.kt (91%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/asset => }/AssetValueType.kt (91%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/predicate/value => }/AtIndex.kt (87%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/BinaryOpIncompatibleNumericValueTypesError.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{core/block => }/BlockHeader.kt (80%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{core/block/stream => }/BlockMessage.kt (82%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/transaction => }/BlockRejectionReason.kt (92%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{core/block/stream => }/BlockSubscriptionRequest.kt (87%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/isi => }/BurnBox.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{core/block => }/CommittedBlock.kt (76%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/isi/If.kt => Conditional.kt} (51%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/config => }/ConfigurationEvent.kt (87%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/predicate/value => }/Container.kt (86%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/expression => }/Contains.kt (85%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/expression => }/ContainsAll.kt (85%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/expression => }/ContainsAny.kt (85%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/expression => }/ContextValue.kt (82%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/EntityFilter.kt => DataEntityFilter.kt} (76%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/Event.kt => DataEvent.kt} (81%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/expression => }/Divide.kt (84%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/DoesAccountHavePermissionToken.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/domain => }/Domain.kt (73%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/domain => }/DomainEvent.kt (85%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/domain => }/DomainEventFilter.kt (84%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/domain => }/DomainFilter.kt (51%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/domain => }/DomainId.kt (82%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/expression => }/Equal.kt (85%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/expression => }/EvaluatesTo.kt (84%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/EvaluationError.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events => }/Event.kt (87%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events => }/EventMessage.kt (87%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events => }/EventSubscriptionRequest.kt (75%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/transaction => }/Executable.kt (91%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/isi => }/ExecuteTriggerBox.kt (71%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/executetrigger => }/ExecuteTriggerEvent.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/executetrigger => }/ExecuteTriggerEventFilter.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/time => }/ExecutionTime.kt (84%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/expression => }/Expression.kt (75%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/isi => }/FailBox.kt (88%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/EventsFilterBox.kt => FilterBox.kt} (81%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/FilterOptAccountEventFilter.kt => FilterOptOfAccountEventFilter.kt} (80%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/FilterOptAccountFilter.kt => FilterOptOfAccountFilter.kt} (81%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/FilterOptAssetDefinitionEventFilter.kt => FilterOptOfAssetDefinitionEventFilter.kt} (80%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/FilterOptAssetDefinitionFilter.kt => FilterOptOfAssetDefinitionFilter.kt} (80%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/FilterOptAssetEventFilter.kt => FilterOptOfAssetEventFilter.kt} (81%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/FilterOptAssetFilter.kt => FilterOptOfAssetFilter.kt} (80%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/FilterOptEntityFilter.kt => FilterOptOfDataEntityFilter.kt} (79%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/FilterOptDomainEventFilter.kt => FilterOptOfDomainEventFilter.kt} (81%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/FilterOptDomainFilter.kt => FilterOptOfDomainFilter.kt} (81%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/FilterOptOriginFilterAssetDefinitionEvent.kt => FilterOptOfOriginFilterOfAccountEvent.kt} (75%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/FilterOptOriginFilterAccountEvent.kt => FilterOptOfOriginFilterOfAssetDefinitionEvent.kt} (72%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/FilterOptOriginFilterTriggerEvent.kt => FilterOptOfOriginFilterOfAssetEvent.kt} (71%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/FilterOptOfOriginFilterOfDomainEvent.kt create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/FilterOptOfOriginFilterOfPeerEvent.kt create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/FilterOptOfOriginFilterOfRoleEvent.kt create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/FilterOptOfOriginFilterOfTriggerEvent.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/FilterOptPeerEventFilter.kt => FilterOptOfPeerEventFilter.kt} (81%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/FilterOptPeerFilter.kt => FilterOptOfPeerFilter.kt} (80%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/FilterOptRoleEventFilter.kt => FilterOptOfRoleEventFilter.kt} (81%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/FilterOptRoleFilter.kt => FilterOptOfRoleFilter.kt} (80%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/FilterOptTriggerEventFilter.kt => FilterOptOfTriggerEventFilter.kt} (80%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/FilterOptTriggerFilter.kt => FilterOptOfTriggerFilter.kt} (81%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/account => }/FindAccountById.kt (77%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/account => }/FindAccountKeyValueByIdAndKey.kt (77%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/account => }/FindAccountsByDomainId.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/account => }/FindAccountsByName.kt (77%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/account => }/FindAccountsWithAsset.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/account => }/FindAllAccounts.kt (80%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/trigger => }/FindAllActiveTriggerIds.kt (80%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/asset => }/FindAllAssets.kt (81%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/asset => }/FindAllAssetsDefinitions.kt (80%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/block => }/FindAllBlockHeaders.kt (80%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/block => }/FindAllBlocks.kt (81%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/domain => }/FindAllDomains.kt (80%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/peer => }/FindAllParameters.kt (81%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/peer => }/FindAllPeers.kt (81%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/permissions => }/FindAllPermissionTokenDefinitions.kt (79%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/role => }/FindAllRoleIds.kt (81%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/role => }/FindAllRoles.kt (81%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/transaction => }/FindAllTransactions.kt (79%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/asset => }/FindAssetById.kt (77%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/asset => }/FindAssetDefinitionById.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/asset => }/FindAssetDefinitionKeyValueByIdAndKey.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/asset => }/FindAssetKeyValueByIdAndKey.kt (77%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/asset => }/FindAssetQuantityById.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/asset => }/FindAssetsByAccountId.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/asset => }/FindAssetsByAssetDefinitionId.kt (79%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/asset => }/FindAssetsByDomainId.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/asset => }/FindAssetsByDomainIdAndAssetDefinitionId.kt (79%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/asset => }/FindAssetsByName.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/block => }/FindBlockHeaderByHash.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/domain => }/FindDomainById.kt (77%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/domain => }/FindDomainKeyValueByIdAndKey.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{core/smartcontracts/isi/error => }/FindError.kt (81%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/permissions => }/FindPermissionTokensByAccountId.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/role => }/FindRoleByRoleId.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/role => }/FindRolesByAccountId.kt (77%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/asset => }/FindTotalAssetQuantityByAssetDefinitionId.kt (79%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/transaction => }/FindTransactionByHash.kt (77%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/transaction => }/FindTransactionsByAccountId.kt (77%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/trigger => }/FindTriggerById.kt (77%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/trigger => }/FindTriggerKeyValueByIdAndKey.kt (77%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/trigger => }/FindTriggersByDomainId.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{primitives/fixed => }/Fixed.kt (80%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/predicate/GenericValuePredicateBox.kt => GenericPredicateBox.kt} (74%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/isi => }/GrantBox.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/expression => }/Greater.kt (84%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{crypto/hash => }/Hash.kt (80%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{crypto/hash => }/HashOf.kt (83%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel => }/IdBox.kt (69%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel => }/IdentifiableBox.kt (70%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/expression => }/If.kt (72%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/isi/Instruction.kt => InstructionBox.kt} (83%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/InstructionEvaluationError.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/transaction => }/InstructionExecutionFail.kt (74%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/InstructionExecutionFailure.kt create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/InstructionType.kt create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/IntervalOfu16.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/predicate/numerical/Interval.kt => IntervalOfu8.kt} (68%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/permission/PermissionValidatorEvent.kt => InvalidParameterError.kt} (55%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/domain => }/IpfsPath.kt (88%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{primitives/addr => }/Ipv4Addr.kt (80%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/predicate/ipaddr => }/Ipv4Predicate.kt (64%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{primitives/addr => }/Ipv6Addr.kt (82%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/predicate/ipaddr => }/Ipv6Predicate.kt (64%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/IsAssetDefinitionOwner.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel => }/LengthLimits.kt (89%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/expression => }/Less.kt (84%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/metadata => }/Limits.kt (88%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/MathError.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/metadata => }/Metadata.kt (83%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/MetadataChangedOfAccountId.kt create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/MetadataChangedOfAssetDefinitionId.kt create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/MetadataChangedOfAssetId.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/MetadataChanged.kt => MetadataChangedOfDomainId.kt} (59%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/MetadataError.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/isi => }/MintBox.kt (78%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/MintabilityError.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/asset => }/Mintable.kt (91%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/MismatchOfAssetDefinitionId.kt create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/MismatchOfAssetValueType.kt create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/MismatchOfValue.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/expression => }/Mod.kt (84%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/expression => }/Multiply.kt (84%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/name => }/Name.kt (88%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/account => }/NewAccount.kt (85%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/asset => }/NewAssetDefinition.kt (84%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/domain => }/NewDomain.kt (85%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/isi => }/NewParameterBox.kt (64%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/role => }/NewRole.kt (87%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/predicate/nontrivial => }/NonTrivial.kt (55%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/expression => }/Not.kt (87%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel => }/NumericValue.kt (92%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/OptimizedExecutable.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/expression => }/Or.kt (89%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/OriginFilterAccountEvent.kt => OriginFilterOfAccountEvent.kt} (59%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/OriginFilterOfAssetDefinitionEvent.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/OriginFilterAssetEvent.kt => OriginFilterOfAssetEvent.kt} (59%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/OriginFilterDomainEvent.kt => OriginFilterOfDomainEvent.kt} (60%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/OriginFilterPeerEvent.kt => OriginFilterOfPeerEvent.kt} (59%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/OriginFilterRoleEvent.kt => OriginFilterOfRoleEvent.kt} (59%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/filters/OriginFilterTriggerEvent.kt => OriginFilterOfTriggerEvent.kt} (59%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query => }/PaginatedQueryResult.kt (67%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/pagination => }/Pagination.kt (88%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/isi => }/Pair.kt (64%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel => }/Parameter.kt (80%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/Id.kt => ParameterId.kt} (64%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/peer => }/Peer.kt (87%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/peer => }/PeerEvent.kt (93%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/peer => }/PeerEventFilter.kt (89%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/peer => }/PeerFilter.kt (51%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/peer => }/PeerId.kt (72%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/PendingTransactions.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/role => }/PermissionRemoved.kt (67%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/permission/token/Token.kt => PermissionToken.kt} (62%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/permission/token/Definition.kt => PermissionTokenDefinition.kt} (62%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/permission => }/PermissionTokenEvent.kt (84%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/PermissionTokenFindError.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/permission/token/TokenId.kt => PermissionTokenId.kt} (59%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/pipeline/EntityKind.kt => PipelineEntityKind.kt} (82%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/pipeline => }/PipelineEvent.kt (66%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/pipeline => }/PipelineEventFilter.kt (63%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/transaction/RejectionReason.kt => PipelineRejectionReason.kt} (84%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/pipeline/Status.kt => PipelineStatus.kt} (81%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/pipeline/StatusKind.kt => PipelineStatusKind.kt} (82%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{crypto => }/PublicKey.kt (76%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query => }/QueryBox.kt (70%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{core/smartcontracts/isi/query/Error.kt => QueryExecutionFailure.kt} (62%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/Payload.kt => QueryPayload.kt} (57%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query => }/QueryResult.kt (83%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/expression => }/RaiseTo.kt (84%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/RangeInclusive.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{core/genesis => }/RawGenesisBlock.kt (62%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/isi => }/RegisterBox.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel => }/RegistrableBox.kt (75%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/transaction => }/RejectedTransaction.kt (67%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/isi => }/RemoveKeyValueBox.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/trigger/action => }/Repeats.kt (93%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/RepetitionError.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/isi => }/RevokeBox.kt (78%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/role => }/Role.kt (73%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/role => }/RoleEvent.kt (81%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/role => }/RoleEventFilter.kt (89%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/role => }/RoleFilter.kt (51%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/role => }/RoleId.kt (82%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/time => }/Schedule.kt (84%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/SemiIntervalOfFixed.kt create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/SemiIntervalOfu128.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/predicate/numerical/SemiInterval.kt => SemiIntervalOfu32.kt} (66%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/predicate/numerical => }/SemiRange.kt (79%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/isi => }/SequenceBox.kt (80%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/isi => }/SetKeyValueBox.kt (77%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/isi => }/SetParameterBox.kt (64%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{crypto/signature => }/Signature.kt (85%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/account => }/SignatureCheckCondition.kt (75%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{crypto/signature => }/SignatureOf.kt (85%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/SignaturesOfOfCommittedBlock.kt create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/SignaturesOfOfTransactionPayload.kt create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/SignedQuery.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/transaction => }/SignedTransaction.kt (51%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/SizeError.kt create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/SocketAddr.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/transaction/NotPermittedFail.kt => SocketAddrHost.kt} (50%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/SocketAddrV4.kt create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/SocketAddrV6.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/sorting => }/Sorting.kt (83%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/predicate/string => }/StringPredicate.kt (97%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/expression => }/Subtract.kt (84%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/time => }/TimeEvent.kt (64%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/time => }/TimeEventFilter.kt (87%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/time/Interval.kt => TimeInterval.kt} (64%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/TransactionExpired.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/transaction => }/TransactionLimitError.kt (79%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/transaction => }/TransactionLimits.kt (88%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/transaction/Payload.kt => TransactionPayload.kt} (75%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/transaction => }/TransactionQueryResult.kt (84%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/transaction => }/TransactionRejectionReason.kt (70%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/transaction => }/TransactionValue.kt (96%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/isi => }/TransferBox.kt (80%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/TriggerBox.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/trigger => }/TriggerEvent.kt (95%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/trigger => }/TriggerEventFilter.kt (88%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/trigger => }/TriggerFilter.kt (51%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/trigger => }/TriggerId.kt (80%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events/data/events/trigger => }/TriggerNumberOfExecutionsChanged.kt (82%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/TriggerOfFilterBoxAndExecutable.kt create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/TriggerOfFilterBoxAndOptimizedExecutable.kt create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/TypeError.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/isi => }/UnregisterBox.kt (79%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/transaction => }/UnsatisfiedSignatureConditionFail.kt (87%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/UpgradableBox.kt create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/UpgradeBox.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/transaction => }/ValidTransaction.kt (62%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/ValidationFail.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/permission/validator => }/Validator.kt (65%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/ValidatorEvent.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{version/RawVersioned.kt => ValidatorMode.kt} (64%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel => }/Value.kt (82%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel => }/ValueKind.kt (88%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/predicate/value => }/ValueOfKey.kt (83%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/predicate/value => }/ValuePredicate.kt (87%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{core/block/stream => }/VersionedBlockMessage.kt (94%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{core/block/stream => }/VersionedBlockSubscriptionRequest.kt (94%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{core/block => }/VersionedCommittedBlock.kt (94%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events => }/VersionedEventMessage.kt (94%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/events => }/VersionedEventSubscriptionRequest.kt (94%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query => }/VersionedPaginatedQueryResult.kt (94%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{core/block/VersionedCandidateBlock.kt => VersionedPendingTransactions.kt} (75%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/transaction => }/VersionedRejectedTransaction.kt (93%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/query/VersionedSignedQueryRequest.kt => VersionedSignedQuery.kt} (70%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/transaction => }/VersionedSignedTransaction.kt (94%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/transaction => }/VersionedValidTransaction.kt (94%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/transaction => }/WasmExecutionFail.kt (87%) create mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/WasmInternalRepr.kt rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/transaction => }/WasmSmartContract.kt (79%) rename modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/{datamodel/expression => }/Where.kt (85%) delete mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/core/block/CandidateBlock.kt delete mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/core/genesis/GenesisTransaction.kt delete mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/core/sumeragi/networktopology/Topology.kt delete mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/crypto/signature/SignaturesOf.kt delete mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/datamodel/asset/AssetDefinitionEntry.kt delete mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/datamodel/blockvalue/BlockHeaderValue.kt delete mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/datamodel/blockvalue/BlockValue.kt delete mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/datamodel/events/data/filters/FilterOptOriginFilterAssetEvent.kt delete mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/datamodel/events/data/filters/FilterOptOriginFilterDomainEvent.kt delete mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/datamodel/events/data/filters/FilterOptOriginFilterPeerEvent.kt delete mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/datamodel/events/data/filters/FilterOptOriginFilterRoleEvent.kt delete mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/datamodel/events/data/filters/OriginFilterAssetDefinitionEvent.kt delete mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/datamodel/permission/validator/ValidatorId.kt delete mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/datamodel/query/SignedQueryRequest.kt delete mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/datamodel/trigger/Trigger.kt delete mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/datamodel/trigger/action/Action.kt delete mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/version/UnsupportedVersion.kt delete mode 100644 modules/model/src/main/kotlin/jp/co/soramitsu/iroha2/generated/version/error/Error.kt create mode 100644 modules/test-tools/src/main/resources/validator.wasm create mode 100644 modules/test-tools/src/test/resources/logback-test.xml diff --git a/docker-compose/docker-compose.yaml b/docker-compose/docker-compose.yaml index 8ef7a0bed..c8e36a8ae 100644 --- a/docker-compose/docker-compose.yaml +++ b/docker-compose/docker-compose.yaml @@ -2,7 +2,7 @@ version: "3.8" services: iroha: - image: hyperledger/iroha2:dev-nightly-94aebf4ee05d8527eac8866898c629cc66b8b9dc + image: hyperledger/iroha2:dev-nightly-0a9fc8ede7a126da87628ae306a845a29f89094c environment: TORII_P2P_ADDR: iroha:1337 TORII_API_URL: iroha:8080 @@ -21,7 +21,7 @@ services: command: iroha --submit-genesis iroha1: - image: hyperledger/iroha2:dev-nightly-94aebf4ee05d8527eac8866898c629cc66b8b9dc + image: hyperledger/iroha2:dev-nightly-0a9fc8ede7a126da87628ae306a845a29f89094c environment: TORII_P2P_ADDR: iroha1:1338 TORII_API_URL: iroha1:8081 @@ -39,7 +39,7 @@ services: - "../modules/test-tools/src/main/resources:/config" iroha2: - image: hyperledger/iroha2:dev-nightly-94aebf4ee05d8527eac8866898c629cc66b8b9dc + image: hyperledger/iroha2:dev-nightly-0a9fc8ede7a126da87628ae306a845a29f89094c environment: TORII_P2P_ADDR: iroha2:1339 TORII_API_URL: iroha2:8082 @@ -57,7 +57,7 @@ services: - "../modules/test-tools/src/main/resources:/config" iroha3: - image: hyperledger/iroha2:dev-nightly-94aebf4ee05d8527eac8866898c629cc66b8b9dc + image: hyperledger/iroha2:dev-nightly-0a9fc8ede7a126da87628ae306a845a29f89094c environment: TORII_P2P_ADDR: iroha3:1340 TORII_API_URL: iroha3:8083 diff --git a/gradle.properties b/gradle.properties index 91e3a75ea..2040d1d76 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,8 @@ # kotlin -kotlinVer=1.6.0 +kotlinVer=1.8.22 kotlinLinterVer=3.9.0 ktorVer=2.0.1 -coroutinesVer=1.6.0 +coroutinesVer=1.7.1 # json serde jacksonKotlinVer=2.13.0 # codegen diff --git a/modules/admin-client/src/test/resources/logback-test.xml b/modules/admin-client/src/test/resources/logback-test.xml new file mode 100644 index 000000000..ed8e3bbab --- /dev/null +++ b/modules/admin-client/src/test/resources/logback-test.xml @@ -0,0 +1,15 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + + \ No newline at end of file diff --git a/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Genesis.kt b/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Genesis.kt index 7bf62335b..1f793df3d 100644 --- a/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Genesis.kt +++ b/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Genesis.kt @@ -1,17 +1,17 @@ package jp.co.soramitsu.iroha2 -import jp.co.soramitsu.iroha2.generated.core.genesis.GenesisTransaction -import jp.co.soramitsu.iroha2.generated.core.genesis.RawGenesisBlock -import jp.co.soramitsu.iroha2.generated.datamodel.IdentifiableBox -import jp.co.soramitsu.iroha2.generated.datamodel.RegistrableBox -import jp.co.soramitsu.iroha2.generated.datamodel.Value -import jp.co.soramitsu.iroha2.generated.datamodel.account.NewAccount -import jp.co.soramitsu.iroha2.generated.datamodel.asset.NewAssetDefinition -import jp.co.soramitsu.iroha2.generated.datamodel.domain.NewDomain -import jp.co.soramitsu.iroha2.generated.datamodel.expression.Expression -import jp.co.soramitsu.iroha2.generated.datamodel.isi.Instruction -import jp.co.soramitsu.iroha2.generated.datamodel.isi.RegisterBox -import jp.co.soramitsu.iroha2.generated.datamodel.metadata.Metadata +import jp.co.soramitsu.iroha2.generated.Expression +import jp.co.soramitsu.iroha2.generated.IdentifiableBox +import jp.co.soramitsu.iroha2.generated.InstructionBox +import jp.co.soramitsu.iroha2.generated.Metadata +import jp.co.soramitsu.iroha2.generated.NewAccount +import jp.co.soramitsu.iroha2.generated.NewAssetDefinition +import jp.co.soramitsu.iroha2.generated.NewDomain +import jp.co.soramitsu.iroha2.generated.RawGenesisBlock +import jp.co.soramitsu.iroha2.generated.RegisterBox +import jp.co.soramitsu.iroha2.generated.RegistrableBox +import jp.co.soramitsu.iroha2.generated.ValidatorMode +import jp.co.soramitsu.iroha2.generated.Value import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardOpenOption @@ -19,8 +19,7 @@ import java.nio.file.StandardOpenOption /** * Genesis block is used to initialise a blockchain */ -open class Genesis(open val genesisBlock: RawGenesisBlock) { - +open class Genesis(open val block: RawGenesisBlock) { /** * Write genesis to file */ @@ -34,31 +33,35 @@ open class Genesis(open val genesisBlock: RawGenesisBlock) { /** * Represent genesis as JSON */ - fun asJson(): String = JSON_SERDE.writeValueAsString(this.genesisBlock) + fun asJson(): String = JSON_SERDE.writeValueAsString(this.block) companion object { + val validatorMode = this::class.java.classLoader.getResource("validator.wasm") + ?.let { ValidatorMode.Path("validator.wasm") } + ?: throw IrohaSdkException("validator.wasm not found") + /** * Return empty genesis */ - fun getEmpty() = Genesis(RawGenesisBlock(listOf(GenesisTransaction(listOf())))) + fun getEmpty() = Genesis(RawGenesisBlock(listOf(listOf()), validatorMode)) /** * List of genesis blocks to single block with unique instructions */ fun List.toSingle(): Genesis { - val uniqueIsi: MutableSet = mutableSetOf() + val uniqueIsi: MutableSet = mutableSetOf() this.forEach { genesis -> - uniqueIsi.addAll(genesis.genesisBlock.transactions.map { it.isi }.flatten()) + uniqueIsi.addAll(genesis.block.transactions.flatten()) } - return Genesis(RawGenesisBlock(listOf(GenesisTransaction(uniqueIsi.mergeMetadata())))) + return Genesis(RawGenesisBlock(listOf(uniqueIsi.mergeMetadata()), validatorMode)) } - private fun MutableSet.mergeMetadata(): List { + private fun MutableSet.mergeMetadata(): List { val metadataMap = mutableMapOf() - // only for Instruction.Register + // only for InstructionBox.Register this.extractIdentifiableBoxes().forEach { idBox -> metadataMap.putMergedMetadata(idBox) } @@ -68,16 +71,16 @@ open class Genesis(open val genesisBlock: RawGenesisBlock) { val idBox = toReplace.first().extractIdentifiableBox() val registrableBox = idBox?.toRegisterBox(metadata) ?: throw RuntimeException("IdentifiableBox shouldn't be null") - this.add(Instruction.Register(RegisterBox(registrableBox.evaluatesTo()))) + this.add(InstructionBox.Register(RegisterBox(registrableBox.evaluatesTo()))) } return this.sorted() } - private fun MutableSet.sorted() = this.sortedWith( + private fun MutableSet.sorted() = this.sortedWith( compareByDescending { instruction -> when (instruction) { - is Instruction.Register -> when (instruction.extractIdentifiableBox()) { + is InstructionBox.Register -> when (instruction.extractIdentifiableBox()) { is IdentifiableBox.NewDomain -> 6 is IdentifiableBox.NewAccount -> 5 is IdentifiableBox.NewAssetDefinition -> 4 @@ -105,7 +108,7 @@ open class Genesis(open val genesisBlock: RawGenesisBlock) { this.newAssetDefinition.id, this.newAssetDefinition.valueType, this.newAssetDefinition.mintable, - metadata + metadata = metadata ) ) @@ -140,14 +143,14 @@ open class Genesis(open val genesisBlock: RawGenesisBlock) { } } - private fun MutableSet.findIsiToReplace( + private fun MutableSet.findIsiToReplace( metadata: Map - ): MutableMap> { - val isiToReplace = mutableMapOf>() + ): MutableMap> { + val isiToReplace = mutableMapOf>() this.forEach { instruction -> runCatching { - instruction.cast() + instruction.cast() .registerBox.`object`.expression .cast().value .cast().identifiableBox diff --git a/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt b/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt index e0834cf02..a002d3f2c 100644 --- a/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt +++ b/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt @@ -17,56 +17,56 @@ import com.fasterxml.jackson.module.kotlin.KotlinFeature import com.fasterxml.jackson.module.kotlin.KotlinModule import io.ipfs.multihash.Multihash import jp.co.soramitsu.iroha2.DigestFunction.Ed25519 -import jp.co.soramitsu.iroha2.generated.crypto.PublicKey -import jp.co.soramitsu.iroha2.generated.crypto.hash.Hash -import jp.co.soramitsu.iroha2.generated.datamodel.IdBox -import jp.co.soramitsu.iroha2.generated.datamodel.IdentifiableBox -import jp.co.soramitsu.iroha2.generated.datamodel.LengthLimits -import jp.co.soramitsu.iroha2.generated.datamodel.NumericValue -import jp.co.soramitsu.iroha2.generated.datamodel.RegistrableBox -import jp.co.soramitsu.iroha2.generated.datamodel.Value -import jp.co.soramitsu.iroha2.generated.datamodel.ValueKind -import jp.co.soramitsu.iroha2.generated.datamodel.account.AccountId -import jp.co.soramitsu.iroha2.generated.datamodel.account.NewAccount -import jp.co.soramitsu.iroha2.generated.datamodel.account.SignatureCheckCondition -import jp.co.soramitsu.iroha2.generated.datamodel.asset.Asset -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetDefinitionId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetValue -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetValueType -import jp.co.soramitsu.iroha2.generated.datamodel.asset.Mintable -import jp.co.soramitsu.iroha2.generated.datamodel.asset.NewAssetDefinition -import jp.co.soramitsu.iroha2.generated.datamodel.blockvalue.BlockHeaderValue -import jp.co.soramitsu.iroha2.generated.datamodel.blockvalue.BlockValue -import jp.co.soramitsu.iroha2.generated.datamodel.domain.DomainId -import jp.co.soramitsu.iroha2.generated.datamodel.domain.NewDomain -import jp.co.soramitsu.iroha2.generated.datamodel.events.EventsFilterBox -import jp.co.soramitsu.iroha2.generated.datamodel.expression.EvaluatesTo -import jp.co.soramitsu.iroha2.generated.datamodel.expression.Expression -import jp.co.soramitsu.iroha2.generated.datamodel.isi.BurnBox -import jp.co.soramitsu.iroha2.generated.datamodel.isi.GrantBox -import jp.co.soramitsu.iroha2.generated.datamodel.isi.Instruction -import jp.co.soramitsu.iroha2.generated.datamodel.isi.MintBox -import jp.co.soramitsu.iroha2.generated.datamodel.isi.RegisterBox -import jp.co.soramitsu.iroha2.generated.datamodel.isi.SetKeyValueBox -import jp.co.soramitsu.iroha2.generated.datamodel.metadata.Limits -import jp.co.soramitsu.iroha2.generated.datamodel.metadata.Metadata -import jp.co.soramitsu.iroha2.generated.datamodel.name.Name -import jp.co.soramitsu.iroha2.generated.datamodel.peer.Peer -import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.Definition -import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.Token -import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.TokenId -import jp.co.soramitsu.iroha2.generated.datamodel.permission.validator.Validator -import jp.co.soramitsu.iroha2.generated.datamodel.role.NewRole -import jp.co.soramitsu.iroha2.generated.datamodel.role.Role -import jp.co.soramitsu.iroha2.generated.datamodel.role.RoleId -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.TransactionLimits -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.TransactionQueryResult -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.TransactionValue -import jp.co.soramitsu.iroha2.generated.datamodel.trigger.Trigger -import jp.co.soramitsu.iroha2.generated.datamodel.trigger.TriggerId -import jp.co.soramitsu.iroha2.generated.primitives.addr.Ipv4Addr -import jp.co.soramitsu.iroha2.generated.primitives.addr.Ipv6Addr +import jp.co.soramitsu.iroha2.generated.AccountId +import jp.co.soramitsu.iroha2.generated.Algorithm +import jp.co.soramitsu.iroha2.generated.Asset +import jp.co.soramitsu.iroha2.generated.AssetDefinitionId +import jp.co.soramitsu.iroha2.generated.AssetId +import jp.co.soramitsu.iroha2.generated.AssetValue +import jp.co.soramitsu.iroha2.generated.AssetValueType +import jp.co.soramitsu.iroha2.generated.BlockHeader +import jp.co.soramitsu.iroha2.generated.BurnBox +import jp.co.soramitsu.iroha2.generated.DomainId +import jp.co.soramitsu.iroha2.generated.EvaluatesTo +import jp.co.soramitsu.iroha2.generated.Expression +import jp.co.soramitsu.iroha2.generated.GrantBox +import jp.co.soramitsu.iroha2.generated.Hash +import jp.co.soramitsu.iroha2.generated.IdBox +import jp.co.soramitsu.iroha2.generated.IdentifiableBox +import jp.co.soramitsu.iroha2.generated.InstructionBox +import jp.co.soramitsu.iroha2.generated.Ipv4Addr +import jp.co.soramitsu.iroha2.generated.Ipv6Addr +import jp.co.soramitsu.iroha2.generated.LengthLimits +import jp.co.soramitsu.iroha2.generated.Limits +import jp.co.soramitsu.iroha2.generated.Metadata +import jp.co.soramitsu.iroha2.generated.MintBox +import jp.co.soramitsu.iroha2.generated.Mintable +import jp.co.soramitsu.iroha2.generated.Name +import jp.co.soramitsu.iroha2.generated.NewAccount +import jp.co.soramitsu.iroha2.generated.NewAssetDefinition +import jp.co.soramitsu.iroha2.generated.NewDomain +import jp.co.soramitsu.iroha2.generated.NewRole +import jp.co.soramitsu.iroha2.generated.NumericValue +import jp.co.soramitsu.iroha2.generated.Peer +import jp.co.soramitsu.iroha2.generated.PermissionToken +import jp.co.soramitsu.iroha2.generated.PermissionTokenDefinition +import jp.co.soramitsu.iroha2.generated.PermissionTokenId +import jp.co.soramitsu.iroha2.generated.PublicKey +import jp.co.soramitsu.iroha2.generated.RegisterBox +import jp.co.soramitsu.iroha2.generated.RegistrableBox +import jp.co.soramitsu.iroha2.generated.Role +import jp.co.soramitsu.iroha2.generated.RoleId +import jp.co.soramitsu.iroha2.generated.SetKeyValueBox +import jp.co.soramitsu.iroha2.generated.SignatureCheckCondition +import jp.co.soramitsu.iroha2.generated.TransactionLimits +import jp.co.soramitsu.iroha2.generated.TransactionQueryResult +import jp.co.soramitsu.iroha2.generated.TransactionValue +import jp.co.soramitsu.iroha2.generated.TriggerBox +import jp.co.soramitsu.iroha2.generated.TriggerId +import jp.co.soramitsu.iroha2.generated.ValidatorMode +import jp.co.soramitsu.iroha2.generated.Value +import jp.co.soramitsu.iroha2.generated.ValueKind +import jp.co.soramitsu.iroha2.generated.VersionedCommittedBlock import java.io.ByteArrayOutputStream import kotlin.reflect.KClass import kotlin.reflect.full.createInstance @@ -81,7 +81,7 @@ val JSON_SERDE by lazy { val module = SimpleModule() // deserializers - module.addDeserializer(Instruction::class.java, InstructionDeserializer) + module.addDeserializer(InstructionBox::class.java, InstructionDeserializer) module.addDeserializer(GrantBox::class.java, GrantBoxDeserializer) module.addDeserializer(Value::class.java, ValueDeserializer) module.addDeserializer(ValueKind::class.java, ValueKindDeserializer) @@ -99,7 +99,7 @@ val JSON_SERDE by lazy { module.addDeserializer(MintBox::class.java, MintBoxDeserializer) module.addDeserializer(SetKeyValueBox::class.java, SetKeyValueBoxDeserializer) module.addDeserializer(Metadata::class.java, MetadataDeserializer) - module.addDeserializer(TokenId::class.java, TokenIdDeserializer) + module.addDeserializer(PermissionTokenId::class.java, TokenIdDeserializer) module.addDeserializer(NewRole::class.java, NewRoleDeserializer) module.addKeyDeserializer(AssetDefinitionId::class.java, AssetDefinitionIdKeyDeserializer) module.addKeyDeserializer(AccountId::class.java, AccountIdKeyDeserializer) @@ -109,7 +109,7 @@ val JSON_SERDE by lazy { // serializers module.addKeySerializer(Name::class.java, NameAsKeySerializer) module.addSerializer(DomainId::class.java, DomainIdSerializer) - module.addSerializer(TokenId::class.java, TokenIdSerializer) + module.addSerializer(PermissionTokenId::class.java, TokenIdSerializer) module.addSerializer(AssetDefinitionId::class.java, AssetDefinitionIdSerializer) module.addSerializer(AccountId::class.java, AccountIdSerializer) module.addSerializer(AssetId::class.java, AssetIdSerializer) @@ -122,9 +122,9 @@ val JSON_SERDE by lazy { module.addSerializer(EvaluatesTo::class.java, EvaluatesToSerializer) module.addSerializer(Metadata::class.java, MetadataSerializer) module.addSerializer(IdentifiableBox.NewRole::class.java, IdentifiableBoxNewRoleSerializer) + module.addSerializer(ValidatorMode::class.java, ValidatorModeSerializer) mapper.registerModule(module) - mapper.registerModule( KotlinModule.Builder() .configure(KotlinFeature.NullToEmptyCollection, true) @@ -137,11 +137,11 @@ val JSON_SERDE by lazy { } } -private fun sealedDeserializeInstruction(p: JsonParser, mapper: ObjectMapper): Instruction { +private fun sealedDeserializeInstruction(p: JsonParser, mapper: ObjectMapper): InstructionBox { val node = p.readValueAsTree().fields().next() val param = node.key - val subtype = Instruction::class.nestedClasses.find { clazz -> + val subtype = InstructionBox::class.nestedClasses.find { clazz -> !clazz.isCompanion && clazz.simpleName == param } ?: throw DeserializationException("Class with constructor($param) not found") @@ -152,7 +152,7 @@ private fun sealedDeserializeInstruction(p: JsonParser, mapper: ObjectMapper): I val toConvert: JsonNode = node.value val arg = mapper.convertValue(toConvert, argTypeName.asClass()) - return subtype.primaryConstructor?.call(arg) as Instruction + return subtype.primaryConstructor?.call(arg) as InstructionBox } private fun sealedDeserializeGrantBox(p: JsonParser, mapper: ObjectMapper): GrantBox { @@ -232,10 +232,10 @@ private fun getValueByClazz(clazz: KClass, subtype: KClass<*>, arg: Any SignatureCheckCondition::class -> Value.SignatureCheckCondition(subtype.primaryConstructor?.call(arg) as SignatureCheckCondition) TransactionValue::class -> Value.TransactionValue(subtype.primaryConstructor?.call(arg) as TransactionValue) TransactionQueryResult::class -> Value.TransactionQueryResult(subtype.primaryConstructor?.call(arg) as TransactionQueryResult) - Token::class -> Value.PermissionToken(subtype.primaryConstructor?.call(arg) as Token) + PermissionToken::class -> Value.PermissionToken(subtype.primaryConstructor?.call(arg) as PermissionToken) Hash::class -> Value.Hash(subtype.primaryConstructor?.call(arg) as Hash) - BlockValue::class -> Value.Block(subtype.primaryConstructor?.call(arg) as BlockValue) - BlockHeaderValue::class -> Value.BlockHeader(subtype.primaryConstructor?.call(arg) as BlockHeaderValue) + VersionedCommittedBlock::class -> Value.Block(subtype.primaryConstructor?.call(arg) as VersionedCommittedBlock) + BlockHeader::class -> Value.BlockHeader(subtype.primaryConstructor?.call(arg) as BlockHeader) Ipv4Addr::class -> Value.Ipv4Addr(subtype.primaryConstructor?.call(arg) as Ipv4Addr) Ipv6Addr::class -> Value.Ipv6Addr(subtype.primaryConstructor?.call(arg) as Ipv6Addr) NumericValue::class -> { @@ -247,6 +247,7 @@ private fun getValueByClazz(clazz: KClass, subtype: KClass<*>, arg: Any else -> throw DeserializationException("Numeric value $param not found") } } + else -> throw DeserializationException("Value type $clazz not found") } } @@ -267,10 +268,10 @@ private fun getClazzByParam(param: String): KClass { "SignatureCheckCondition" -> SignatureCheckCondition::class "TransactionValue" -> TransactionValue::class "TransactionQueryResult" -> TransactionQueryResult::class - "PermissionToken" -> Token::class + "PermissionToken" -> PermissionToken::class "Hash" -> Hash::class - "Block" -> BlockValue::class - "BlockHeader" -> BlockHeaderValue::class + "Block" -> VersionedCommittedBlock::class + "BlockHeader" -> BlockHeader::class "Ipv4Addr" -> Ipv4Addr::class "Ipv6Addr" -> Ipv6Addr::class "U32" -> NumericValue::class @@ -316,13 +317,17 @@ private fun getRegisterBox(arg: Any): RegisterBox { return when (arg) { is NewDomain -> RegisterBox(RegistrableBox.Domain(arg).evaluatesTo()) is NewAccount -> RegisterBox(RegistrableBox.Account(arg).evaluatesTo()) - is Definition -> RegisterBox(RegistrableBox.PermissionTokenDefinition(arg).evaluatesTo()) + is PermissionTokenDefinition -> RegisterBox(RegistrableBox.PermissionTokenDefinition(arg).evaluatesTo()) is Peer -> RegisterBox(RegistrableBox.Peer(arg).evaluatesTo()) is NewAssetDefinition -> RegisterBox(RegistrableBox.AssetDefinition(arg).evaluatesTo()) is Asset -> RegisterBox(RegistrableBox.Asset(arg).evaluatesTo()) - is Trigger<*> -> RegisterBox(RegistrableBox.Trigger(arg as Trigger).evaluatesTo()) is NewRole -> RegisterBox(RegistrableBox.Role(arg).evaluatesTo()) - is Validator -> RegisterBox(RegistrableBox.Validator(arg).evaluatesTo()) + is TriggerBox -> when (arg) { + is TriggerBox.Raw -> RegistrableBox.Trigger(arg.triggerOfFilterBoxAndExecutable) + else -> throw IrohaSdkException("Unsupported trigger type") + }.let { + RegisterBox(it.evaluatesTo()) + } else -> throw DeserializationException("Register box `$arg` not found") } } @@ -390,19 +395,11 @@ private fun sealedDeserializeNewRole(p: JsonParser, mapper: ObjectMapper): NewRo nodes.add(node) } - val tokens = nodes[1].map { - mapper.convertValue( - it, - "jp.co.soramitsu.iroha2.generated.datamodel.permission.token.Token".asClass() - ) as Token + val tokens = nodes[1].map { // todo + mapper.convertValue(it, "jp.co.soramitsu.iroha2.generated.PermissionToken".asClass()) as PermissionToken } val roleId = RoleId(nodes[0].asText().asName()) - return NewRole( - inner = Role( - id = roleId, - permissions = tokens - ) - ) + return NewRole(Role(id = roleId, permissions = tokens)) } private fun sealedDeserializeMetadata(p: JsonParser, mapper: ObjectMapper): Metadata { @@ -418,10 +415,10 @@ private fun sealedDeserializeMetadata(p: JsonParser, mapper: ObjectMapper): Meta } /** - * Deserializer for [Iroha Special Instructions][Instruction] + * Deserializer for [Iroha Special Instructions][InstructionBox] */ -object InstructionDeserializer : JsonDeserializer() { - override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Instruction { +object InstructionDeserializer : JsonDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): InstructionBox { return sealedDeserializeInstruction(p, JSON_SERDE) } } @@ -490,8 +487,8 @@ object MetadataDeserializer : JsonDeserializer() { } } -object TokenIdDeserializer : JsonDeserializer() { - override fun deserialize(p: JsonParser, ctxt: DeserializationContext): TokenId { +object TokenIdDeserializer : JsonDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): PermissionTokenId { return p.readValueAs(String::class.java).asTokenId() } } @@ -521,7 +518,7 @@ object AssetValueTypeDeserializer : JsonDeserializer() { object PublicKeyDeserializer : JsonDeserializer() { override fun deserialize(p: JsonParser, ctxt: DeserializationContext): PublicKey { val key = p.readValueAs(String::class.java) - return PublicKey(Ed25519.hashFunName, key.fromHex()) + return PublicKey(Algorithm.Ed25519(), key.fromHex()) } } @@ -660,10 +657,10 @@ object DomainIdSerializer : JsonSerializer() { } /** - * Serializer for [TokenId] + * Serializer for [PermissionTokenId] */ -object TokenIdSerializer : JsonSerializer() { - override fun serialize(value: TokenId, gen: JsonGenerator, serializers: SerializerProvider) { +object TokenIdSerializer : JsonSerializer() { + override fun serialize(value: PermissionTokenId, gen: JsonGenerator, serializers: SerializerProvider) { gen.writeString(value.asString()) } } @@ -704,6 +701,18 @@ object NameSerializer : JsonSerializer() { } } +/** + * Serializer for [Name] + */ +object ValidatorModeSerializer : JsonSerializer() { + override fun serialize(value: ValidatorMode, gen: JsonGenerator, serializers: SerializerProvider) { + when (value) { + is ValidatorMode.Path -> gen.writeString(value.string) + else -> throw IrohaSdkException("Unsupported type ${this::class}") + } + } +} + /** * Custom serializer for [UInt] */ @@ -759,11 +768,11 @@ object IdentifiableBoxNewRoleSerializer : JsonSerializer() { override fun serialize(value: ModelEnum, gen: JsonGenerator, serializers: SerializerProvider) { when (value) { - is Instruction.Grant -> value.serialize(gen) - is Instruction.Burn -> value.serialize(gen) - is Instruction.Mint -> value.serialize(gen) - is Instruction.SetKeyValue -> value.serialize(gen) - is Instruction.Register -> value.serialize(gen) + is InstructionBox.Grant -> value.serialize(gen) + is InstructionBox.Burn -> value.serialize(gen) + is InstructionBox.Mint -> value.serialize(gen) + is InstructionBox.SetKeyValue -> value.serialize(gen) + is InstructionBox.Register -> value.serialize(gen) is Expression.Raw -> value.serialize(gen) is Value.Identifiable -> value.serialize(gen) is Value.Id -> value.serialize(gen) @@ -780,15 +789,15 @@ private fun Expression.Raw.serialize(gen: JsonGenerator) = this.serializeEnum(ge private fun EvaluatesTo<*>.serialize(gen: JsonGenerator) = this.serializeEnum(gen) -private fun Instruction.SetKeyValue.serialize(gen: JsonGenerator) = this.serializeBox(gen) +private fun InstructionBox.SetKeyValue.serialize(gen: JsonGenerator) = this.serializeBox(gen) -private fun Instruction.Grant.serialize(gen: JsonGenerator) = this.serializeBox(gen) +private fun InstructionBox.Grant.serialize(gen: JsonGenerator) = this.serializeBox(gen) -private fun Instruction.Burn.serialize(gen: JsonGenerator) = this.serializeBox(gen) +private fun InstructionBox.Burn.serialize(gen: JsonGenerator) = this.serializeBox(gen) -private fun Instruction.Mint.serialize(gen: JsonGenerator) = this.serializeBox(gen) +private fun InstructionBox.Mint.serialize(gen: JsonGenerator) = this.serializeBox(gen) -private fun Instruction.Register.serialize(gen: JsonGenerator) { +private fun InstructionBox.Register.serialize(gen: JsonGenerator) { val clazz = this::class val memberProperties = clazz.memberProperties when (memberProperties.size) { @@ -811,7 +820,7 @@ private fun Instruction.Register.serialize(gen: JsonGenerator) { /** * Serializes BurnBox, MintBox, GrantBox etc... */ -private inline fun Instruction.serializeBox(gen: JsonGenerator) { +private inline fun InstructionBox.serializeBox(gen: JsonGenerator) { val clazz = this::class val memberProperties = clazz.memberProperties when (memberProperties.size) { @@ -848,9 +857,9 @@ private fun MintBox.serializeBox( private fun GrantBox.serializeBox(gen: JsonGenerator) { val fieldData = when (val rawValue = this.`object`.expression.cast().value) { - is Value.PermissionToken -> Value.PermissionToken::class.simpleName to rawValue.token + is Value.PermissionToken -> Value.PermissionToken::class.simpleName to rawValue.permissionToken is Value.Id -> RoleId::class.simpleName to rawValue.idBox.cast().roleId - else -> throw IrohaSdkException("Grant instruction serialization error") + else -> throw IrohaSdkException("Grant InstructionBox serialization error") } gen.writeObjectField(fieldData.first, fieldData.second) gen.writeObjectField("destination_id", this.destinationId) @@ -878,8 +887,8 @@ private fun mintBurnSerialize( is NumericValue.U32 -> NumericValue.U32::class.simpleName to rawValue.u32 is NumericValue.U64 -> NumericValue.U64::class.simpleName to rawValue.u64 is NumericValue.U128 -> NumericValue.U128::class.simpleName to rawValue.u128 - is NumericValue.Fixed -> NumericValue.Fixed::class.simpleName to rawValue.fixed.fixedPoint - else -> throw IrohaSdkException("Grant instruction serialization error") + is NumericValue.Fixed -> NumericValue.Fixed::class.simpleName to rawValue.fixed.fixedPointOfI64 + else -> throw IrohaSdkException("Grant InstructionBox serialization error") } gen.writeObjectField(fieldData.first, fieldData.second) gen.writeObjectField("destination_id", destinationId) @@ -898,6 +907,7 @@ private fun serializeSingleMember(gen: JsonGenerator, value: Any) { gen.writeObjectField(clazz.simpleName, memberProperties.first().call(value)) gen.writeEndObject() } + else -> throw SerializationException("Expected enum that accepts exactly 0 or 1 members as tuple") } } diff --git a/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt b/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt index db3ebbfb8..3ba34f237 100644 --- a/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt +++ b/modules/block/src/test/kotlin/jp/co/soramitsu/iroha2/DeserializerTest.kt @@ -1,10 +1,13 @@ package jp.co.soramitsu.iroha2 -import jp.co.soramitsu.iroha2.generated.core.genesis.RawGenesisBlock +import jp.co.soramitsu.iroha2.generated.RawGenesisBlock +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import java.io.File import kotlin.test.assertEquals +// https://app.zenhub.com/workspaces/iroha-v2-60ddb820813b9100181fc060/issues/gh/hyperledger/iroha-java/342 +@Disabled class DeserializerTest { @Test fun `should deserialize genesis block`() { @@ -21,7 +24,7 @@ class DeserializerTest { // Grant -> PermissionToken // Register -> PermissionTokenDefinition // Grant -> PermissionToken - assert(block.transactions.first().isi.size == 7) + assert(block.transactions.flatten().size == 7) val genesis = Genesis(block) val newJson = removeWhiteSpaceAndReplacePubKey(genesis.asJson()) @@ -64,7 +67,7 @@ class DeserializerTest { // SetKeyValue -> AssetId // SetKeyValue -> AssetId // Grant -> PermissionToken - assert(block.transactions.first().isi.size == 27) + assert(block.transactions.flatten().size == 27) val genesis = Genesis(block) val newJson = removeWhiteSpaceAndReplacePubKey(genesis.asJson()) @@ -100,7 +103,7 @@ class DeserializerTest { // Mint -> AssetId // Mint -> AssetId // Mint -> AssetId - assert(block.transactions.first().isi.size == 20) + assert(block.transactions.flatten().size == 20) val genesis = Genesis(block) val newJson = removeWhiteSpaceAndReplacePubKey(genesis.asJson()) diff --git a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Enums.kt b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Enums.kt index 5fbf57b6d..090daf6e6 100644 --- a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Enums.kt +++ b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Enums.kt @@ -1,25 +1,47 @@ package jp.co.soramitsu.iroha2 -import jp.co.soramitsu.iroha2.generated.datamodel.name.Name +import jp.co.soramitsu.iroha2.generated.Name enum class Permissions(val type: Name) { - CanSetKeyValueUserAssetsToken("can_set_key_value_in_user_assets".asName()), - CanRemoveKeyValueInUserAssets("can_remove_key_value_in_user_assets".asName()), - CanSetKeyValueInUserMetadata("can_set_key_value_in_user_metadata".asName()), - CanRemoveKeyValueInUserMetadata("can_remove_key_value_in_user_metadata".asName()), + CanUnregisterAccount("can_unregister_account".asName()), + CanMintUserPublicKeys("can_mint_user_public_keys".asName()), + CanBurnUserPublicKeys("can_burn_user_public_keys".asName()), + CanMintUserSignatureCheckConditions("can_mint_user_signature_check_conditions".asName()), + CanSetKeyValueInUserAccount("can_set_key_value_in_user_account".asName()), + CanRemoveKeyValueInUserAccount("can_remove_key_value_in_user_account".asName()), + CanRegisterAssetsWithDefinition("can_register_assets_with_definition".asName()), + CanUnregisterAssetsWithDefinition("can_unregister_assets_with_definition".asName()), + CanUnregisterUserAssets("can_unregister_user_assets".asName()), + CanBurnAssetWithDefinition("can_burn_assets_with_definition".asName()), + CanBurnUserAssetsToken("can_burn_user_assets".asName()), + CanMintUserAssetDefinitionsToken("can_mint_assets_with_definition".asName()), + CanTransferAssetsWithDefinition("can_transfer_assets_with_definition".asName()), + CanTransferUserAssetsToken("can_transfer_user_asset".asName()), + CanSetKeyValueUserAssetsToken("can_set_key_value_in_user_asset".asName()), + CanRemoveKeyValueInUserAssets("can_remove_key_value_in_user_asset".asName()), + CanUnregisterAssetDefinition("can_unregister_asset_definition".asName()), CanSetKeyValueInAssetDefinition("can_set_key_value_in_asset_definition".asName()), CanRemoveKeyValueInAssetDefinition("can_remove_key_value_in_asset_definition".asName()), - CanMintUserAssetDefinitionsToken("can_mint_user_asset_definitions".asName()), - CanBurnAssetWithDefinition("can_burn_asset_with_definition".asName()), - CanBurnUserAssetsToken("can_burn_user_assets".asName()), - CanRegisterDomainsToken("can_register_domains".asName()), - CanTransferUserAssetsToken("can_transfer_user_assets".asName()), - CanUnregisterAssetWithDefinition("can_unregister_asset_with_definition".asName()), - CanTransferOnlyFixedNumberOfTimesPerPeriod("can_transfer_only_fixed_number_of_times_per_period".asName()), + CanUnregisterDomain("can_unregister_domain".asName()), + CanSetKeyValueInDomain("can_set_key_value_in_domain".asName()), + CanRemoveKeyValueInDomain("can_remove_key_value_in_domain".asName()), + CanGrantPermissionToCreateParameters("can_grant_permission_to_create_parameters".asName()), + CanRevokePermissionToCreateParameters("can_revoke_permission_to_create_parameters".asName()), + CanCreateParameters("can_create_parameters".asName()), + CanGrantPermissionToSetParameters("can_grant_permission_to_set_parameters".asName()), + CanRevokePermissionToSetParameters("can_revoke_permission_to_set_parameters".asName()), + CanSetParameters("can_set_parameters".asName()), + CanUnregisterAnyPeer("can_unregister_any_peer".asName()), + CanUnregisterAnyRole("can_unregister_any_role".asName()), + CanExecuteUserTrigger("can_execute_user_trigger".asName()), + CanUnregisterUserTrigger("can_unregister_user_trigger".asName()), + CanMintUserTrigger("can_mint_user_trigger".asName()), + CanUpgradeValidator("can_upgrade_validator".asName()) } enum class IdKey(val type: String) { AccountId("account_id"), AssetId("asset_id"), - AssetDefinitionId("asset_definition_id") + AssetDefinitionId("asset_definition_id"), + DomainId("domain_id") } diff --git a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Extensions.kt b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Extensions.kt index c27ab3a37..cd22b82c8 100644 --- a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Extensions.kt +++ b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Extensions.kt @@ -1,38 +1,45 @@ package jp.co.soramitsu.iroha2 import io.ktor.websocket.Frame -import jp.co.soramitsu.iroha2.generated.crypto.hash.Hash -import jp.co.soramitsu.iroha2.generated.crypto.signature.Signature -import jp.co.soramitsu.iroha2.generated.crypto.signature.SignatureOf -import jp.co.soramitsu.iroha2.generated.datamodel.IdBox -import jp.co.soramitsu.iroha2.generated.datamodel.IdentifiableBox -import jp.co.soramitsu.iroha2.generated.datamodel.NumericValue -import jp.co.soramitsu.iroha2.generated.datamodel.RegistrableBox -import jp.co.soramitsu.iroha2.generated.datamodel.Value -import jp.co.soramitsu.iroha2.generated.datamodel.ValueKind -import jp.co.soramitsu.iroha2.generated.datamodel.account.AccountId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.Asset -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetDefinitionId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetValue -import jp.co.soramitsu.iroha2.generated.datamodel.domain.DomainId -import jp.co.soramitsu.iroha2.generated.datamodel.events.EventsFilterBox -import jp.co.soramitsu.iroha2.generated.datamodel.events.time.ExecutionTime -import jp.co.soramitsu.iroha2.generated.datamodel.expression.EvaluatesTo -import jp.co.soramitsu.iroha2.generated.datamodel.expression.Expression -import jp.co.soramitsu.iroha2.generated.datamodel.isi.Instruction -import jp.co.soramitsu.iroha2.generated.datamodel.metadata.Metadata -import jp.co.soramitsu.iroha2.generated.datamodel.name.Name -import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.Token -import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.TokenId -import jp.co.soramitsu.iroha2.generated.datamodel.role.RoleId -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.Executable -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.Payload -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.SignedTransaction -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.VersionedSignedTransaction -import jp.co.soramitsu.iroha2.generated.datamodel.trigger.Trigger -import jp.co.soramitsu.iroha2.generated.datamodel.trigger.TriggerId -import jp.co.soramitsu.iroha2.generated.primitives.fixed.Fixed +import jp.co.soramitsu.iroha2.generated.AccountId +import jp.co.soramitsu.iroha2.generated.Algorithm +import jp.co.soramitsu.iroha2.generated.Asset +import jp.co.soramitsu.iroha2.generated.AssetDefinitionId +import jp.co.soramitsu.iroha2.generated.AssetId +import jp.co.soramitsu.iroha2.generated.AssetValue +import jp.co.soramitsu.iroha2.generated.DomainId +import jp.co.soramitsu.iroha2.generated.EvaluatesTo +import jp.co.soramitsu.iroha2.generated.Executable +import jp.co.soramitsu.iroha2.generated.ExecutionTime +import jp.co.soramitsu.iroha2.generated.Expression +import jp.co.soramitsu.iroha2.generated.FilterBox +import jp.co.soramitsu.iroha2.generated.FindError +import jp.co.soramitsu.iroha2.generated.Fixed +import jp.co.soramitsu.iroha2.generated.Hash +import jp.co.soramitsu.iroha2.generated.IdBox +import jp.co.soramitsu.iroha2.generated.IdentifiableBox +import jp.co.soramitsu.iroha2.generated.InstructionBox +import jp.co.soramitsu.iroha2.generated.Metadata +import jp.co.soramitsu.iroha2.generated.Name +import jp.co.soramitsu.iroha2.generated.NumericValue +import jp.co.soramitsu.iroha2.generated.PermissionToken +import jp.co.soramitsu.iroha2.generated.PermissionTokenId +import jp.co.soramitsu.iroha2.generated.RegistrableBox +import jp.co.soramitsu.iroha2.generated.RoleId +import jp.co.soramitsu.iroha2.generated.Signature +import jp.co.soramitsu.iroha2.generated.SignatureOf +import jp.co.soramitsu.iroha2.generated.SignaturesOfOfTransactionPayload +import jp.co.soramitsu.iroha2.generated.SignedTransaction +import jp.co.soramitsu.iroha2.generated.SocketAddr +import jp.co.soramitsu.iroha2.generated.SocketAddrHost +import jp.co.soramitsu.iroha2.generated.TransactionPayload +import jp.co.soramitsu.iroha2.generated.TriggerBox +import jp.co.soramitsu.iroha2.generated.TriggerId +import jp.co.soramitsu.iroha2.generated.TriggerOfFilterBoxAndExecutable +import jp.co.soramitsu.iroha2.generated.TriggerOfFilterBoxAndOptimizedExecutable +import jp.co.soramitsu.iroha2.generated.Value +import jp.co.soramitsu.iroha2.generated.ValueKind +import jp.co.soramitsu.iroha2.generated.VersionedSignedTransaction import jp.co.soramitsu.iroha2.transaction.TransactionBuilder import net.i2p.crypto.eddsa.EdDSAEngine import org.bouncycastle.jcajce.provider.digest.Blake2b @@ -44,6 +51,7 @@ import java.security.MessageDigest import java.security.PrivateKey import java.security.PublicKey import kotlin.experimental.or +import jp.co.soramitsu.iroha2.generated.PublicKey as IrohaPublicKey fun Signature.asSignatureOf() = SignatureOf(this) @@ -74,7 +82,7 @@ fun String.asAssetId() = this.split(ASSET_ID_DELIMITER).takeIf { } } ?: throw IllegalArgumentException("Incorrect asset ID: $this") -fun String.asTokenId() = TokenId(Name(this)) +fun String.asTokenId() = PermissionTokenId(Name(this)) fun String.asDomainId() = DomainId(Name(this)) @@ -82,8 +90,9 @@ fun String.asName() = Name(this) fun String.asValue() = Value.String(this) +// TODO fun String.asValueKind() = when (this) { - "Id" -> ValueKind.Id() + ValueKind.Id::class.java.name -> ValueKind.Id() "Bool" -> ValueKind.Bool() "String" -> ValueKind.String() "Name" -> ValueKind.Name() @@ -119,6 +128,12 @@ fun Boolean.asValue() = Value.Bool(this) fun AccountId.asValue() = Value.Id(IdBox.AccountId(this)) +fun AssetId.asValue() = Value.Id(IdBox.AssetId(this)) + +fun AssetDefinitionId.asValue() = Value.Id(IdBox.AssetDefinitionId(this)) + +fun DomainId.asValue() = Value.Id(IdBox.DomainId(this)) + fun ByteArray.toFrame(fin: Boolean = true) = Frame.Binary(fin, this) fun ByteArray.toHex(): String = try { @@ -136,8 +151,8 @@ fun String.fromHex(): ByteArray = try { /** * Convert a public key to an Iroha public key */ -fun PublicKey.toIrohaPublicKey(): jp.co.soramitsu.iroha2.generated.crypto.PublicKey { - return jp.co.soramitsu.iroha2.generated.crypto.PublicKey(DigestFunction.Ed25519.hashFunName, this.bytes()) +fun PublicKey.toIrohaPublicKey(): IrohaPublicKey { + return IrohaPublicKey(Algorithm.Ed25519(), this.bytes()) } /** @@ -187,7 +202,7 @@ fun ByteArray.hash(): ByteArray { fun VersionedSignedTransaction.V1.hash(): ByteArray { return this.signedTransaction .payload - .let { Payload.encode(it) } + .let { TransactionPayload.encode(it) } .hash() } @@ -206,12 +221,12 @@ fun VersionedSignedTransaction.appendSignatures(vararg keypairs: KeyPair): Versi is VersionedSignedTransaction.V1 -> { val encodedPayload = signedTransaction .payload - .let { Payload.encode(it) } + .let { TransactionPayload.encode(it) } val signatures = keypairs.map { Signature( it.public.toIrohaPublicKey(), it.private.sign(encodedPayload) - ).asSignatureOf() + ).asSignatureOf() }.toSet() VersionedSignedTransaction.V1( @@ -224,6 +239,10 @@ fun VersionedSignedTransaction.appendSignatures(vararg keypairs: KeyPair): Versi } } +fun SignaturesOfOfTransactionPayload.plus( + signatures: Set> +) = SignaturesOfOfTransactionPayload(this.signatures.plus(signatures)) + /** * Cast to another type */ @@ -248,7 +267,7 @@ inline fun T.evaluatesTo(): EvaluatesTo { is IdBox -> Value.Id(this) is Hash -> Value.Hash(this) is Name -> Value.Name(this) - is Token -> Value.PermissionToken(this) + is PermissionToken -> Value.PermissionToken(this) is IdentifiableBox -> Value.Identifiable(this) is RegistrableBox -> Value.Identifiable(this.toIdentifiableBox()) is Value -> this @@ -271,15 +290,13 @@ fun RegistrableBox.toIdentifiableBox() = when (this) { is RegistrableBox.AssetDefinition -> IdentifiableBox.NewAssetDefinition(this.newAssetDefinition) is RegistrableBox.Role -> IdentifiableBox.NewRole(this.newRole) is RegistrableBox.Domain -> IdentifiableBox.NewDomain(this.newDomain) - is RegistrableBox.Trigger -> IdentifiableBox.Trigger(this.trigger) is RegistrableBox.PermissionTokenDefinition -> IdentifiableBox.PermissionTokenDefinition( - this.definition + this.permissionTokenDefinition ) - - is RegistrableBox.Validator -> IdentifiableBox.Validator(this.validator) + is RegistrableBox.Trigger -> IdentifiableBox.Trigger(TriggerBox.Raw(this.triggerOfFilterBoxAndExecutable)) } -fun T.asValue() = when (this) { +inline fun T.asValue() = when (this) { is String -> this.asValue() is Long -> this.asValue() is Int -> this.asValue() @@ -287,7 +304,9 @@ fun T.asValue() = when (this) { is BigDecimal -> this.asValue() is Boolean -> this.asValue() is AccountId -> this.asValue() - else -> throw RuntimeException("Unsupported type") + is AssetDefinitionId -> this.asValue() + is AssetId -> this.asValue() + else -> throw RuntimeException("Unsupported type ${T::class}") } fun AssetId.asString() = this.definitionId.asString() + ASSET_ID_DELIMITER + this.accountId.asString() @@ -298,7 +317,7 @@ fun AccountId.asString() = this.name.string + ACCOUNT_ID_DELIMITER + this.domain fun DomainId.asString() = this.name.string -fun TokenId.asString() = this.name.string +fun PermissionTokenId.asString() = this.name.string fun RoleId.asString() = this.name.string @@ -311,14 +330,14 @@ fun Metadata.merge(extra: Metadata) = Metadata( this.map.toMutableMap().also { it.putAll(extra.map) } ) -fun Instruction.Register.extractIdentifiableBox() = runCatching { +fun InstructionBox.Register.extractIdentifiableBox() = runCatching { this.registerBox.`object`.expression .cast().value .cast().identifiableBox }.getOrNull() -fun Iterable.extractIdentifiableBoxes() = this.asSequence() - .filterIsInstance() +fun Iterable.extractIdentifiableBoxes() = this.asSequence() + .filterIsInstance() .map { it.registerBox.`object`.expression } .filterIsInstance() .map { it.value } @@ -333,40 +352,39 @@ fun IdBox.extractId(): Any = when (this) { is IdBox.DomainId -> this.domainId is IdBox.TriggerId -> this.triggerId is IdBox.PeerId -> this.peerId - is IdBox.PermissionTokenDefinitionId -> this.tokenId - is IdBox.ParameterId -> this.id - is IdBox.ValidatorId -> this.validatorId + is IdBox.PermissionTokenDefinitionId -> this.permissionTokenId + is IdBox.ParameterId -> this.parameterId } -fun Instruction.Register.extractAccount() = this +fun InstructionBox.Register.extractAccount() = this .registerBox.`object`.expression .cast().value .cast().identifiableBox .cast().newAccount -fun Instruction.Register.extractDomain() = this +fun InstructionBox.Register.extractDomain() = this .registerBox.`object`.expression .cast().value .cast().identifiableBox .cast().newDomain -fun Instruction.Register.extractAssetDefinition() = this +fun InstructionBox.Register.extractAssetDefinition() = this .registerBox.`object`.expression .cast().value .cast().identifiableBox .cast().newAssetDefinition -fun Instruction.SetKeyValue.extractKey() = this +fun InstructionBox.SetKeyValue.extractKey() = this .setKeyValueBox.key.expression .cast().value .cast().name .string -fun Instruction.SetKeyValue.extractAccountId() = this.setKeyValueBox.objectId.extractAccountId() +fun InstructionBox.SetKeyValue.extractAccountId() = this.setKeyValueBox.objectId.extractAccountId() -fun Instruction.Unregister.extractAccountId() = this.unregisterBox.objectId.extractAccountId() +fun InstructionBox.Unregister.extractAccountId() = this.unregisterBox.objectId.extractAccountId() -fun Instruction.Unregister.extractDomainId() = this.unregisterBox.objectId.extractDomainId() +fun InstructionBox.Unregister.extractDomainId() = this.unregisterBox.objectId.extractDomainId() fun EvaluatesTo.extractAssetId() = this .expression @@ -386,66 +404,66 @@ fun EvaluatesTo.extractDomainId() = this .cast().idBox .cast().domainId -fun Instruction.Mint.extractPublicKey() = this +fun InstructionBox.Mint.extractPublicKey() = this .mintBox.`object`.expression .cast().value .cast().publicKey .payload.toHex() -inline fun VersionedSignedTransaction.V1.extractInstruction() = this +inline fun VersionedSignedTransaction.V1.extractInstruction() = this .extractInstructionVec() .first().cast() -inline fun VersionedSignedTransaction.V1.extractInstructions() = this +inline fun VersionedSignedTransaction.V1.extractInstructions() = this .extractInstructionVec() .cast>() -inline fun VersionedSignedTransaction.V1.extractInstructionVec() = this +inline fun VersionedSignedTransaction.V1.extractInstructionVec() = this .signedTransaction.payload.instructions .cast() .vec.filterIsInstance() -fun Instruction.Register.extractNewDomainMetadata() = this +fun InstructionBox.Register.extractNewDomainMetadata() = this .registerBox.`object`.expression .cast().value .cast().identifiableBox .cast().newDomain.metadata -fun Instruction.SetKeyValue.extractDomainId() = this +fun InstructionBox.SetKeyValue.extractDomainId() = this .setKeyValueBox.objectId.expression .cast().value .cast().idBox .cast().domainId -fun Instruction.SetKeyValue.key() = this +fun InstructionBox.SetKeyValue.key() = this .setKeyValueBox.key.expression .cast().value .cast().name.string -fun Instruction.SetKeyValue.extractValueString() = this +fun InstructionBox.SetKeyValue.extractValueString() = this .setKeyValueBox.value.expression .cast().value .cast().string -fun Instruction.SetKeyValue.extractValueU32() = this.setKeyValueBox.value.extractValueU32() +fun InstructionBox.SetKeyValue.extractValueU32() = this.setKeyValueBox.value.extractValueU32() -fun Instruction.SetKeyValue.extractValueU128() = this +fun InstructionBox.SetKeyValue.extractValueU128() = this .setKeyValueBox.value.expression .cast().value .getValue().numericValue .getValue() -fun Instruction.SetKeyValue.extractValueBoolean() = this +fun InstructionBox.SetKeyValue.extractValueBoolean() = this .setKeyValueBox.value.expression .cast().value .cast().bool -fun Instruction.Grant.extractValuePermissionToken() = this +fun InstructionBox.Grant.extractValuePermissionToken() = this .grantBox.`object`.expression .cast().value - .cast().token + .cast().permissionToken -fun Instruction.Burn.extractValueU32() = this.burnBox.`object`.extractValueU32() +fun InstructionBox.Burn.extractValueU32() = this.burnBox.`object`.extractValueU32() fun EvaluatesTo.extractValueU32() = this .expression @@ -453,10 +471,14 @@ fun EvaluatesTo.extractValueU32() = this .getValue().numericValue .getValue() -fun Trigger<*>.extractIsi() = this.action.executable.cast().vec +fun TriggerOfFilterBoxAndOptimizedExecutable.extractIsi() = this.action.executable.cast().vec +fun TriggerOfFilterBoxAndExecutable.extractIsi() = this.action.executable.cast().vec + +fun TriggerOfFilterBoxAndOptimizedExecutable.extractSchedule() = this.action.filter.extractSchedule() +fun TriggerOfFilterBoxAndExecutable.extractSchedule() = this.action.filter.extractSchedule() -fun Trigger<*>.extractSchedule() = this.action.filter - .cast() +fun FilterBox.extractSchedule() = this + .cast() .timeEventFilter.executionTime .cast().schedule @@ -486,7 +508,7 @@ fun Map.getU128Value(key: String) = this[key.asName()] fun Map.getFixedValue(key: String) = this[key.asName()] ?.cast()?.numericValue - ?.cast()?.fixed?.fixedPoint + ?.cast()?.fixed?.fixedPointOfI64 fun Map.getNameValue(key: String) = this[key.asName()]?.cast()?.name @@ -494,7 +516,7 @@ inline fun NumericValue.getValue() = when (this) { is NumericValue.U32 -> this.u32.cast() is NumericValue.U64 -> this.u64.cast() is NumericValue.U128 -> this.u128.cast() - is NumericValue.Fixed -> this.fixed.fixedPoint.cast() + is NumericValue.Fixed -> this.fixed.fixedPointOfI64.cast() } inline fun Value.getValue() = when (this) { @@ -519,3 +541,35 @@ inline fun Metadata.extract(key: String) = this.map.extract(key) fun Asset.metadata() = this.value.cast().metadata.map fun TransactionBuilder.merge(other: TransactionBuilder) = this.instructions.value.addAll(other.instructions.value) + +fun TriggerBox.id() = when (this) { + is TriggerBox.Raw -> this.triggerOfFilterBoxAndExecutable.id + is TriggerBox.Optimized -> this.triggerOfFilterBoxAndOptimizedExecutable.id +} + +fun String.toSocketAddr() = this.split(":").let { parts -> + if (parts.size != 2) throw IrohaSdkException("Incorrect address") + + SocketAddr.Host(SocketAddrHost(parts.first(), parts.last().toInt())) +} + +fun String.replace(oldValue: String) = this.replace(oldValue, "") + +fun String.replace(regex: Regex) = this.replace(regex, "") + +fun FindError.extract() = when (this) { + is FindError.Account -> this.accountId.asString() + is FindError.Asset -> this.assetId.asString() + is FindError.AssetDefinition -> this.assetDefinitionId.asString() + is FindError.Domain -> this.domainId.asString() + is FindError.Role -> this.roleId.asString() + is FindError.Block -> this.hashOf.hash.arrayOfU8.toHex() + is FindError.MetadataKey -> this.name.string + is FindError.Parameter -> this.parameterId.name.string + is FindError.Peer -> this.peerId.address.toString() + is FindError.PermissionToken -> this.permissionTokenFindError.permissionTokenId.name.string + is FindError.PermissionTokenDefinition -> this.permissionTokenId.name.string + is FindError.PublicKey -> this.publicKey.payload.toString() + is FindError.Trigger -> this.triggerId.asString() + is FindError.Transaction -> this.hashOf.hash.arrayOfU8.toHex() +} diff --git a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Extractors.kt b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Extractors.kt index 0203a0fcf..80e50f3e0 100644 --- a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Extractors.kt +++ b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Extractors.kt @@ -1,26 +1,26 @@ package jp.co.soramitsu.iroha2 -import jp.co.soramitsu.iroha2.generated.datamodel.IdBox -import jp.co.soramitsu.iroha2.generated.datamodel.IdentifiableBox -import jp.co.soramitsu.iroha2.generated.datamodel.NumericValue -import jp.co.soramitsu.iroha2.generated.datamodel.Value -import jp.co.soramitsu.iroha2.generated.datamodel.account.Account -import jp.co.soramitsu.iroha2.generated.datamodel.asset.Asset -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetDefinition -import jp.co.soramitsu.iroha2.generated.datamodel.blockvalue.BlockHeaderValue -import jp.co.soramitsu.iroha2.generated.datamodel.blockvalue.BlockValue -import jp.co.soramitsu.iroha2.generated.datamodel.domain.Domain -import jp.co.soramitsu.iroha2.generated.datamodel.peer.Peer -import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.Definition -import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.Token -import jp.co.soramitsu.iroha2.generated.datamodel.query.PaginatedQueryResult -import jp.co.soramitsu.iroha2.generated.datamodel.query.VersionedPaginatedQueryResult -import jp.co.soramitsu.iroha2.generated.datamodel.role.Role -import jp.co.soramitsu.iroha2.generated.datamodel.role.RoleId -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.TransactionQueryResult -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.TransactionValue -import jp.co.soramitsu.iroha2.generated.datamodel.trigger.Trigger -import jp.co.soramitsu.iroha2.generated.datamodel.trigger.TriggerId +import jp.co.soramitsu.iroha2.generated.Account +import jp.co.soramitsu.iroha2.generated.Asset +import jp.co.soramitsu.iroha2.generated.AssetDefinition +import jp.co.soramitsu.iroha2.generated.BlockHeader +import jp.co.soramitsu.iroha2.generated.Domain +import jp.co.soramitsu.iroha2.generated.IdBox +import jp.co.soramitsu.iroha2.generated.IdentifiableBox +import jp.co.soramitsu.iroha2.generated.NumericValue +import jp.co.soramitsu.iroha2.generated.PaginatedQueryResult +import jp.co.soramitsu.iroha2.generated.Peer +import jp.co.soramitsu.iroha2.generated.PermissionToken +import jp.co.soramitsu.iroha2.generated.PermissionTokenDefinition +import jp.co.soramitsu.iroha2.generated.Role +import jp.co.soramitsu.iroha2.generated.RoleId +import jp.co.soramitsu.iroha2.generated.TransactionQueryResult +import jp.co.soramitsu.iroha2.generated.TransactionValue +import jp.co.soramitsu.iroha2.generated.TriggerBox +import jp.co.soramitsu.iroha2.generated.TriggerId +import jp.co.soramitsu.iroha2.generated.Value +import jp.co.soramitsu.iroha2.generated.VersionedCommittedBlock +import jp.co.soramitsu.iroha2.generated.VersionedPaginatedQueryResult import java.math.BigInteger /** @@ -146,19 +146,19 @@ object PeersExtractor : ResultExtractor> { /** * Extract a trigger from a query [result] */ -object TriggerExtractor : ResultExtractor> { - override fun extract(result: PaginatedQueryResult): Trigger<*> { - return extractIdentifiable(result.result.value, IdentifiableBox.Trigger::trigger) +object TriggerBoxExtractor : ResultExtractor { + override fun extract(result: PaginatedQueryResult): TriggerBox { + return extractIdentifiable(result.result.value, IdentifiableBox.Trigger::triggerBox) } } /** * Extract a list of triggers from a query [result] */ -object TriggersExtractor : ResultExtractor>> { - override fun extract(result: PaginatedQueryResult): List> { +object TriggerBoxesExtractor : ResultExtractor> { + override fun extract(result: PaginatedQueryResult): List { return extractVec(result.result.value) { - extractIdentifiable(it, IdentifiableBox.Trigger::trigger) + extractIdentifiable(it, IdentifiableBox.Trigger::triggerBox) } } } @@ -177,10 +177,10 @@ object TriggerIdsExtractor : ResultExtractor> { /** * Extract a list of permission tokens from a query [result] */ -object PermissionTokensExtractor : ResultExtractor> { - override fun extract(result: PaginatedQueryResult): List { +object PermissionTokensExtractor : ResultExtractor> { + override fun extract(result: PaginatedQueryResult): List { return extractVec(result.result.value) { - extractValue(it, Value.PermissionToken::token) + extractValue(it, Value.PermissionToken::permissionToken) } } } @@ -188,10 +188,10 @@ object PermissionTokensExtractor : ResultExtractor> { /** * Extract a list of permission token definitions from a query [result] */ -object PermissionTokenDefinitionsExtractor : ResultExtractor> { - override fun extract(result: PaginatedQueryResult): List { +object PermissionTokenDefinitionsExtractor : ResultExtractor> { + override fun extract(result: PaginatedQueryResult): List { return extractVec(result.result.value) { - extractIdentifiable(it, IdentifiableBox.PermissionTokenDefinition::definition) + extractIdentifiable(it, IdentifiableBox.PermissionTokenDefinition::permissionTokenDefinition) } } } @@ -232,25 +232,25 @@ object TransactionQueryResultExtractor : ResultExtractor> { - override fun extract(result: PaginatedQueryResult): List { +object BlocksValueExtractor : ResultExtractor> { + override fun extract(result: PaginatedQueryResult): List { return extractVec(result.result.value) { - extractValue(it, Value.Block::blockValue) + extractValue(it, Value.Block::versionedCommittedBlock) } } } -object BlockHeadersValueExtractor : ResultExtractor> { - override fun extract(result: PaginatedQueryResult): List { +object BlockHeadersExtractor : ResultExtractor> { + override fun extract(result: PaginatedQueryResult): List { return extractVec(result.result.value) { - extractValue(it, Value.BlockHeader::blockHeaderValue) + extractValue(it, Value.BlockHeader::blockHeader) } } } -object BlockHeaderValueExtractor : ResultExtractor { - override fun extract(result: PaginatedQueryResult): BlockHeaderValue { - return extractValue(result.result.value, Value.BlockHeader::blockHeaderValue) +object BlockHeaderExtractor : ResultExtractor { + override fun extract(result: PaginatedQueryResult): BlockHeader { + return extractValue(result.result.value, Value.BlockHeader::blockHeader) } } diff --git a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Page.kt b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Page.kt index 14f50f8fe..35afc3972 100644 --- a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Page.kt +++ b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Page.kt @@ -1,6 +1,6 @@ package jp.co.soramitsu.iroha2 -import jp.co.soramitsu.iroha2.generated.datamodel.pagination.Pagination +import jp.co.soramitsu.iroha2.generated.Pagination import java.math.BigInteger /** diff --git a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/client/Iroha2AsyncClient.kt b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/client/Iroha2AsyncClient.kt index a88e59690..f3ecddbed 100644 --- a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/client/Iroha2AsyncClient.kt +++ b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/client/Iroha2AsyncClient.kt @@ -1,6 +1,6 @@ package jp.co.soramitsu.iroha2.client -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.VersionedSignedTransaction +import jp.co.soramitsu.iroha2.generated.VersionedSignedTransaction import jp.co.soramitsu.iroha2.query.QueryAndExtractor import kotlinx.coroutines.future.asCompletableFuture import kotlinx.coroutines.future.future diff --git a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/client/Iroha2Client.kt b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/client/Iroha2Client.kt index 8491dc9da..978cd1fe6 100644 --- a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/client/Iroha2Client.kt +++ b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/client/Iroha2Client.kt @@ -31,24 +31,25 @@ import jp.co.soramitsu.iroha2.Page import jp.co.soramitsu.iroha2.TransactionRejectedException import jp.co.soramitsu.iroha2.WebSocketProtocolException import jp.co.soramitsu.iroha2.cast -import jp.co.soramitsu.iroha2.generated.core.block.stream.BlockSubscriptionRequest -import jp.co.soramitsu.iroha2.generated.core.block.stream.VersionedBlockMessage -import jp.co.soramitsu.iroha2.generated.core.block.stream.VersionedBlockSubscriptionRequest -import jp.co.soramitsu.iroha2.generated.datamodel.events.Event -import jp.co.soramitsu.iroha2.generated.datamodel.events.EventMessage -import jp.co.soramitsu.iroha2.generated.datamodel.events.EventSubscriptionRequest -import jp.co.soramitsu.iroha2.generated.datamodel.events.VersionedEventMessage -import jp.co.soramitsu.iroha2.generated.datamodel.events.VersionedEventSubscriptionRequest -import jp.co.soramitsu.iroha2.generated.datamodel.events.pipeline.EntityKind -import jp.co.soramitsu.iroha2.generated.datamodel.events.pipeline.Status -import jp.co.soramitsu.iroha2.generated.datamodel.pagination.Pagination -import jp.co.soramitsu.iroha2.generated.datamodel.query.VersionedPaginatedQueryResult -import jp.co.soramitsu.iroha2.generated.datamodel.query.VersionedSignedQueryRequest -import jp.co.soramitsu.iroha2.generated.datamodel.sorting.Sorting -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.BlockRejectionReason -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.RejectionReason -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.TransactionRejectionReason -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.VersionedSignedTransaction +import jp.co.soramitsu.iroha2.extract +import jp.co.soramitsu.iroha2.generated.BlockRejectionReason +import jp.co.soramitsu.iroha2.generated.BlockSubscriptionRequest +import jp.co.soramitsu.iroha2.generated.Event +import jp.co.soramitsu.iroha2.generated.EventMessage +import jp.co.soramitsu.iroha2.generated.EventSubscriptionRequest +import jp.co.soramitsu.iroha2.generated.Pagination +import jp.co.soramitsu.iroha2.generated.PipelineEntityKind +import jp.co.soramitsu.iroha2.generated.PipelineRejectionReason +import jp.co.soramitsu.iroha2.generated.PipelineStatus +import jp.co.soramitsu.iroha2.generated.Sorting +import jp.co.soramitsu.iroha2.generated.TransactionRejectionReason +import jp.co.soramitsu.iroha2.generated.VersionedBlockMessage +import jp.co.soramitsu.iroha2.generated.VersionedBlockSubscriptionRequest +import jp.co.soramitsu.iroha2.generated.VersionedEventMessage +import jp.co.soramitsu.iroha2.generated.VersionedEventSubscriptionRequest +import jp.co.soramitsu.iroha2.generated.VersionedPaginatedQueryResult +import jp.co.soramitsu.iroha2.generated.VersionedSignedQuery +import jp.co.soramitsu.iroha2.generated.VersionedSignedTransaction import jp.co.soramitsu.iroha2.hash import jp.co.soramitsu.iroha2.query.QueryAndExtractor import jp.co.soramitsu.iroha2.toFrame @@ -156,7 +157,7 @@ open class Iroha2Client( ): Page { logger.debug("Sending query") val response: HttpResponse = client.post("$peerUrl$QUERY_ENDPOINT") { - setBody(VersionedSignedQueryRequest.encode(queryAndExtractor.query)) + setBody(VersionedSignedQuery.encode(queryAndExtractor.query)) page?.also { parameter("start", it.start) parameter("limit", it.limit) @@ -203,6 +204,7 @@ open class Iroha2Client( fireAndForget { signedTransaction } } } + // todo уровень логов /** * Subscribe to block streaming @@ -292,22 +294,20 @@ open class Iroha2Client( when (val event = eventPublisherMessage.event) { is Event.Pipeline -> { val eventInner = event.pipelineEvent - if (eventInner.entityKind is EntityKind.Transaction && hash.contentEquals(eventInner.hash.array)) { + if (eventInner.entityKind is PipelineEntityKind.Transaction && hash.contentEquals(eventInner.hash.arrayOfU8)) { when (val status = eventInner.status) { - is Status.Committed -> { + is PipelineStatus.Committed -> { logger.debug("Transaction {} committed", hexHash) return hash } - is Status.Rejected -> { - val reason = status.rejectionReason.message() + is PipelineStatus.Rejected -> { + val reason = status.pipelineRejectionReason.message() logger.error("Transaction {} was rejected by reason: `{}`", hexHash, reason) throw TransactionRejectedException("Transaction rejected with reason '$reason'") } - is Status.Validating -> { - logger.debug("Transaction {} is validating", hexHash) - } + is PipelineStatus.Validating -> logger.debug("Transaction {} is validating", hexHash) } } return null @@ -323,19 +323,18 @@ open class Iroha2Client( /** * Extract the rejection reason */ - private fun RejectionReason.message(): String { + private fun PipelineRejectionReason.message(): String { return when (this) { - is RejectionReason.Block -> when (this.blockRejectionReason) { + is PipelineRejectionReason.Block -> when (this.blockRejectionReason) { is BlockRejectionReason.ConsensusBlockRejection -> "Block was rejected during consensus" } - is RejectionReason.Transaction -> when (val reason = this.transactionRejectionReason) { + is PipelineRejectionReason.Transaction -> when (val reason = this.transactionRejectionReason) { is TransactionRejectionReason.InstructionExecution -> { val details = reason.instructionExecutionFail "Failed: `${details.reason}` during execution of instruction: ${details.instruction::class.qualifiedName}" } - is TransactionRejectionReason.NotPermitted -> reason.notPermittedFail.reason is TransactionRejectionReason.UnexpectedGenesisAccountSignature -> "Genesis account can sign only transactions in the genesis block" @@ -343,7 +342,10 @@ open class Iroha2Client( reason.unsatisfiedSignatureConditionFail.reason is TransactionRejectionReason.WasmExecution -> reason.wasmExecutionFail.reason - is TransactionRejectionReason.LimitCheck -> reason.transactionLimitError.string + is TransactionRejectionReason.LimitCheck -> reason.transactionLimitError.reason + is TransactionRejectionReason.Expired -> reason.transactionExpired.timeToLiveMs.toString() + is TransactionRejectionReason.AccountDoesNotExist -> reason.findError.extract() + is TransactionRejectionReason.Validation -> reason.validationFail.toString() } } } @@ -370,7 +372,7 @@ open class Iroha2Client( private fun eventSubscriberMessageOf( hash: ByteArray, - entityKind: EntityKind = EntityKind.Transaction() + entityKind: PipelineEntityKind = PipelineEntityKind.Transaction() ): VersionedEventSubscriptionRequest.V1 { return VersionedEventSubscriptionRequest.V1( EventSubscriptionRequest( diff --git a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/query/Queries.kt b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/query/Queries.kt index f87ae43e5..c69dcd1ed 100644 --- a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/query/Queries.kt +++ b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/query/Queries.kt @@ -1,55 +1,55 @@ package jp.co.soramitsu.iroha2.query import jp.co.soramitsu.iroha2.evaluatesTo -import jp.co.soramitsu.iroha2.generated.crypto.hash.Hash -import jp.co.soramitsu.iroha2.generated.datamodel.account.AccountId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetDefinitionId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetId -import jp.co.soramitsu.iroha2.generated.datamodel.domain.DomainId -import jp.co.soramitsu.iroha2.generated.datamodel.name.Name -import jp.co.soramitsu.iroha2.generated.datamodel.query.QueryBox -import jp.co.soramitsu.iroha2.generated.datamodel.query.account.FindAccountById -import jp.co.soramitsu.iroha2.generated.datamodel.query.account.FindAccountKeyValueByIdAndKey -import jp.co.soramitsu.iroha2.generated.datamodel.query.account.FindAccountsByDomainId -import jp.co.soramitsu.iroha2.generated.datamodel.query.account.FindAccountsByName -import jp.co.soramitsu.iroha2.generated.datamodel.query.account.FindAccountsWithAsset -import jp.co.soramitsu.iroha2.generated.datamodel.query.account.FindAllAccounts -import jp.co.soramitsu.iroha2.generated.datamodel.query.asset.FindAllAssets -import jp.co.soramitsu.iroha2.generated.datamodel.query.asset.FindAllAssetsDefinitions -import jp.co.soramitsu.iroha2.generated.datamodel.query.asset.FindAssetById -import jp.co.soramitsu.iroha2.generated.datamodel.query.asset.FindAssetDefinitionById -import jp.co.soramitsu.iroha2.generated.datamodel.query.asset.FindAssetDefinitionKeyValueByIdAndKey -import jp.co.soramitsu.iroha2.generated.datamodel.query.asset.FindAssetKeyValueByIdAndKey -import jp.co.soramitsu.iroha2.generated.datamodel.query.asset.FindAssetQuantityById -import jp.co.soramitsu.iroha2.generated.datamodel.query.asset.FindAssetsByAccountId -import jp.co.soramitsu.iroha2.generated.datamodel.query.asset.FindAssetsByAssetDefinitionId -import jp.co.soramitsu.iroha2.generated.datamodel.query.asset.FindAssetsByDomainId -import jp.co.soramitsu.iroha2.generated.datamodel.query.asset.FindAssetsByDomainIdAndAssetDefinitionId -import jp.co.soramitsu.iroha2.generated.datamodel.query.asset.FindAssetsByName -import jp.co.soramitsu.iroha2.generated.datamodel.query.asset.FindTotalAssetQuantityByAssetDefinitionId -import jp.co.soramitsu.iroha2.generated.datamodel.query.block.FindAllBlockHeaders -import jp.co.soramitsu.iroha2.generated.datamodel.query.block.FindAllBlocks -import jp.co.soramitsu.iroha2.generated.datamodel.query.block.FindBlockHeaderByHash -import jp.co.soramitsu.iroha2.generated.datamodel.query.domain.FindAllDomains -import jp.co.soramitsu.iroha2.generated.datamodel.query.domain.FindDomainById -import jp.co.soramitsu.iroha2.generated.datamodel.query.domain.FindDomainKeyValueByIdAndKey -import jp.co.soramitsu.iroha2.generated.datamodel.query.peer.FindAllParameters -import jp.co.soramitsu.iroha2.generated.datamodel.query.peer.FindAllPeers -import jp.co.soramitsu.iroha2.generated.datamodel.query.permissions.FindAllPermissionTokenDefinitions -import jp.co.soramitsu.iroha2.generated.datamodel.query.permissions.FindPermissionTokensByAccountId -import jp.co.soramitsu.iroha2.generated.datamodel.query.role.FindAllRoleIds -import jp.co.soramitsu.iroha2.generated.datamodel.query.role.FindAllRoles -import jp.co.soramitsu.iroha2.generated.datamodel.query.role.FindRoleByRoleId -import jp.co.soramitsu.iroha2.generated.datamodel.query.role.FindRolesByAccountId -import jp.co.soramitsu.iroha2.generated.datamodel.query.transaction.FindAllTransactions -import jp.co.soramitsu.iroha2.generated.datamodel.query.transaction.FindTransactionByHash -import jp.co.soramitsu.iroha2.generated.datamodel.query.transaction.FindTransactionsByAccountId -import jp.co.soramitsu.iroha2.generated.datamodel.query.trigger.FindAllActiveTriggerIds -import jp.co.soramitsu.iroha2.generated.datamodel.query.trigger.FindTriggerById -import jp.co.soramitsu.iroha2.generated.datamodel.query.trigger.FindTriggerKeyValueByIdAndKey -import jp.co.soramitsu.iroha2.generated.datamodel.query.trigger.FindTriggersByDomainId -import jp.co.soramitsu.iroha2.generated.datamodel.role.RoleId -import jp.co.soramitsu.iroha2.generated.datamodel.trigger.TriggerId +import jp.co.soramitsu.iroha2.generated.AccountId +import jp.co.soramitsu.iroha2.generated.AssetDefinitionId +import jp.co.soramitsu.iroha2.generated.AssetId +import jp.co.soramitsu.iroha2.generated.DomainId +import jp.co.soramitsu.iroha2.generated.FindAccountById +import jp.co.soramitsu.iroha2.generated.FindAccountKeyValueByIdAndKey +import jp.co.soramitsu.iroha2.generated.FindAccountsByDomainId +import jp.co.soramitsu.iroha2.generated.FindAccountsByName +import jp.co.soramitsu.iroha2.generated.FindAccountsWithAsset +import jp.co.soramitsu.iroha2.generated.FindAllAccounts +import jp.co.soramitsu.iroha2.generated.FindAllActiveTriggerIds +import jp.co.soramitsu.iroha2.generated.FindAllAssets +import jp.co.soramitsu.iroha2.generated.FindAllAssetsDefinitions +import jp.co.soramitsu.iroha2.generated.FindAllBlockHeaders +import jp.co.soramitsu.iroha2.generated.FindAllBlocks +import jp.co.soramitsu.iroha2.generated.FindAllDomains +import jp.co.soramitsu.iroha2.generated.FindAllParameters +import jp.co.soramitsu.iroha2.generated.FindAllPeers +import jp.co.soramitsu.iroha2.generated.FindAllPermissionTokenDefinitions +import jp.co.soramitsu.iroha2.generated.FindAllRoleIds +import jp.co.soramitsu.iroha2.generated.FindAllRoles +import jp.co.soramitsu.iroha2.generated.FindAllTransactions +import jp.co.soramitsu.iroha2.generated.FindAssetById +import jp.co.soramitsu.iroha2.generated.FindAssetDefinitionById +import jp.co.soramitsu.iroha2.generated.FindAssetDefinitionKeyValueByIdAndKey +import jp.co.soramitsu.iroha2.generated.FindAssetKeyValueByIdAndKey +import jp.co.soramitsu.iroha2.generated.FindAssetQuantityById +import jp.co.soramitsu.iroha2.generated.FindAssetsByAccountId +import jp.co.soramitsu.iroha2.generated.FindAssetsByAssetDefinitionId +import jp.co.soramitsu.iroha2.generated.FindAssetsByDomainId +import jp.co.soramitsu.iroha2.generated.FindAssetsByDomainIdAndAssetDefinitionId +import jp.co.soramitsu.iroha2.generated.FindAssetsByName +import jp.co.soramitsu.iroha2.generated.FindBlockHeaderByHash +import jp.co.soramitsu.iroha2.generated.FindDomainById +import jp.co.soramitsu.iroha2.generated.FindDomainKeyValueByIdAndKey +import jp.co.soramitsu.iroha2.generated.FindPermissionTokensByAccountId +import jp.co.soramitsu.iroha2.generated.FindRoleByRoleId +import jp.co.soramitsu.iroha2.generated.FindRolesByAccountId +import jp.co.soramitsu.iroha2.generated.FindTotalAssetQuantityByAssetDefinitionId +import jp.co.soramitsu.iroha2.generated.FindTransactionByHash +import jp.co.soramitsu.iroha2.generated.FindTransactionsByAccountId +import jp.co.soramitsu.iroha2.generated.FindTriggerById +import jp.co.soramitsu.iroha2.generated.FindTriggerKeyValueByIdAndKey +import jp.co.soramitsu.iroha2.generated.FindTriggersByDomainId +import jp.co.soramitsu.iroha2.generated.Hash +import jp.co.soramitsu.iroha2.generated.Name +import jp.co.soramitsu.iroha2.generated.QueryBox +import jp.co.soramitsu.iroha2.generated.RoleId +import jp.co.soramitsu.iroha2.generated.TriggerId /** * Queries are sent to an Iroha peer and prompt a response with details from the current world state view. diff --git a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/query/QueryBuilder.kt b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/query/QueryBuilder.kt index acda5c0be..dd5df16bc 100644 --- a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/query/QueryBuilder.kt +++ b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/query/QueryBuilder.kt @@ -6,8 +6,8 @@ import jp.co.soramitsu.iroha2.AssetDefinitionExtractor import jp.co.soramitsu.iroha2.AssetDefinitionsExtractor import jp.co.soramitsu.iroha2.AssetExtractor import jp.co.soramitsu.iroha2.AssetsExtractor -import jp.co.soramitsu.iroha2.BlockHeaderValueExtractor -import jp.co.soramitsu.iroha2.BlockHeadersValueExtractor +import jp.co.soramitsu.iroha2.BlockHeaderExtractor +import jp.co.soramitsu.iroha2.BlockHeadersExtractor import jp.co.soramitsu.iroha2.BlocksValueExtractor import jp.co.soramitsu.iroha2.DomainExtractor import jp.co.soramitsu.iroha2.DomainsExtractor @@ -21,29 +21,29 @@ import jp.co.soramitsu.iroha2.RolesExtractor import jp.co.soramitsu.iroha2.TransactionQueryResultExtractor import jp.co.soramitsu.iroha2.TransactionValueExtractor import jp.co.soramitsu.iroha2.TransactionValuesExtractor -import jp.co.soramitsu.iroha2.TriggerExtractor +import jp.co.soramitsu.iroha2.TriggerBoxExtractor +import jp.co.soramitsu.iroha2.TriggerBoxesExtractor import jp.co.soramitsu.iroha2.TriggerIdsExtractor -import jp.co.soramitsu.iroha2.TriggersExtractor import jp.co.soramitsu.iroha2.U32Extractor import jp.co.soramitsu.iroha2.ValueExtractor import jp.co.soramitsu.iroha2.asName import jp.co.soramitsu.iroha2.asSignatureOf import jp.co.soramitsu.iroha2.fromHex -import jp.co.soramitsu.iroha2.generated.crypto.hash.Hash -import jp.co.soramitsu.iroha2.generated.crypto.signature.Signature -import jp.co.soramitsu.iroha2.generated.datamodel.account.AccountId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetDefinitionId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetId -import jp.co.soramitsu.iroha2.generated.datamodel.domain.DomainId -import jp.co.soramitsu.iroha2.generated.datamodel.name.Name -import jp.co.soramitsu.iroha2.generated.datamodel.predicate.GenericValuePredicateBox -import jp.co.soramitsu.iroha2.generated.datamodel.predicate.value.ValuePredicate -import jp.co.soramitsu.iroha2.generated.datamodel.query.Payload -import jp.co.soramitsu.iroha2.generated.datamodel.query.QueryBox -import jp.co.soramitsu.iroha2.generated.datamodel.query.SignedQueryRequest -import jp.co.soramitsu.iroha2.generated.datamodel.query.VersionedSignedQueryRequest -import jp.co.soramitsu.iroha2.generated.datamodel.role.RoleId -import jp.co.soramitsu.iroha2.generated.datamodel.trigger.TriggerId +import jp.co.soramitsu.iroha2.generated.AccountId +import jp.co.soramitsu.iroha2.generated.AssetDefinitionId +import jp.co.soramitsu.iroha2.generated.AssetId +import jp.co.soramitsu.iroha2.generated.DomainId +import jp.co.soramitsu.iroha2.generated.GenericPredicateBox +import jp.co.soramitsu.iroha2.generated.Hash +import jp.co.soramitsu.iroha2.generated.Name +import jp.co.soramitsu.iroha2.generated.QueryBox +import jp.co.soramitsu.iroha2.generated.QueryPayload +import jp.co.soramitsu.iroha2.generated.RoleId +import jp.co.soramitsu.iroha2.generated.Signature +import jp.co.soramitsu.iroha2.generated.SignedQuery +import jp.co.soramitsu.iroha2.generated.TriggerId +import jp.co.soramitsu.iroha2.generated.ValuePredicate +import jp.co.soramitsu.iroha2.generated.VersionedSignedQuery import jp.co.soramitsu.iroha2.hash import jp.co.soramitsu.iroha2.sign import jp.co.soramitsu.iroha2.toIrohaHash @@ -55,7 +55,7 @@ import java.time.Instant class QueryBuilder( private val query: QueryBox, private val resultExtractor: ResultExtractor, - private val queryFilter: GenericValuePredicateBox? = null + private val queryFilter: GenericPredicateBox? = null ) { private var accountId: AccountId? = null @@ -73,22 +73,17 @@ class QueryBuilder( this.apply { this.creationTime(BigInteger.valueOf(creationTimeMillis)) } fun buildSigned(keyPair: KeyPair): QueryAndExtractor { - val filter = queryFilter ?: GenericValuePredicateBox.Raw(ValuePredicate.Pass()) - val payload = Payload( + val filter = queryFilter ?: GenericPredicateBox.Raw(ValuePredicate.Pass()) + val payload = QueryPayload( creationTimeMillis ?: fallbackCreationTime(), query, checkNotNull(accountId) { "Account Id of the sender is mandatory" }, filter ) - val encodedPayload = Payload.encode(payload) - val signature = Signature( - keyPair.public.toIrohaPublicKey(), - keyPair.private.sign(encodedPayload) - ) + val encodedPayload = QueryPayload.encode(payload) + val signature = Signature(keyPair.public.toIrohaPublicKey(), keyPair.private.sign(encodedPayload)) - val query = VersionedSignedQueryRequest.V1( - SignedQueryRequest(payload, signature.asSignatureOf()) - ) + val query = VersionedSignedQuery.V1(SignedQuery(payload, signature.asSignatureOf())) return QueryAndExtractor(query, resultExtractor) } @@ -96,7 +91,7 @@ class QueryBuilder( companion object { @JvmStatic - fun findAllAccounts(queryFilter: GenericValuePredicateBox? = null) = QueryBuilder( + fun findAllAccounts(queryFilter: GenericPredicateBox? = null) = QueryBuilder( Queries.findAllAccounts(), AccountsExtractor, queryFilter @@ -117,7 +112,7 @@ class QueryBuilder( @JvmStatic fun findAccountsByName( name: Name, - queryFilter: GenericValuePredicateBox? = null + queryFilter: GenericPredicateBox? = null ) = QueryBuilder( Queries.findAccountsByName(name), AccountsExtractor, @@ -127,17 +122,17 @@ class QueryBuilder( @JvmStatic fun findAccountsByDomainId( domainId: DomainId, - queryFilter: GenericValuePredicateBox? = null + queryFilter: GenericPredicateBox? = null ) = QueryBuilder(Queries.findAccountsByDomainId(domainId), AccountsExtractor, queryFilter) @JvmStatic fun findAccountsWithAsset( definitionId: AssetDefinitionId, - queryFilter: GenericValuePredicateBox? = null + queryFilter: GenericPredicateBox? = null ) = QueryBuilder(Queries.findAccountsWithAsset(definitionId), AccountsExtractor, queryFilter) @JvmStatic - fun findAllAssets(queryFilter: GenericValuePredicateBox? = null) = QueryBuilder( + fun findAllAssets(queryFilter: GenericPredicateBox? = null) = QueryBuilder( Queries.findAllAssets(), AssetsExtractor, queryFilter @@ -145,7 +140,7 @@ class QueryBuilder( @JvmStatic fun findAllAssetsDefinitions( - queryFilter: GenericValuePredicateBox? = null + queryFilter: GenericPredicateBox? = null ) = QueryBuilder( Queries.findAllAssetsDefinitions(), AssetDefinitionsExtractor, @@ -155,7 +150,7 @@ class QueryBuilder( @JvmStatic fun findAssetsByName( name: Name, - queryFilter: GenericValuePredicateBox? = null + queryFilter: GenericPredicateBox? = null ) = QueryBuilder( Queries.findAssetsByName(name), AssetsExtractor, @@ -165,7 +160,7 @@ class QueryBuilder( @JvmStatic fun findAssetsByAccountId( accountId: AccountId, - queryFilter: GenericValuePredicateBox? = null + queryFilter: GenericPredicateBox? = null ) = QueryBuilder( Queries.findAssetsByAccountId(accountId), AssetsExtractor, @@ -181,7 +176,7 @@ class QueryBuilder( @JvmStatic fun findAssetsByDomainId( domainId: DomainId, - queryFilter: GenericValuePredicateBox? = null + queryFilter: GenericPredicateBox? = null ) = QueryBuilder( Queries.findAssetsByDomainId(domainId), AssetsExtractor, @@ -197,7 +192,7 @@ class QueryBuilder( @JvmStatic fun findAllAssetsDefinitions( assetDefinition: AssetDefinitionId, - queryFilter: GenericValuePredicateBox? = null + queryFilter: GenericPredicateBox? = null ) = QueryBuilder( Queries.findAssetsByAssetDefinitionId(assetDefinition), AssetDefinitionsExtractor, @@ -208,7 +203,7 @@ class QueryBuilder( fun findAssetsByDomainIdAndAssetDefinitionId( domainId: DomainId, assetDefinition: AssetDefinitionId, - queryFilter: GenericValuePredicateBox? = null + queryFilter: GenericPredicateBox? = null ) = QueryBuilder( Queries.findAssetsByDomainIdAndAssetDefinitionId(domainId, assetDefinition), AssetsExtractor, @@ -262,20 +257,20 @@ class QueryBuilder( @JvmStatic fun findAllDomains( - queryFilter: GenericValuePredicateBox? = null + queryFilter: GenericPredicateBox? = null ) = QueryBuilder(Queries.findAllDomains(), DomainsExtractor, queryFilter) @JvmStatic fun findDomainById(domainId: DomainId) = QueryBuilder(Queries.findDomainById(domainId), DomainExtractor) @JvmStatic - fun findAllPeers(queryFilter: GenericValuePredicateBox? = null) = + fun findAllPeers(queryFilter: GenericPredicateBox? = null) = QueryBuilder(Queries.findAllPeers(), PeersExtractor, queryFilter) @JvmStatic fun findTransactionsByAccountId( accountId: AccountId, - queryFilter: GenericValuePredicateBox? = null + queryFilter: GenericPredicateBox? = null ) = QueryBuilder( Queries.findTransactionsByAccountId(accountId), TransactionValuesExtractor, @@ -285,7 +280,7 @@ class QueryBuilder( @JvmStatic fun findPermissionTokensByAccountId( accountId: AccountId, - queryFilter: GenericValuePredicateBox? = null + queryFilter: GenericPredicateBox? = null ) = QueryBuilder( Queries.findPermissionTokensByAccountId(accountId), PermissionTokensExtractor, @@ -294,7 +289,7 @@ class QueryBuilder( @JvmStatic fun findAllPermissionTokenDefinitions( - queryFilter: GenericValuePredicateBox? = null + queryFilter: GenericPredicateBox? = null ) = QueryBuilder( Queries.findAllPermissionTokenDefinitions(), PermissionTokenDefinitionsExtractor, @@ -302,7 +297,7 @@ class QueryBuilder( ) @JvmStatic - fun findRolesByAccountId(accountId: AccountId, queryFilter: GenericValuePredicateBox? = null) = + fun findRolesByAccountId(accountId: AccountId, queryFilter: GenericPredicateBox? = null) = QueryBuilder( Queries.findRolesByAccountId(accountId), RoleIdsExtractor, @@ -310,21 +305,21 @@ class QueryBuilder( ) @JvmStatic - fun findAllRoleIds(queryFilter: GenericValuePredicateBox? = null) = QueryBuilder( + fun findAllRoleIds(queryFilter: GenericPredicateBox? = null) = QueryBuilder( Queries.findAllRoleIds(), RoleIdsExtractor, queryFilter ) @JvmStatic - fun findAllRoles(queryFilter: GenericValuePredicateBox? = null) = QueryBuilder( + fun findAllRoles(queryFilter: GenericPredicateBox? = null) = QueryBuilder( Queries.findAllRoles(), RolesExtractor, queryFilter ) @JvmStatic - fun findRoleByRoleId(roleId: RoleId, queryFilter: GenericValuePredicateBox? = null) = + fun findRoleByRoleId(roleId: RoleId, queryFilter: GenericPredicateBox? = null) = QueryBuilder( Queries.findRoleByRoleId(roleId), RoleExtractor, @@ -344,30 +339,30 @@ class QueryBuilder( fun findTransactionByHash(hex: String) = findTransactionByHash(hex.fromHex().hash().toIrohaHash()) @JvmStatic - fun findAllTransactions(queryFilter: GenericValuePredicateBox? = null) = QueryBuilder( + fun findAllTransactions(queryFilter: GenericPredicateBox? = null) = QueryBuilder( Queries.findAllTransactions(), TransactionQueryResultExtractor, queryFilter ) @JvmStatic - fun findAllBlocks(queryFilter: GenericValuePredicateBox? = null) = QueryBuilder( + fun findAllBlocks(queryFilter: GenericPredicateBox? = null) = QueryBuilder( Queries.findAllBlocks(), BlocksValueExtractor, queryFilter ) @JvmStatic - fun findAllBlockHeaders(queryFilter: GenericValuePredicateBox? = null) = QueryBuilder( + fun findAllBlockHeaders(queryFilter: GenericPredicateBox? = null) = QueryBuilder( Queries.findAllBlockHeaders(), - BlockHeadersValueExtractor, + BlockHeadersExtractor, queryFilter ) @JvmStatic fun findBlockHeaderByHash(hash: Hash) = QueryBuilder( Queries.findBlockHeaderByHash(hash), - BlockHeaderValueExtractor + BlockHeaderExtractor ) @JvmStatic @@ -379,7 +374,7 @@ class QueryBuilder( @JvmStatic fun findTriggerById(id: TriggerId) = QueryBuilder( Queries.findTriggerById(id), - TriggerExtractor + TriggerBoxExtractor ) @JvmStatic @@ -389,7 +384,7 @@ class QueryBuilder( ) @JvmStatic - fun findAllActiveTriggerIds(queryFilter: GenericValuePredicateBox? = null) = QueryBuilder( + fun findAllActiveTriggerIds(queryFilter: GenericPredicateBox? = null) = QueryBuilder( Queries.findAllActiveTriggerIds(), TriggerIdsExtractor, queryFilter @@ -398,15 +393,15 @@ class QueryBuilder( @JvmStatic fun findTriggersByDomainId( domainId: DomainId, - queryFilter: GenericValuePredicateBox? = null + queryFilter: GenericPredicateBox? = null ) = QueryBuilder( Queries.findTriggersByDomainId(domainId), - TriggersExtractor, + TriggerBoxesExtractor, queryFilter ) @JvmStatic - fun findAllParameters(queryFilter: GenericValuePredicateBox? = null) = QueryBuilder( + fun findAllParameters(queryFilter: GenericPredicateBox? = null) = QueryBuilder( Queries.findAllParameters(), ValueExtractor, queryFilter @@ -419,4 +414,4 @@ class QueryBuilder( * * [R] is a type of extracted value as a result of query execution */ -class QueryAndExtractor(val query: VersionedSignedQueryRequest, val resultExtractor: ResultExtractor) +class QueryAndExtractor(val query: VersionedSignedQuery, val resultExtractor: ResultExtractor) diff --git a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/transaction/Filters.kt b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/transaction/Filters.kt index 6953b0a61..1cc3a7c97 100644 --- a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/transaction/Filters.kt +++ b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/transaction/Filters.kt @@ -1,64 +1,64 @@ package jp.co.soramitsu.iroha2.transaction +import jp.co.soramitsu.iroha2.generated.AccountEventFilter +import jp.co.soramitsu.iroha2.generated.AccountFilter +import jp.co.soramitsu.iroha2.generated.AccountId +import jp.co.soramitsu.iroha2.generated.AssetDefinitionEventFilter +import jp.co.soramitsu.iroha2.generated.AssetDefinitionFilter +import jp.co.soramitsu.iroha2.generated.AssetEventFilter +import jp.co.soramitsu.iroha2.generated.AssetFilter +import jp.co.soramitsu.iroha2.generated.DataEntityFilter +import jp.co.soramitsu.iroha2.generated.DomainEventFilter +import jp.co.soramitsu.iroha2.generated.DomainFilter import jp.co.soramitsu.iroha2.generated.Duration -import jp.co.soramitsu.iroha2.generated.datamodel.account.AccountId -import jp.co.soramitsu.iroha2.generated.datamodel.events.EventsFilterBox -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.events.account.AccountEventFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.events.account.AccountFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.events.asset.AssetDefinitionEventFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.events.asset.AssetDefinitionFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.events.asset.AssetEventFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.events.asset.AssetFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.events.domain.DomainEventFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.events.domain.DomainFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.events.peer.PeerEventFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.events.peer.PeerFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.events.role.RoleEventFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.events.role.RoleFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.events.trigger.TriggerEventFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.events.trigger.TriggerFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.EntityFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptAccountEventFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptAccountFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptAssetDefinitionEventFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptAssetDefinitionFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptAssetEventFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptAssetFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptDomainEventFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptDomainFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptEntityFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptOriginFilterAccountEvent -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptOriginFilterAssetDefinitionEvent -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptOriginFilterAssetEvent -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptOriginFilterDomainEvent -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptOriginFilterPeerEvent -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptOriginFilterRoleEvent -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptOriginFilterTriggerEvent -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptPeerEventFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptPeerFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptRoleEventFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptRoleFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptTriggerEventFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.FilterOptTriggerFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.OriginFilterAccountEvent -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.OriginFilterAssetDefinitionEvent -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.OriginFilterAssetEvent -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.OriginFilterDomainEvent -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.OriginFilterPeerEvent -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.OriginFilterRoleEvent -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.filters.OriginFilterTriggerEvent -import jp.co.soramitsu.iroha2.generated.datamodel.events.executetrigger.ExecuteTriggerEventFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.pipeline.EntityKind -import jp.co.soramitsu.iroha2.generated.datamodel.events.pipeline.PipelineEventFilter -import jp.co.soramitsu.iroha2.generated.datamodel.events.pipeline.StatusKind -import jp.co.soramitsu.iroha2.generated.datamodel.events.time.ExecutionTime -import jp.co.soramitsu.iroha2.generated.datamodel.events.time.Schedule -import jp.co.soramitsu.iroha2.generated.datamodel.events.time.TimeEventFilter -import jp.co.soramitsu.iroha2.generated.datamodel.predicate.GenericValuePredicateBox -import jp.co.soramitsu.iroha2.generated.datamodel.predicate.nontrivial.NonTrivial -import jp.co.soramitsu.iroha2.generated.datamodel.predicate.string.StringPredicate -import jp.co.soramitsu.iroha2.generated.datamodel.predicate.value.ValuePredicate -import jp.co.soramitsu.iroha2.generated.datamodel.trigger.TriggerId +import jp.co.soramitsu.iroha2.generated.ExecuteTriggerEventFilter +import jp.co.soramitsu.iroha2.generated.ExecutionTime +import jp.co.soramitsu.iroha2.generated.FilterBox +import jp.co.soramitsu.iroha2.generated.FilterOptOfAccountEventFilter +import jp.co.soramitsu.iroha2.generated.FilterOptOfAccountFilter +import jp.co.soramitsu.iroha2.generated.FilterOptOfAssetDefinitionEventFilter +import jp.co.soramitsu.iroha2.generated.FilterOptOfAssetDefinitionFilter +import jp.co.soramitsu.iroha2.generated.FilterOptOfAssetEventFilter +import jp.co.soramitsu.iroha2.generated.FilterOptOfAssetFilter +import jp.co.soramitsu.iroha2.generated.FilterOptOfDataEntityFilter +import jp.co.soramitsu.iroha2.generated.FilterOptOfDomainEventFilter +import jp.co.soramitsu.iroha2.generated.FilterOptOfDomainFilter +import jp.co.soramitsu.iroha2.generated.FilterOptOfOriginFilterOfAccountEvent +import jp.co.soramitsu.iroha2.generated.FilterOptOfOriginFilterOfAssetDefinitionEvent +import jp.co.soramitsu.iroha2.generated.FilterOptOfOriginFilterOfAssetEvent +import jp.co.soramitsu.iroha2.generated.FilterOptOfOriginFilterOfDomainEvent +import jp.co.soramitsu.iroha2.generated.FilterOptOfOriginFilterOfPeerEvent +import jp.co.soramitsu.iroha2.generated.FilterOptOfOriginFilterOfRoleEvent +import jp.co.soramitsu.iroha2.generated.FilterOptOfOriginFilterOfTriggerEvent +import jp.co.soramitsu.iroha2.generated.FilterOptOfPeerEventFilter +import jp.co.soramitsu.iroha2.generated.FilterOptOfPeerFilter +import jp.co.soramitsu.iroha2.generated.FilterOptOfRoleEventFilter +import jp.co.soramitsu.iroha2.generated.FilterOptOfRoleFilter +import jp.co.soramitsu.iroha2.generated.FilterOptOfTriggerEventFilter +import jp.co.soramitsu.iroha2.generated.FilterOptOfTriggerFilter +import jp.co.soramitsu.iroha2.generated.GenericPredicateBox +import jp.co.soramitsu.iroha2.generated.NonTrivial +import jp.co.soramitsu.iroha2.generated.OriginFilterOfAccountEvent +import jp.co.soramitsu.iroha2.generated.OriginFilterOfAssetDefinitionEvent +import jp.co.soramitsu.iroha2.generated.OriginFilterOfAssetEvent +import jp.co.soramitsu.iroha2.generated.OriginFilterOfDomainEvent +import jp.co.soramitsu.iroha2.generated.OriginFilterOfPeerEvent +import jp.co.soramitsu.iroha2.generated.OriginFilterOfRoleEvent +import jp.co.soramitsu.iroha2.generated.OriginFilterOfTriggerEvent +import jp.co.soramitsu.iroha2.generated.PeerEventFilter +import jp.co.soramitsu.iroha2.generated.PeerFilter +import jp.co.soramitsu.iroha2.generated.PipelineEntityKind +import jp.co.soramitsu.iroha2.generated.PipelineEventFilter +import jp.co.soramitsu.iroha2.generated.PipelineStatusKind +import jp.co.soramitsu.iroha2.generated.RoleEventFilter +import jp.co.soramitsu.iroha2.generated.RoleFilter +import jp.co.soramitsu.iroha2.generated.Schedule +import jp.co.soramitsu.iroha2.generated.StringPredicate +import jp.co.soramitsu.iroha2.generated.TimeEventFilter +import jp.co.soramitsu.iroha2.generated.TriggerEventFilter +import jp.co.soramitsu.iroha2.generated.TriggerFilter +import jp.co.soramitsu.iroha2.generated.TriggerId +import jp.co.soramitsu.iroha2.generated.ValuePredicate import jp.co.soramitsu.iroha2.toIrohaHash /** @@ -70,19 +70,15 @@ object Filters { /** * Create a data filter */ - fun data(entityFilter: EntityFilter? = null): EventsFilterBox.Data { - return EventsFilterBox.Data( - entityFilter?.let { FilterOptEntityFilter.BySome(it) } - ?: FilterOptEntityFilter.AcceptAll() - ) - } + fun data(entityFilter: DataEntityFilter? = null) = FilterBox.Data( + entityFilter?.let { FilterOptOfDataEntityFilter.BySome(it) } + ?: FilterOptOfDataEntityFilter.AcceptAll() + ) /** * Create a [time based event filter][TimeEventFilter] */ - fun time(eventFilter: TimeEventFilter): EventsFilterBox.Time { - return EventsFilterBox.Time(eventFilter) - } + fun time(eventFilter: TimeEventFilter) = FilterBox.Time(eventFilter) /** * Execute a given trigger based on a specified [authority] @@ -90,32 +86,28 @@ object Filters { fun executeTrigger( triggerId: TriggerId, authority: AccountId - ): EventsFilterBox.ExecuteTrigger { - return EventsFilterBox.ExecuteTrigger( - ExecuteTriggerEventFilter(triggerId, authority) - ) - } + ) = FilterBox.ExecuteTrigger( + ExecuteTriggerEventFilter(triggerId, authority) + ) /** * Create an event filter that matches events based on associated [entityKind], [statusKind], [hash], * or any combination of these. * - * @see jp.co.soramitsu.iroha2.generated.datamodel.events.pipeline.StatusKind - * @see jp.co.soramitsu.iroha2.generated.datamodel.events.pipeline.EntityKind + * @see jp.co.soramitsu.iroha2.generated.pipeline.StatusKind + * @see jp.co.soramitsu.iroha2.generated.pipeline.EntityKind */ fun pipeline( - entityKind: EntityKind? = null, - statusKind: StatusKind? = null, + entityKind: PipelineEntityKind? = null, + statusKind: PipelineStatusKind? = null, hash: ByteArray? = null - ): EventsFilterBox.Pipeline { - return EventsFilterBox.Pipeline( - PipelineEventFilter( - entityKind, - statusKind, - hash?.toIrohaHash() - ) + ) = FilterBox.Pipeline( + PipelineEventFilter( + entityKind, + statusKind, + hash?.toIrohaHash() ) - } + ) } /** @@ -126,206 +118,164 @@ object EntityFilters { /** * Match events associated with asset definition and apply another filter referenced by - * its [id][FilterOptOriginFilterAssetDefinitionEvent] or [event type][AssetDefinitionEventFilter] + * its [id][OriginFilterOfAssetDefinitionEvent] or [event type][AssetDefinitionEventFilter] */ fun byAssetDefinition( - originFilter: OriginFilterAssetDefinitionEvent? = null, + originFilter: OriginFilterOfAssetDefinitionEvent? = null, eventFilter: AssetDefinitionEventFilter? = null - ): EntityFilter.ByAssetDefinition { - return EntityFilter.ByAssetDefinition( - FilterOptAssetDefinitionFilter.BySome( - AssetDefinitionFilter( - originFilter?.let { FilterOptOriginFilterAssetDefinitionEvent.BySome(it) } - ?: FilterOptOriginFilterAssetDefinitionEvent.AcceptAll(), - eventFilter?.let { FilterOptAssetDefinitionEventFilter.BySome(it) } - ?: FilterOptAssetDefinitionEventFilter.AcceptAll() - ) + ) = DataEntityFilter.ByAssetDefinition( + FilterOptOfAssetDefinitionFilter.BySome( + AssetDefinitionFilter( + originFilter?.let { FilterOptOfOriginFilterOfAssetDefinitionEvent.BySome(it) } + ?: FilterOptOfOriginFilterOfAssetDefinitionEvent.AcceptAll(), + eventFilter?.let { FilterOptOfAssetDefinitionEventFilter.BySome(it) } + ?: FilterOptOfAssetDefinitionEventFilter.AcceptAll() ) ) - } + ) /** * Match events associated with asset definition */ - fun byAssetDefinition(): EntityFilter.ByAssetDefinition { - return EntityFilter.ByAssetDefinition( - FilterOptAssetDefinitionFilter.AcceptAll() - ) - } + fun byAssetDefinition() = DataEntityFilter.ByAssetDefinition(FilterOptOfAssetDefinitionFilter.AcceptAll()) /** * Match events associated with accounts and apply another filter referenced by - * its [id][OriginFilterAccountEvent] or [event type][AccountEventFilter] + * its [id][OriginFilterOfAccountEvent] or [event type][AccountEventFilter] */ fun byAccount( - idFilter: OriginFilterAccountEvent? = null, + idFilter: OriginFilterOfAccountEvent? = null, eventFilter: AccountEventFilter? = null - ): EntityFilter.ByAccount { - return EntityFilter.ByAccount( - FilterOptAccountFilter.BySome( - AccountFilter( - idFilter?.let { FilterOptOriginFilterAccountEvent.BySome(it) } - ?: FilterOptOriginFilterAccountEvent.AcceptAll(), - eventFilter?.let { FilterOptAccountEventFilter.BySome(it) } - ?: FilterOptAccountEventFilter.AcceptAll() - ) + ) = DataEntityFilter.ByAccount( + FilterOptOfAccountFilter.BySome( + AccountFilter( + idFilter?.let { FilterOptOfOriginFilterOfAccountEvent.BySome(it) } + ?: FilterOptOfOriginFilterOfAccountEvent.AcceptAll(), + eventFilter?.let { FilterOptOfAccountEventFilter.BySome(it) } + ?: FilterOptOfAccountEventFilter.AcceptAll() ) ) - } + ) /** * Match events associated with accounts */ - fun byAccount(): EntityFilter.ByAccount { - return EntityFilter.ByAccount( - FilterOptAccountFilter.AcceptAll() - ) - } + fun byAccount() = DataEntityFilter.ByAccount(FilterOptOfAccountFilter.AcceptAll()) /** * Match events associated with assets and apply another filter referenced by - * its [id][FilterOptOriginFilterAssetEvent] or [event type][AssetEventFilter] + * its [id][FilterOptOfOriginFilterOfAssetEvent] or [event type][AssetEventFilter] */ fun byAsset( - assetFilter: OriginFilterAssetEvent? = null, + assetFilter: OriginFilterOfAssetEvent? = null, eventFilter: AssetEventFilter? = null - ): EntityFilter.ByAsset { - return EntityFilter.ByAsset( - FilterOptAssetFilter.BySome( - AssetFilter( - assetFilter?.let { FilterOptOriginFilterAssetEvent.BySome(it) } - ?: FilterOptOriginFilterAssetEvent.AcceptAll(), - eventFilter?.let { FilterOptAssetEventFilter.BySome(it) } - ?: FilterOptAssetEventFilter.AcceptAll() - ) + ) = DataEntityFilter.ByAsset( + FilterOptOfAssetFilter.BySome( + AssetFilter( + assetFilter?.let { FilterOptOfOriginFilterOfAssetEvent.BySome(it) } + ?: FilterOptOfOriginFilterOfAssetEvent.AcceptAll(), + eventFilter?.let { FilterOptOfAssetEventFilter.BySome(it) } + ?: FilterOptOfAssetEventFilter.AcceptAll() ) ) - } + ) /** * Match events associated with assets */ - fun byAsset(): EntityFilter.ByAsset { - return EntityFilter.ByAsset( - FilterOptAssetFilter.AcceptAll() - ) - } + fun byAsset() = DataEntityFilter.ByAsset(FilterOptOfAssetFilter.AcceptAll()) /** * Match events associated with triggers and apply another filter referenced by - * its [id][FilterOptOriginFilterTriggerEvent] or [event type][TriggerEventFilter] + * its [id][FilterOptOfOriginFilterOfTriggerEvent] or [event type][TriggerEventFilter] */ fun byTrigger( - idFilter: OriginFilterTriggerEvent? = null, + idFilter: OriginFilterOfTriggerEvent? = null, eventFilter: TriggerEventFilter? = null - ): EntityFilter.ByTrigger { - return EntityFilter.ByTrigger( - FilterOptTriggerFilter.BySome( - TriggerFilter( - idFilter?.let { FilterOptOriginFilterTriggerEvent.BySome(it) } - ?: FilterOptOriginFilterTriggerEvent.AcceptAll(), - eventFilter?.let { FilterOptTriggerEventFilter.BySome(it) } - ?: FilterOptTriggerEventFilter.AcceptAll() - ) + ) = DataEntityFilter.ByTrigger( + FilterOptOfTriggerFilter.BySome( + TriggerFilter( + idFilter?.let { FilterOptOfOriginFilterOfTriggerEvent.BySome(it) } + ?: FilterOptOfOriginFilterOfTriggerEvent.AcceptAll(), + eventFilter?.let { FilterOptOfTriggerEventFilter.BySome(it) } + ?: FilterOptOfTriggerEventFilter.AcceptAll() ) ) - } + ) /** * Match events associated with triggers */ - fun byTrigger(): EntityFilter.ByTrigger { - return EntityFilter.ByTrigger( - FilterOptTriggerFilter.AcceptAll() - ) - } + fun byTrigger() = DataEntityFilter.ByTrigger(FilterOptOfTriggerFilter.AcceptAll()) /** * Match events associated with domains and apply another filter referenced by - * its [id][FilterOptOriginFilterDomainEvent] or [event type][DomainEventFilter] + * its [id][FilterOptOfOriginFilterOfDomainEvent] or [event type][DomainEventFilter] */ fun byDomain( - originFilter: OriginFilterDomainEvent? = null, + originFilter: OriginFilterOfDomainEvent? = null, eventFilter: DomainEventFilter? = null - ): EntityFilter.ByDomain { - return EntityFilter.ByDomain( - FilterOptDomainFilter.BySome( - DomainFilter( - originFilter?.let { FilterOptOriginFilterDomainEvent.BySome(it) } - ?: FilterOptOriginFilterDomainEvent.AcceptAll(), - eventFilter?.let { FilterOptDomainEventFilter.BySome(it) } - ?: FilterOptDomainEventFilter.AcceptAll() - ) + ) = DataEntityFilter.ByDomain( + FilterOptOfDomainFilter.BySome( + DomainFilter( + originFilter?.let { FilterOptOfOriginFilterOfDomainEvent.BySome(it) } + ?: FilterOptOfOriginFilterOfDomainEvent.AcceptAll(), + eventFilter?.let { FilterOptOfDomainEventFilter.BySome(it) } + ?: FilterOptOfDomainEventFilter.AcceptAll() ) ) - } + ) /** * Match events associated with domains */ - fun byDomain(): EntityFilter.ByDomain { - return EntityFilter.ByDomain( - FilterOptDomainFilter.AcceptAll() - ) - } + fun byDomain() = DataEntityFilter.ByDomain(FilterOptOfDomainFilter.AcceptAll()) /** * Match events associated with peers and apply another filter referenced by - * its [id][PeerEventOriginFilter] or [event type][PeerEventFilter] + * its [id][FilterOptOfOriginFilterOfPeerEvent] or [event type][PeerEventFilter] */ fun byPeer( - originFilter: OriginFilterPeerEvent? = null, + originFilter: OriginFilterOfPeerEvent? = null, eventFilter: PeerEventFilter? = null - ): EntityFilter.ByPeer { - return EntityFilter.ByPeer( - FilterOptPeerFilter.BySome( - PeerFilter( - originFilter?.let { FilterOptOriginFilterPeerEvent.BySome(it) } - ?: FilterOptOriginFilterPeerEvent.AcceptAll(), - eventFilter?.let { FilterOptPeerEventFilter.BySome(it) } - ?: FilterOptPeerEventFilter.AcceptAll() - ) + ) = DataEntityFilter.ByPeer( + FilterOptOfPeerFilter.BySome( + PeerFilter( + originFilter?.let { FilterOptOfOriginFilterOfPeerEvent.BySome(it) } + ?: FilterOptOfOriginFilterOfPeerEvent.AcceptAll(), + eventFilter?.let { FilterOptOfPeerEventFilter.BySome(it) } + ?: FilterOptOfPeerEventFilter.AcceptAll() ) ) - } + ) /** * Match events associated with peers */ - fun byPeer(): EntityFilter.ByPeer { - return EntityFilter.ByPeer( - FilterOptPeerFilter.AcceptAll() - ) - } + fun byPeer() = DataEntityFilter.ByPeer(FilterOptOfPeerFilter.AcceptAll()) /** * Match events associated with roles and apply another filter referenced by * its [id][FilterOptOriginFilterRoleEvent] or [event type][RoleEventFilter] */ fun byRole( - originFilter: OriginFilterRoleEvent? = null, + originFilter: OriginFilterOfRoleEvent? = null, eventFilter: RoleEventFilter? = null - ): EntityFilter.ByRole { - return EntityFilter.ByRole( - FilterOptRoleFilter.BySome( - RoleFilter( - originFilter?.let { FilterOptOriginFilterRoleEvent.BySome(it) } - ?: FilterOptOriginFilterRoleEvent.AcceptAll(), - eventFilter?.let { FilterOptRoleEventFilter.BySome(it) } - ?: FilterOptRoleEventFilter.AcceptAll() - ) + ) = DataEntityFilter.ByRole( + FilterOptOfRoleFilter.BySome( + RoleFilter( + originFilter?.let { FilterOptOfOriginFilterOfRoleEvent.BySome(it) } + ?: FilterOptOfOriginFilterOfRoleEvent.AcceptAll(), + eventFilter?.let { FilterOptOfRoleEventFilter.BySome(it) } + ?: FilterOptOfRoleEventFilter.AcceptAll() ) ) - } + ) /** * Match events associated with roles */ - fun byRole(): EntityFilter.ByRole { - return EntityFilter.ByRole( - FilterOptRoleFilter.AcceptAll() - ) - } + fun byRole() = DataEntityFilter.ByRole(FilterOptOfRoleFilter.AcceptAll()) } /** @@ -339,22 +289,12 @@ object EventFilters { fun timeEventFilter( start: Duration, period: Duration? = null - ): TimeEventFilter { - return TimeEventFilter( - ExecutionTime.Schedule( - Schedule(start, period) - ) - ) - } + ) = TimeEventFilter(ExecutionTime.Schedule(Schedule(start, period))) /** * Create a pre-commit filter */ - fun timeEventFilter(): TimeEventFilter { - return TimeEventFilter( - ExecutionTime.PreCommit() - ) - } + fun timeEventFilter() = TimeEventFilter(ExecutionTime.PreCommit()) } /** @@ -365,28 +305,28 @@ object QueryFilters { /** * Starts with filter */ - fun startsWith(prefix: String) = GenericValuePredicateBox.Raw( + fun startsWith(prefix: String) = GenericPredicateBox.Raw( ValuePredicate.Identifiable(StringPredicate.StartsWith(prefix)) ) /** * Ends with filter */ - fun endsWith(suffix: String) = GenericValuePredicateBox.Raw( + fun endsWith(suffix: String) = GenericPredicateBox.Raw( ValuePredicate.Identifiable(StringPredicate.EndsWith(suffix)) ) /** * Contains filter */ - fun contains(value: String) = GenericValuePredicateBox.Raw( + fun contains(value: String) = GenericPredicateBox.Raw( ValuePredicate.Identifiable(StringPredicate.Contains(value)) ) /** * Is filter */ - fun `is`(value: String) = GenericValuePredicateBox.Raw( + fun `is`(value: String) = GenericPredicateBox.Raw( ValuePredicate.Identifiable(StringPredicate.Is(value)) ) @@ -394,15 +334,15 @@ object QueryFilters { * Filter for multiple matches (OR) */ fun or(vararg predicates: StringPredicate) = predicates - .map { GenericValuePredicateBox.Raw(ValuePredicate.Identifiable(it)) }.toList() - .let { NonTrivial>(it) } - .let { GenericValuePredicateBox.Or(it) } + .map { GenericPredicateBox.Raw(ValuePredicate.Identifiable(it)) }.toList() + .let { NonTrivial>(it) } + .let { GenericPredicateBox.Or(it) } /** * Filter for multiple matches (AND) */ fun and(vararg predicates: StringPredicate) = predicates - .map { GenericValuePredicateBox.Raw(ValuePredicate.Identifiable(it)) }.toList() - .let { NonTrivial>(it) } - .let { GenericValuePredicateBox.And(it) } + .map { GenericPredicateBox.Raw(ValuePredicate.Identifiable(it)) }.toList() + .let { NonTrivial>(it) } + .let { GenericPredicateBox.And(it) } } diff --git a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/transaction/Instructions.kt b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/transaction/Instructions.kt index b7404087d..230e04ded 100644 --- a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/transaction/Instructions.kt +++ b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/transaction/Instructions.kt @@ -1,67 +1,63 @@ package jp.co.soramitsu.iroha2.transaction -import jp.co.soramitsu.iroha2.DigestFunction import jp.co.soramitsu.iroha2.IdKey import jp.co.soramitsu.iroha2.Permissions import jp.co.soramitsu.iroha2.asName import jp.co.soramitsu.iroha2.asValue import jp.co.soramitsu.iroha2.cast import jp.co.soramitsu.iroha2.evaluatesTo -import jp.co.soramitsu.iroha2.generated.crypto.PublicKey -import jp.co.soramitsu.iroha2.generated.datamodel.IdBox -import jp.co.soramitsu.iroha2.generated.datamodel.RegistrableBox -import jp.co.soramitsu.iroha2.generated.datamodel.Value -import jp.co.soramitsu.iroha2.generated.datamodel.ValueKind -import jp.co.soramitsu.iroha2.generated.datamodel.account.AccountId -import jp.co.soramitsu.iroha2.generated.datamodel.account.NewAccount -import jp.co.soramitsu.iroha2.generated.datamodel.asset.Asset -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetDefinitionId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetValue -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetValueType -import jp.co.soramitsu.iroha2.generated.datamodel.asset.Mintable -import jp.co.soramitsu.iroha2.generated.datamodel.asset.NewAssetDefinition -import jp.co.soramitsu.iroha2.generated.datamodel.domain.DomainId -import jp.co.soramitsu.iroha2.generated.datamodel.domain.IpfsPath -import jp.co.soramitsu.iroha2.generated.datamodel.domain.NewDomain -import jp.co.soramitsu.iroha2.generated.datamodel.events.EventsFilterBox -import jp.co.soramitsu.iroha2.generated.datamodel.events.time.TimeEventFilter -import jp.co.soramitsu.iroha2.generated.datamodel.isi.BurnBox -import jp.co.soramitsu.iroha2.generated.datamodel.isi.ExecuteTriggerBox -import jp.co.soramitsu.iroha2.generated.datamodel.isi.FailBox -import jp.co.soramitsu.iroha2.generated.datamodel.isi.GrantBox -import jp.co.soramitsu.iroha2.generated.datamodel.isi.If -import jp.co.soramitsu.iroha2.generated.datamodel.isi.Instruction -import jp.co.soramitsu.iroha2.generated.datamodel.isi.MintBox -import jp.co.soramitsu.iroha2.generated.datamodel.isi.Pair -import jp.co.soramitsu.iroha2.generated.datamodel.isi.RegisterBox -import jp.co.soramitsu.iroha2.generated.datamodel.isi.RemoveKeyValueBox -import jp.co.soramitsu.iroha2.generated.datamodel.isi.SequenceBox -import jp.co.soramitsu.iroha2.generated.datamodel.isi.SetKeyValueBox -import jp.co.soramitsu.iroha2.generated.datamodel.isi.TransferBox -import jp.co.soramitsu.iroha2.generated.datamodel.isi.UnregisterBox -import jp.co.soramitsu.iroha2.generated.datamodel.metadata.Metadata -import jp.co.soramitsu.iroha2.generated.datamodel.name.Name -import jp.co.soramitsu.iroha2.generated.datamodel.peer.Peer -import jp.co.soramitsu.iroha2.generated.datamodel.peer.PeerId -import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.Definition -import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.Token -import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.TokenId -import jp.co.soramitsu.iroha2.generated.datamodel.role.NewRole -import jp.co.soramitsu.iroha2.generated.datamodel.role.Role -import jp.co.soramitsu.iroha2.generated.datamodel.role.RoleId -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.Executable -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.WasmSmartContract -import jp.co.soramitsu.iroha2.generated.datamodel.trigger.Trigger -import jp.co.soramitsu.iroha2.generated.datamodel.trigger.TriggerId -import jp.co.soramitsu.iroha2.generated.datamodel.trigger.action.Action -import jp.co.soramitsu.iroha2.generated.datamodel.trigger.action.Repeats -import jp.co.soramitsu.iroha2.toValueId +import jp.co.soramitsu.iroha2.generated.AccountId +import jp.co.soramitsu.iroha2.generated.ActionOfFilterBoxAndExecutable +import jp.co.soramitsu.iroha2.generated.Algorithm +import jp.co.soramitsu.iroha2.generated.Asset +import jp.co.soramitsu.iroha2.generated.AssetDefinitionId +import jp.co.soramitsu.iroha2.generated.AssetId +import jp.co.soramitsu.iroha2.generated.AssetValue +import jp.co.soramitsu.iroha2.generated.AssetValueType +import jp.co.soramitsu.iroha2.generated.BurnBox +import jp.co.soramitsu.iroha2.generated.Conditional +import jp.co.soramitsu.iroha2.generated.DomainId +import jp.co.soramitsu.iroha2.generated.Executable +import jp.co.soramitsu.iroha2.generated.ExecuteTriggerBox +import jp.co.soramitsu.iroha2.generated.FailBox +import jp.co.soramitsu.iroha2.generated.FilterBox +import jp.co.soramitsu.iroha2.generated.GrantBox +import jp.co.soramitsu.iroha2.generated.IdBox +import jp.co.soramitsu.iroha2.generated.InstructionBox +import jp.co.soramitsu.iroha2.generated.IpfsPath +import jp.co.soramitsu.iroha2.generated.Metadata +import jp.co.soramitsu.iroha2.generated.MintBox +import jp.co.soramitsu.iroha2.generated.Mintable +import jp.co.soramitsu.iroha2.generated.Name +import jp.co.soramitsu.iroha2.generated.NewAccount +import jp.co.soramitsu.iroha2.generated.NewAssetDefinition +import jp.co.soramitsu.iroha2.generated.NewDomain +import jp.co.soramitsu.iroha2.generated.NewRole +import jp.co.soramitsu.iroha2.generated.Pair +import jp.co.soramitsu.iroha2.generated.Peer +import jp.co.soramitsu.iroha2.generated.PeerId +import jp.co.soramitsu.iroha2.generated.PermissionToken +import jp.co.soramitsu.iroha2.generated.PermissionTokenDefinition +import jp.co.soramitsu.iroha2.generated.PermissionTokenId +import jp.co.soramitsu.iroha2.generated.PublicKey +import jp.co.soramitsu.iroha2.generated.RegisterBox +import jp.co.soramitsu.iroha2.generated.RegistrableBox +import jp.co.soramitsu.iroha2.generated.RemoveKeyValueBox +import jp.co.soramitsu.iroha2.generated.Repeats +import jp.co.soramitsu.iroha2.generated.Role +import jp.co.soramitsu.iroha2.generated.RoleId +import jp.co.soramitsu.iroha2.generated.SequenceBox +import jp.co.soramitsu.iroha2.generated.SetKeyValueBox +import jp.co.soramitsu.iroha2.generated.TimeEventFilter +import jp.co.soramitsu.iroha2.generated.TransferBox +import jp.co.soramitsu.iroha2.generated.TriggerId +import jp.co.soramitsu.iroha2.generated.TriggerOfFilterBoxAndExecutable +import jp.co.soramitsu.iroha2.generated.UnregisterBox +import jp.co.soramitsu.iroha2.generated.Value +import jp.co.soramitsu.iroha2.generated.ValueKind +import jp.co.soramitsu.iroha2.generated.WasmSmartContract +import jp.co.soramitsu.iroha2.toSocketAddr import java.math.BigDecimal -import java.math.BigInteger - -val COUNT_PARAM_NAME by lazy { "count".asName() } -val PERIOD_PARAM_NAME by lazy { "period".asName() } /** * Iroha Special Instructions cover all possible actions within a blockchain @@ -74,15 +70,9 @@ object Instructions { */ fun registerRole( roleId: RoleId, - vararg tokens: Token - ): Instruction.Register { - return registerSome { - RegistrableBox.Role( - NewRole( - Role(roleId, tokens.toList()) - ) - ) - } + vararg tokens: PermissionToken + ) = registerSome { + RegistrableBox.Role(NewRole(Role(roleId, tokens.toList()))) } /** @@ -93,66 +83,55 @@ object Instructions { id: AccountId, signatories: List, metadata: Metadata = Metadata(mapOf()) - ): Instruction.Register { - return registerSome { - RegistrableBox.Account( - NewAccount(id, signatories, metadata) - ) - } + ) = registerSome { + RegistrableBox.Account(NewAccount(id, signatories, metadata)) } /** * Register a permission token */ fun registerPermissionToken( - permissionsId: TokenId, + permissionsId: PermissionTokenId, params: Map = mapOf() - ): Instruction.Register { - return registerSome { - RegistrableBox.PermissionTokenDefinition(Definition(permissionsId, params)) - } + ) = registerSome { + RegistrableBox.PermissionTokenDefinition(PermissionTokenDefinition(permissionsId, params)) } - fun registerPermissionToken(permission: Permissions, idKey: IdKey): Instruction.Register { - return registerPermissionToken(permission.type, idKey.type) - } + fun registerPermissionToken( + permission: Permissions, + idKey: IdKey + ) = registerPermissionToken(permission.type, idKey.type) - fun registerPermissionToken(name: Name, idKey: IdKey): Instruction.Register { - return registerPermissionToken(name, idKey.type) - } + fun registerPermissionToken(name: Name, idKey: IdKey) = registerPermissionToken(name, idKey.type) - fun registerPermissionToken(name: Name, idKey: String): Instruction.Register { - return registerPermissionToken( - TokenId(name), - mapOf(idKey.asName() to ValueKind.Id()) - ) - } + fun registerPermissionToken(name: Name, idKey: String) = registerPermissionToken( + PermissionTokenId(name), + mapOf(idKey.asName() to ValueKind.Id()) + ) /** * Register a time trigger */ fun registerTimeTrigger( triggerId: TriggerId, - isi: List, + isi: List, repeats: Repeats, accountId: AccountId, filter: TimeEventFilter, metadata: Metadata - ): Instruction { - return registerSome { - RegistrableBox.Trigger( - Trigger( - triggerId, - Action( - Executable.Instructions(isi), - repeats, - accountId, - EventsFilterBox.Time(filter), - metadata - ) + ) = registerSome { + RegistrableBox.Trigger( + TriggerOfFilterBoxAndExecutable( + triggerId, + ActionOfFilterBoxAndExecutable( + Executable.Instructions(isi), + repeats, + accountId, + FilterBox.Time(filter), + metadata ) ) - } + ) } /** @@ -160,25 +139,23 @@ object Instructions { */ fun registerExecutableTrigger( triggerId: TriggerId, - isi: List, + isi: List, repeats: Repeats, accountId: AccountId, metadata: Metadata - ): Instruction.Register { - return registerSome { - RegistrableBox.Trigger( - Trigger( - triggerId, - Action( - Executable.Instructions(isi), - repeats, - accountId, - Filters.executeTrigger(triggerId, accountId), - metadata - ) + ) = registerSome { + RegistrableBox.Trigger( + TriggerOfFilterBoxAndExecutable( + triggerId, + ActionOfFilterBoxAndExecutable( + Executable.Instructions(isi), + repeats, + accountId, + Filters.executeTrigger(triggerId, accountId), + metadata ) ) - } + ) } /** @@ -186,26 +163,24 @@ object Instructions { */ fun registerEventTrigger( triggerId: TriggerId, - isi: List, + isi: List, repeats: Repeats, accountId: AccountId, metadata: Metadata, - filter: EventsFilterBox - ): Instruction.Register { - return registerSome { - RegistrableBox.Trigger( - Trigger( - triggerId, - Action( - Executable.Instructions(isi), - repeats, - accountId, - filter, - metadata - ) + filter: FilterBox + ) = registerSome { + RegistrableBox.Trigger( + TriggerOfFilterBoxAndExecutable( + triggerId, + ActionOfFilterBoxAndExecutable( + Executable.Instructions(isi), + repeats, + accountId, + filter, + metadata ) ) - } + ) } /** @@ -217,24 +192,20 @@ object Instructions { repeats: Repeats, accountId: AccountId, metadata: Metadata, - filter: EventsFilterBox - ): Instruction.Register { - return registerSome { - RegistrableBox.Trigger( - Trigger( - triggerId, - Action( - Executable.Wasm( - WasmSmartContract(wasm) - ), - repeats, - accountId, - filter, - metadata - ) + filter: FilterBox + ) = registerSome { + RegistrableBox.Trigger( + TriggerOfFilterBoxAndExecutable( + triggerId, + ActionOfFilterBoxAndExecutable( + Executable.Wasm(WasmSmartContract(wasm)), + repeats, + accountId, + filter, + metadata ) ) - } + ) } /** @@ -242,37 +213,29 @@ object Instructions { */ fun registerPreCommitTrigger( triggerId: TriggerId, - isi: List, + isi: List, repeats: Repeats, accountId: AccountId, metadata: Metadata - ): Instruction.Register { - return registerSome { - RegistrableBox.Trigger( - Trigger( - triggerId, - Action( - Executable.Instructions(isi), - repeats, - accountId, - Filters.time(EventFilters.timeEventFilter()), - metadata - ) + ) = registerSome { + RegistrableBox.Trigger( + TriggerOfFilterBoxAndExecutable( + triggerId, + ActionOfFilterBoxAndExecutable( + Executable.Instructions(isi), + repeats, + accountId, + Filters.time(EventFilters.timeEventFilter()), + metadata ) ) - } + ) } /** * Unregister a trigger */ - fun unregisterTrigger( - id: TriggerId - ): Instruction.Unregister { - return unregisterSome { - IdBox.TriggerId(id) - } - } + fun unregisterTrigger(id: TriggerId) = unregisterSome { IdBox.TriggerId(id) } /** * Unregister a trigger @@ -280,34 +243,24 @@ object Instructions { fun unregisterTrigger( triggerName: String, domainId: DomainId? = null - ): Instruction.Unregister { - return unregisterSome { - IdBox.TriggerId( - TriggerId(triggerName.asName(), domainId) - ) - } + ) = unregisterSome { + IdBox.TriggerId(TriggerId(triggerName.asName(), domainId)) } /** * Unregister an asset */ - fun unregisterAsset(id: AssetId): Instruction.Unregister { - return unregisterSome { IdBox.AssetId(id) } - } + fun unregisterAsset(id: AssetId) = unregisterSome { IdBox.AssetId(id) } /** * Unregister an account */ - fun unregisterAccount(id: AccountId): Instruction.Unregister { - return unregisterSome { IdBox.AccountId(id) } - } + fun unregisterAccount(id: AccountId) = unregisterSome { IdBox.AccountId(id) } /** * Unregister a domain */ - fun unregisterDomain(id: DomainId): Instruction.Unregister { - return unregisterSome { IdBox.DomainId(id) } - } + fun unregisterDomain(id: DomainId) = unregisterSome { IdBox.DomainId(id) } /** * Register an asset @@ -318,21 +271,17 @@ object Instructions { assetValueType: AssetValueType, metadata: Metadata = Metadata(mapOf()), mintable: Mintable = Mintable.Infinitely() - ): Instruction.Register { - return registerSome { - RegistrableBox.AssetDefinition( - NewAssetDefinition(id, assetValueType, mintable, metadata) - ) - } + ) = registerSome { + RegistrableBox.AssetDefinition( + NewAssetDefinition(id, assetValueType, mintable, metadata = metadata) + ) } /** * Register an asset */ - fun registerAsset(id: AssetId, assetValue: AssetValue): Instruction.Register { - return registerSome { - RegistrableBox.Asset(Asset(id, assetValue)) - } + fun registerAsset(id: AssetId, assetValue: AssetValue) = registerSome { + RegistrableBox.Asset(Asset(id, assetValue)) } /** @@ -343,12 +292,8 @@ object Instructions { domainId: DomainId, metadata: Map = mapOf(), logo: IpfsPath? = null - ): Instruction.Register { - return registerSome { - RegistrableBox.Domain( - NewDomain(domainId, logo, Metadata(metadata)) - ) - } + ) = registerSome { + RegistrableBox.Domain(NewDomain(domainId, logo, Metadata(metadata))) } /** @@ -358,18 +303,11 @@ object Instructions { fun registerPeer( address: String, payload: ByteArray, - digestFunction: String = DigestFunction.Ed25519.hashFunName - ): Instruction.Register { - return registerSome { - RegistrableBox.Peer( - Peer( - PeerId( - address, - PublicKey(digestFunction, payload) - ) - ) - ) - } + digestFunction: Algorithm = Algorithm.Ed25519() + ) = registerSome { + RegistrableBox.Peer( + Peer(PeerId(address.toSocketAddr(), PublicKey(digestFunction, payload))) + ) } /** @@ -379,13 +317,11 @@ object Instructions { fun unregisterPeer( address: String, payload: ByteArray, - digestFunction: String = DigestFunction.Ed25519.hashFunName - ): Instruction.Unregister { - return unregisterSome { - IdBox.PeerId( - PeerId(address, PublicKey(digestFunction, payload)) - ) - } + digestFunction: Algorithm = Algorithm.Ed25519() + ) = unregisterSome { + IdBox.PeerId( + PeerId(address.toSocketAddr(), PublicKey(digestFunction, payload)) + ) } /** @@ -395,15 +331,13 @@ object Instructions { assetId: AssetId, key: Name, value: Value - ): Instruction.SetKeyValue { - return Instruction.SetKeyValue( - SetKeyValueBox( - objectId = IdBox.AssetId(assetId).evaluatesTo(), - key = key.evaluatesTo(), - value = value.evaluatesTo() - ) + ) = InstructionBox.SetKeyValue( + SetKeyValueBox( + objectId = IdBox.AssetId(assetId).evaluatesTo(), + key = key.evaluatesTo(), + value = value.evaluatesTo() ) - } + ) /** * Set key/value for a given asset definition @@ -412,15 +346,13 @@ object Instructions { definitionId: AssetDefinitionId, key: Name, value: Value - ): Instruction.SetKeyValue { - return Instruction.SetKeyValue( - SetKeyValueBox( - objectId = IdBox.AssetDefinitionId(definitionId).evaluatesTo(), - key = key.evaluatesTo(), - value = value.evaluatesTo() - ) + ) = InstructionBox.SetKeyValue( + SetKeyValueBox( + objectId = IdBox.AssetDefinitionId(definitionId).evaluatesTo(), + key = key.evaluatesTo(), + value = value.evaluatesTo() ) - } + ) /** * Set key/value in the metadata of a given account @@ -429,27 +361,23 @@ object Instructions { accountId: AccountId, key: Name, value: Value - ): Instruction.SetKeyValue { - return Instruction.SetKeyValue( - SetKeyValueBox( - objectId = IdBox.AccountId(accountId).evaluatesTo(), - key = key.evaluatesTo(), - value = value.evaluatesTo() - ) + ) = InstructionBox.SetKeyValue( + SetKeyValueBox( + objectId = IdBox.AccountId(accountId).evaluatesTo(), + key = key.evaluatesTo(), + value = value.evaluatesTo() ) - } + ) /** * Remove key/value from a given asset */ - fun removeKeyValue(assetId: AssetId, key: Name): Instruction.RemoveKeyValue { - return Instruction.RemoveKeyValue( - RemoveKeyValueBox( - objectId = IdBox.AssetId(assetId).evaluatesTo(), - key = key.evaluatesTo() - ) + fun removeKeyValue(assetId: AssetId, key: Name) = InstructionBox.RemoveKeyValue( + RemoveKeyValueBox( + objectId = IdBox.AssetId(assetId).evaluatesTo(), + key = key.evaluatesTo() ) - } + ) /** * Set key/value in the metadata of a given domain @@ -458,354 +386,149 @@ object Instructions { domainId: DomainId, key: Name, value: Value - ): Instruction.SetKeyValue { - return Instruction.SetKeyValue( - SetKeyValueBox( - objectId = IdBox.DomainId(domainId).evaluatesTo(), - key = key.evaluatesTo(), - value = value.evaluatesTo() - ) + ) = InstructionBox.SetKeyValue( + SetKeyValueBox( + objectId = IdBox.DomainId(domainId).evaluatesTo(), + key = key.evaluatesTo(), + value = value.evaluatesTo() ) - } + ) /** * Execute a trigger */ - fun executeTrigger(triggerId: TriggerId): Instruction.ExecuteTrigger { - return Instruction.ExecuteTrigger(ExecuteTriggerBox(triggerId)) - } + fun executeTrigger(triggerId: TriggerId) = InstructionBox.ExecuteTrigger(ExecuteTriggerBox(triggerId.evaluatesTo())) /** * Mint an asset of the [AssetValueType.Quantity] asset value type */ - fun mintAsset( - assetId: AssetId, - quantity: Int - ): Instruction.Mint { - return mintSome(quantity.asValue(), assetId) - } + fun mintAsset(assetId: AssetId, quantity: Int) = mintSome(quantity.asValue(), assetId) /** * Mint an asset of the [AssetValueType.Fixed] asset value type */ - fun mintAsset( - assetId: AssetId, - quantity: BigDecimal - ): Instruction.Mint { - return mintSome(quantity.asValue(), assetId) - } + fun mintAsset(assetId: AssetId, quantity: BigDecimal) = mintSome(quantity.asValue(), assetId) /** * Mint a public key */ - fun mintPublicKey(accountId: AccountId, pubKey: PublicKey): Instruction { - return mintSome( - Value.PublicKey(pubKey), - IdBox.AccountId(accountId) - ) - } + fun mintPublicKey(accountId: AccountId, pubKey: PublicKey) = mintSome( + Value.PublicKey(pubKey), + IdBox.AccountId(accountId) + ) /** * Burn an asset of the [AssetValueType.Quantity] asset value type */ - fun burnAsset(assetId: AssetId, value: Int): Instruction { - return burnSome(value.asValue(), IdBox.AssetId(assetId)) - } + fun burnAsset(assetId: AssetId, value: Int) = burnSome(value.asValue(), IdBox.AssetId(assetId)) /** * Burn an asset of the [AssetValueType.Fixed] asset value type */ - fun burnAsset(assetId: AssetId, value: BigDecimal): Instruction { - return burnSome(value.asValue(), IdBox.AssetId(assetId)) - } + fun burnAsset(assetId: AssetId, value: BigDecimal) = burnSome(value.asValue(), IdBox.AssetId(assetId)) /** * Burn a public key */ - fun burnPublicKey(accountId: AccountId, pubKey: PublicKey): Instruction { - return burnSome( - Value.PublicKey(pubKey), - IdBox.AccountId(accountId) - ) - } + fun burnPublicKey(accountId: AccountId, pubKey: PublicKey) = burnSome( + Value.PublicKey(pubKey), + IdBox.AccountId(accountId) + ) fun removePublicKey(accountId: AccountId, pubKey: PublicKey) = burnPublicKey(accountId, pubKey) - /** - * Grant an account the [Permissions.CanSetKeyValueUserAssetsToken] permission - */ - fun grantSetKeyValueAsset(assetId: AssetId, target: AccountId): Instruction { - return grantSome(IdBox.AccountId(target)) { - Token( - definitionId = TokenId(Permissions.CanSetKeyValueUserAssetsToken.type), - params = mapOf(IdKey.AssetId.type.asName() to assetId.toValueId()) - ) - } - } - - /** - * Grant an account the [Permissions.CanRemoveKeyValueInUserAssets] permission - */ - fun grantRemoveKeyValueAsset(assetId: AssetId, target: AccountId): Instruction { - return grantSome(IdBox.AccountId(target)) { - Token( - definitionId = TokenId(Permissions.CanRemoveKeyValueInUserAssets.type), - params = mapOf(IdKey.AssetId.type.asName() to assetId.toValueId()) - ) - } - } - - /** - * Grant an account the [Permissions.CanSetKeyValueInUserMetadata] permission - */ - fun grantSetKeyValueMetadata(accountId: AccountId, target: AccountId): Instruction { - return grantSome(IdBox.AccountId(target)) { - Token( - definitionId = TokenId(Permissions.CanSetKeyValueInUserMetadata.type), - params = mapOf(IdKey.AccountId.type.asName() to accountId.toValueId()) - ) - } - } - - /** - * Grant an account the [Permissions.CanRemoveKeyValueInUserMetadata] permission - */ - fun grantRemoveKeyValueMetadata(accountId: AccountId, target: AccountId): Instruction { - return grantSome(IdBox.AccountId(target)) { - Token( - definitionId = TokenId(Permissions.CanRemoveKeyValueInUserMetadata.type), - params = mapOf(IdKey.AccountId.type.asName() to accountId.toValueId()) - ) - } - } - - /** - * Grant an account the [Permissions.CanSetKeyValueInAssetDefinition] permission - */ - fun grantSetKeyValueAssetDefinition(assetDefinitionId: AssetDefinitionId, target: AccountId): Instruction { - return grantSome(IdBox.AccountId(target)) { - Token( - definitionId = TokenId(Permissions.CanSetKeyValueInAssetDefinition.type), - params = mapOf( - IdKey.AssetDefinitionId.type.asName() to assetDefinitionId.toValueId() - ) - ) - } - } - - /** - * Grant an account the [Permissions.CanRemoveKeyValueInAssetDefinition] permission - */ - fun grantRemoveKeyValueAssetDefinition(assetDefinitionId: AssetDefinitionId, target: AccountId): Instruction { - return grantSome(IdBox.AccountId(target)) { - Token( - definitionId = TokenId(Permissions.CanRemoveKeyValueInAssetDefinition.type), - params = mapOf( - IdKey.AssetDefinitionId.type.asName() to assetDefinitionId.toValueId() - ) - ) - } - } - - /** - * Grant an account the [Permissions.CanMintUserAssetDefinitionsToken] permission - */ - fun grantMintUserAssetDefinitions(assetDefinitionId: AssetDefinitionId, target: AccountId): Instruction { - return grantSome(IdBox.AccountId(target)) { - Token( - definitionId = TokenId(Permissions.CanMintUserAssetDefinitionsToken.type), - params = mapOf( - IdKey.AssetDefinitionId.type.asName() to assetDefinitionId.toValueId() - ) - ) - } - } - - /** - * Grant an account the [Permissions.CanTransferOnlyFixedNumberOfTimesPerPeriod] permission - */ - fun grantTransferOnlyFixedNumberOfTimesPerPeriod(period: BigInteger, count: Long, target: AccountId): Instruction { - return grantSome(IdBox.AccountId(target)) { - Token( - definitionId = TokenId(Permissions.CanTransferOnlyFixedNumberOfTimesPerPeriod.type), - params = mapOf( - PERIOD_PARAM_NAME to period.asValue(), - COUNT_PARAM_NAME to count.asValue() - ) - ) - } - } - - /** - * Grant an account the [Permissions.CanBurnAssetWithDefinition] permission - */ - fun grantBurnAssetWithDefinitionId(assetDefinitionId: AssetDefinitionId, target: AccountId): Instruction { - return grantSome(IdBox.AccountId(target)) { - Token( - definitionId = TokenId(Permissions.CanBurnAssetWithDefinition.type), - params = mapOf( - IdKey.AssetDefinitionId.type.asName() to assetDefinitionId.toValueId() - ) - ) - } - } - - /** - * Grant an account the [Permissions.CanBurnUserAssetsToken] permission - */ - fun grantBurnAssets(assetId: AssetId, target: AccountId): Instruction { - return grantSome(IdBox.AccountId(target)) { - Token( - definitionId = TokenId(Permissions.CanBurnUserAssetsToken.type), - params = mapOf( - IdKey.AssetId.type.asName() to assetId.toValueId() - ) - ) - } - } - - /** - * Grant an account the [Permissions.CanRegisterDomainsToken] permission - */ - fun grantRegisterDomains(target: AccountId): Instruction { - return grantSome(IdBox.AccountId(target)) { - Token( - definitionId = TokenId(Permissions.CanRegisterDomainsToken.type), - params = emptyMap() - ) - } - } - /** * Grant an account the [Permissions.CanTransferUserAssetsToken] permission */ - fun grantTransferUserAsset(assetId: AssetId, target: AccountId): Instruction { - return grantSome(IdBox.AccountId(target)) { - Token( - definitionId = TokenId(Permissions.CanTransferUserAssetsToken.type), - params = mapOf( - IdKey.AssetId.type.asName() to assetId.toValueId() - ) - ) - } - } - - /** - * Grant an account the [Permissions.CanUnregisterAssetWithDefinition] permission - */ - fun grantUnregisterAssetDefinition(assetDefinitionId: AssetDefinitionId, target: AccountId): Instruction { - return grantSome(IdBox.AccountId(target)) { - Token( - definitionId = TokenId(Permissions.CanUnregisterAssetWithDefinition.type), - params = mapOf( - IdKey.AssetDefinitionId.type.asName() to assetDefinitionId.toValueId() - ) - ) - } + fun grantPermissionToken( + permission: Permissions, + params: Map, + target: AccountId + ) = grantSome(IdBox.AccountId(target)) { + PermissionToken(definitionId = PermissionTokenId(permission.type), params = params) } /** * Grant an account a given role. */ - fun grantRole(roleId: RoleId, accountId: AccountId): Instruction { - return Instruction.Grant( - GrantBox( - destinationId = accountId.evaluatesTo().cast(), - `object` = IdBox.RoleId(roleId).evaluatesTo().cast() - ) + fun grantRole(roleId: RoleId, accountId: AccountId) = InstructionBox.Grant( + GrantBox( + destinationId = accountId.evaluatesTo().cast(), + `object` = IdBox.RoleId(roleId).evaluatesTo().cast() ) - } + ) /** * Transfer an asset from the identifiable source. */ - fun transferAsset(sourceId: AssetId, value: Int, destinationId: AssetId): Instruction { - return Instruction.Transfer( - TransferBox( - sourceId = IdBox.AssetId(sourceId).evaluatesTo(), - `object` = value.asValue().evaluatesTo(), - destinationId = IdBox.AssetId(destinationId).evaluatesTo() - ) + fun transferAsset(sourceId: AssetId, value: Int, destinationId: AccountId) = InstructionBox.Transfer( + TransferBox( + sourceId = IdBox.AssetId(sourceId).evaluatesTo(), + `object` = value.asValue().evaluatesTo(), + destinationId = IdBox.AccountId(destinationId).evaluatesTo() ) - } + ) /** * Evaluate one instruction if a [condition] is met and another one otherwise. */ - fun `if`(condition: Boolean, then: Instruction, otherwise: Instruction): Instruction { - return Instruction.If( - If(condition.evaluatesTo(), then, otherwise) - ) - } + fun `if`( + condition: Boolean, + then: InstructionBox, + otherwise: InstructionBox + ) = InstructionBox.If(Conditional(condition.evaluatesTo(), then, otherwise)) /** * Pair two instructions together. */ - fun pair(left: Instruction, right: Instruction): Instruction { - return Instruction.Pair(Pair(left, right)) - } + fun pair(left: InstructionBox, right: InstructionBox) = InstructionBox.Pair(Pair(left, right)) /** * Combine multiple [instructions] into a sequence. */ - fun sequence(instructions: List): Instruction { - return Instruction.Sequence(SequenceBox(instructions)) - } + fun sequence(instructions: List) = InstructionBox.Sequence(SequenceBox(instructions)) /** * Fail a transaction with a given [message]. */ - fun fail(message: String): Instruction { - return Instruction.Fail(FailBox(message)) - } + fun fail(message: String) = InstructionBox.Fail(FailBox(message)) - private inline fun unregisterSome(idBox: () -> IdBox): Instruction.Unregister { - return Instruction.Unregister( - UnregisterBox(idBox().evaluatesTo()) - ) - } + private inline fun unregisterSome(idBox: () -> IdBox) = InstructionBox.Unregister( + UnregisterBox(idBox().evaluatesTo()) + ) private inline fun registerSome( regBox: () -> RegistrableBox - ): Instruction.Register { - return Instruction.Register( - RegisterBox(regBox().evaluatesTo()) + ) = InstructionBox.Register(RegisterBox(regBox().evaluatesTo())) + + private inline fun grantSome( + idBox: IdBox, + permissionToken: () -> PermissionToken + ) = InstructionBox.Grant( + GrantBox( + destinationId = idBox.evaluatesTo(), + `object` = Value.PermissionToken(permissionToken()).evaluatesTo() ) - } + ) - private inline fun grantSome(idBox: IdBox, permissionToken: () -> Token): Instruction.Grant { - return Instruction.Grant( - GrantBox( - destinationId = idBox.evaluatesTo(), - `object` = Value.PermissionToken(permissionToken()).evaluatesTo() - ) + private fun burnSome(value: Value, idBox: IdBox) = InstructionBox.Burn( + BurnBox( + `object` = value.evaluatesTo(), + destinationId = idBox.evaluatesTo() ) - } + ) - private fun burnSome(value: Value, idBox: IdBox): Instruction.Burn { - return Instruction.Burn( - BurnBox( - `object` = value.evaluatesTo(), - destinationId = idBox.evaluatesTo() - ) - ) - } - - private fun mintSome(value: Value, idBox: IdBox): Instruction.Mint { - return Instruction.Mint( - MintBox( - `object` = value.evaluatesTo(), - destinationId = idBox.evaluatesTo() - ) + private fun mintSome(value: Value, idBox: IdBox) = InstructionBox.Mint( + MintBox( + `object` = value.evaluatesTo(), + destinationId = idBox.evaluatesTo() ) - } + ) - private fun mintSome(value: Value, assetId: AssetId): Instruction.Mint { - return Instruction.Mint( - MintBox( - `object` = value.evaluatesTo(), - destinationId = IdBox.AssetId(assetId).evaluatesTo() - ) + private fun mintSome(value: Value, assetId: AssetId) = InstructionBox.Mint( + MintBox( + `object` = value.evaluatesTo(), + destinationId = IdBox.AssetId(assetId).evaluatesTo() ) - } + ) } diff --git a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/transaction/TransactionBuilder.kt b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/transaction/TransactionBuilder.kt index d8b9dac65..ff2e36c3f 100644 --- a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/transaction/TransactionBuilder.kt +++ b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/transaction/TransactionBuilder.kt @@ -1,35 +1,36 @@ package jp.co.soramitsu.iroha2.transaction -import jp.co.soramitsu.iroha2.DigestFunction import jp.co.soramitsu.iroha2.IdKey import jp.co.soramitsu.iroha2.Permissions import jp.co.soramitsu.iroha2.U32_MAX_VALUE import jp.co.soramitsu.iroha2.asName import jp.co.soramitsu.iroha2.asSignatureOf -import jp.co.soramitsu.iroha2.generated.crypto.PublicKey -import jp.co.soramitsu.iroha2.generated.crypto.signature.Signature -import jp.co.soramitsu.iroha2.generated.datamodel.Value -import jp.co.soramitsu.iroha2.generated.datamodel.account.AccountId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetDefinitionId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetValue -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetValueType -import jp.co.soramitsu.iroha2.generated.datamodel.asset.Mintable -import jp.co.soramitsu.iroha2.generated.datamodel.domain.DomainId -import jp.co.soramitsu.iroha2.generated.datamodel.domain.IpfsPath -import jp.co.soramitsu.iroha2.generated.datamodel.events.EventsFilterBox -import jp.co.soramitsu.iroha2.generated.datamodel.events.time.TimeEventFilter -import jp.co.soramitsu.iroha2.generated.datamodel.isi.Instruction -import jp.co.soramitsu.iroha2.generated.datamodel.metadata.Metadata -import jp.co.soramitsu.iroha2.generated.datamodel.name.Name -import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.Token -import jp.co.soramitsu.iroha2.generated.datamodel.role.RoleId -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.Executable -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.Payload -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.SignedTransaction -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.VersionedSignedTransaction -import jp.co.soramitsu.iroha2.generated.datamodel.trigger.TriggerId -import jp.co.soramitsu.iroha2.generated.datamodel.trigger.action.Repeats +import jp.co.soramitsu.iroha2.generated.AccountId +import jp.co.soramitsu.iroha2.generated.Algorithm +import jp.co.soramitsu.iroha2.generated.AssetDefinitionId +import jp.co.soramitsu.iroha2.generated.AssetId +import jp.co.soramitsu.iroha2.generated.AssetValue +import jp.co.soramitsu.iroha2.generated.AssetValueType +import jp.co.soramitsu.iroha2.generated.DomainId +import jp.co.soramitsu.iroha2.generated.Executable +import jp.co.soramitsu.iroha2.generated.FilterBox +import jp.co.soramitsu.iroha2.generated.InstructionBox +import jp.co.soramitsu.iroha2.generated.IpfsPath +import jp.co.soramitsu.iroha2.generated.Metadata +import jp.co.soramitsu.iroha2.generated.Mintable +import jp.co.soramitsu.iroha2.generated.Name +import jp.co.soramitsu.iroha2.generated.PermissionToken +import jp.co.soramitsu.iroha2.generated.PublicKey +import jp.co.soramitsu.iroha2.generated.Repeats +import jp.co.soramitsu.iroha2.generated.RoleId +import jp.co.soramitsu.iroha2.generated.Signature +import jp.co.soramitsu.iroha2.generated.SignaturesOfOfTransactionPayload +import jp.co.soramitsu.iroha2.generated.SignedTransaction +import jp.co.soramitsu.iroha2.generated.TimeEventFilter +import jp.co.soramitsu.iroha2.generated.TransactionPayload +import jp.co.soramitsu.iroha2.generated.TriggerId +import jp.co.soramitsu.iroha2.generated.Value +import jp.co.soramitsu.iroha2.generated.VersionedSignedTransaction import jp.co.soramitsu.iroha2.sign import jp.co.soramitsu.iroha2.toIrohaPublicKey import java.math.BigDecimal @@ -43,7 +44,7 @@ import kotlin.random.nextLong class TransactionBuilder(builder: TransactionBuilder.() -> Unit = {}) { var accountId: AccountId? - val instructions: Lazy> + val instructions: Lazy> var creationTimeMillis: BigInteger? var timeToLiveMillis: BigInteger? var nonce: Long? @@ -63,11 +64,11 @@ class TransactionBuilder(builder: TransactionBuilder.() -> Unit = {}) { fun account(accountName: Name, domainId: DomainId) = this.account(AccountId(accountName, domainId)) - fun instructions(vararg instructions: Instruction) = this.apply { this.instructions.value.addAll(instructions) } + fun instructions(vararg instructions: InstructionBox) = this.apply { this.instructions.value.addAll(instructions) } - fun instructions(instructions: Iterable) = this.apply { this.instructions.value.addAll(instructions) } + fun instructions(instructions: Iterable) = this.apply { this.instructions.value.addAll(instructions) } - fun instruction(instruction: Instruction) = this.apply { this.instructions.value.add(instruction) } + fun instruction(instruction: InstructionBox) = this.apply { this.instructions.value.add(instruction) } fun creationTime(creationTimeMillis: BigInteger) = this.apply { this.creationTimeMillis = creationTimeMillis } @@ -82,7 +83,7 @@ class TransactionBuilder(builder: TransactionBuilder.() -> Unit = {}) { fun timeToLive(ttlMillis: Long) = this.apply { this.timeToLive(ttlMillis.toBigInteger()) } fun buildSigned(vararg keyPairs: KeyPair): VersionedSignedTransaction { - val payload = Payload( + val payload = TransactionPayload( checkNotNull(accountId) { "Account Id of the sender is mandatory" }, Executable.Instructions(instructions.value), creationTimeMillis ?: fallbackCreationTime(), @@ -90,24 +91,24 @@ class TransactionBuilder(builder: TransactionBuilder.() -> Unit = {}) { nonce, metadata.value ) - val encodedPayload = Payload.encode(payload) + val encodedPayload = TransactionPayload.encode(payload) val signatures = keyPairs.map { Signature( it.public.toIrohaPublicKey(), it.private.sign(encodedPayload) - ).asSignatureOf() + ).asSignatureOf() }.toList() return VersionedSignedTransaction.V1( - SignedTransaction(payload, signatures) + SignedTransaction(payload, SignaturesOfOfTransactionPayload(signatures)) ) } @JvmOverloads fun registerTimeTrigger( triggerId: TriggerId, - isi: List, + isi: List, repeats: Repeats, accountId: AccountId, filter: TimeEventFilter, @@ -128,7 +129,7 @@ class TransactionBuilder(builder: TransactionBuilder.() -> Unit = {}) { @JvmOverloads fun registerExecutableTrigger( triggerId: TriggerId, - isi: List, + isi: List, repeats: Repeats, accountId: AccountId, metadata: Metadata = Metadata(mapOf()) @@ -147,11 +148,11 @@ class TransactionBuilder(builder: TransactionBuilder.() -> Unit = {}) { @JvmOverloads fun registerEventTrigger( triggerId: TriggerId, - isi: List, + isi: List, repeats: Repeats, accountId: AccountId, metadata: Metadata = Metadata(mapOf()), - filter: EventsFilterBox + filter: FilterBox ) = this.apply { instructions.value.add( Instructions.registerEventTrigger( @@ -172,7 +173,7 @@ class TransactionBuilder(builder: TransactionBuilder.() -> Unit = {}) { repeats: Repeats, accountId: AccountId, metadata: Metadata = Metadata(mapOf()), - filter: EventsFilterBox + filter: FilterBox ) = this.apply { instructions.value.add( Instructions.registerWasmTrigger( @@ -189,7 +190,7 @@ class TransactionBuilder(builder: TransactionBuilder.() -> Unit = {}) { @JvmOverloads fun registerPreCommitTrigger( triggerId: TriggerId, - isi: List, + isi: List, repeats: Repeats, accountId: AccountId, metadata: Metadata = Metadata(mapOf()) @@ -240,7 +241,7 @@ class TransactionBuilder(builder: TransactionBuilder.() -> Unit = {}) { fun registerRole( id: RoleId, - vararg tokens: Token + vararg tokens: PermissionToken ) = this.apply { instructions.value.add(Instructions.registerRole(id, *tokens)) } @JvmOverloads @@ -356,57 +357,22 @@ class TransactionBuilder(builder: TransactionBuilder.() -> Unit = {}) { fun registerPeer( address: String, payload: ByteArray, - digestFunction: String = DigestFunction.Ed25519.hashFunName - ) = this.apply { instructions.value.add(Instructions.registerPeer(address, payload, digestFunction)) } + algorithm: Algorithm = Algorithm.Ed25519() + ) = this.apply { instructions.value.add(Instructions.registerPeer(address, payload, algorithm)) } @JvmOverloads fun unregisterPeer( address: String, payload: ByteArray, - digestFunction: String = DigestFunction.Ed25519.hashFunName - ) = this.apply { instructions.value.add(Instructions.unregisterPeer(address, payload, digestFunction)) } + algorithm: Algorithm = Algorithm.Ed25519() + ) = this.apply { instructions.value.add(Instructions.unregisterPeer(address, payload, algorithm)) } - fun grantSetKeyValueAsset(assetId: AssetId, target: AccountId) = - this.apply { instructions.value.add(Instructions.grantSetKeyValueAsset(assetId, target)) } - - fun grantRemoveKeyValueAsset(assetId: AssetId, target: AccountId) = - this.apply { instructions.value.add(Instructions.grantRemoveKeyValueAsset(assetId, target)) } - - fun grantSetKeyValueMetadata(accountId: AccountId, target: AccountId) = - this.apply { instructions.value.add(Instructions.grantSetKeyValueMetadata(accountId, target)) } - - fun grantMintUserAssetDefinitions(assetDefinitionId: AssetDefinitionId, target: AccountId) = - this.apply { instructions.value.add(Instructions.grantMintUserAssetDefinitions(assetDefinitionId, target)) } - - fun grantBurnAssetWithDefinitionId(assetDefinitionId: AssetDefinitionId, target: AccountId) = - this.apply { instructions.value.add(Instructions.grantBurnAssetWithDefinitionId(assetDefinitionId, target)) } - - fun grantSetKeyValueAssetDefinition(assetDefinitionId: AssetDefinitionId, target: AccountId) = this.apply { - instructions.value.add(Instructions.grantSetKeyValueAssetDefinition(assetDefinitionId, target)) - } - - fun grantRemoveKeyValueAssetDefinition(assetDefinitionId: AssetDefinitionId, target: AccountId) = this.apply { - instructions.value.add(Instructions.grantRemoveKeyValueAssetDefinition(assetDefinitionId, target)) - } - - fun grantTransferOnlyFixedNumberOfTimesPerPeriod(period: BigInteger, count: Long, target: AccountId) = this.apply { - instructions.value.add(Instructions.grantTransferOnlyFixedNumberOfTimesPerPeriod(period, count, target)) - } - - fun grantBurnAssets(assetId: AssetId, target: AccountId) = this.apply { - instructions.value.add(Instructions.grantBurnAssets(assetId, target)) - } - - fun grantRegisterDomains(target: AccountId) = this.apply { - instructions.value.add(Instructions.grantRegisterDomains(target)) - } - - fun grantTransferUserAsset(assetId: AssetId, target: AccountId) = this.apply { - instructions.value.add(Instructions.grantTransferUserAsset(assetId, target)) + fun grantPermissionToken(permission: Permissions, params: Pair, target: AccountId) = this.apply { + instructions.value.add(Instructions.grantPermissionToken(permission, mapOf(params), target)) } - fun grantUnregisterAssetDefinition(assetDefinitionId: AssetDefinitionId, target: AccountId) = this.apply { - instructions.value.add(Instructions.grantUnregisterAssetDefinition(assetDefinitionId, target)) + fun grantPermissionToken(permission: Permissions, params: Map, target: AccountId) = this.apply { + instructions.value.add(Instructions.grantPermissionToken(permission, params, target)) } fun burnAsset(assetId: AssetId, value: Int) = this.apply { @@ -427,19 +393,19 @@ class TransactionBuilder(builder: TransactionBuilder.() -> Unit = {}) { fun removePublicKey(accountId: AccountId, pubKey: PublicKey) = burnPublicKey(accountId, pubKey) - fun transferAsset(sourceId: AssetId, value: Int, destinationId: AssetId) = this.apply { + fun transferAsset(sourceId: AssetId, value: Int, destinationId: AccountId) = this.apply { instructions.value.add(Instructions.transferAsset(sourceId, value, destinationId)) } - fun `if`(condition: Boolean, then: Instruction, otherwise: Instruction) = this.apply { + fun `if`(condition: Boolean, then: InstructionBox, otherwise: InstructionBox) = this.apply { instructions.value.add(Instructions.`if`(condition, then, otherwise)) } - fun pair(left: Instruction, right: Instruction) = this.apply { + fun pair(left: InstructionBox, right: InstructionBox) = this.apply { instructions.value.add(Instructions.pair(left, right)) } - fun sequence(vararg instructions: Instruction) = this.apply { + fun sequence(vararg instructions: InstructionBox) = this.apply { this.instructions.value.add(Instructions.sequence(instructions.toList())) } diff --git a/modules/client/src/test/java/jp/co/soramitsu/iroha2/JavaTest.java b/modules/client/src/test/java/jp/co/soramitsu/iroha2/JavaTest.java index c539efb5c..367a209da 100644 --- a/modules/client/src/test/java/jp/co/soramitsu/iroha2/JavaTest.java +++ b/modules/client/src/test/java/jp/co/soramitsu/iroha2/JavaTest.java @@ -1,17 +1,17 @@ package jp.co.soramitsu.iroha2; import jp.co.soramitsu.iroha2.client.Iroha2AsyncClient; -import jp.co.soramitsu.iroha2.generated.datamodel.Value; -import jp.co.soramitsu.iroha2.generated.datamodel.account.Account; -import jp.co.soramitsu.iroha2.generated.datamodel.account.AccountId; -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetId; -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetValue; -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetValueType; -import jp.co.soramitsu.iroha2.generated.datamodel.domain.Domain; -import jp.co.soramitsu.iroha2.generated.datamodel.domain.DomainId; -import jp.co.soramitsu.iroha2.generated.datamodel.metadata.Metadata; -import jp.co.soramitsu.iroha2.generated.datamodel.name.Name; -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.VersionedSignedTransaction; +import jp.co.soramitsu.iroha2.generated.Value; +import jp.co.soramitsu.iroha2.generated.Account; +import jp.co.soramitsu.iroha2.generated.AccountId; +import jp.co.soramitsu.iroha2.generated.AssetId; +import jp.co.soramitsu.iroha2.generated.AssetValue; +import jp.co.soramitsu.iroha2.generated.AssetValueType; +import jp.co.soramitsu.iroha2.generated.Domain; +import jp.co.soramitsu.iroha2.generated.DomainId; +import jp.co.soramitsu.iroha2.generated.Metadata; +import jp.co.soramitsu.iroha2.generated.Name; +import jp.co.soramitsu.iroha2.generated.VersionedSignedTransaction; import jp.co.soramitsu.iroha2.query.QueryAndExtractor; import jp.co.soramitsu.iroha2.query.QueryBuilder; import jp.co.soramitsu.iroha2.testengine.DefaultGenesis; diff --git a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/BlockStreamTest.kt b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/BlockStreamTest.kt index c4d3f1ac2..72129bff1 100644 --- a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/BlockStreamTest.kt +++ b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/BlockStreamTest.kt @@ -6,15 +6,15 @@ import io.qameta.allure.Story import jp.co.soramitsu.iroha2.annotations.Sdk import jp.co.soramitsu.iroha2.annotations.SdkTestId import jp.co.soramitsu.iroha2.client.Iroha2Client -import jp.co.soramitsu.iroha2.generated.core.block.CommittedBlock -import jp.co.soramitsu.iroha2.generated.core.block.VersionedCommittedBlock -import jp.co.soramitsu.iroha2.generated.core.block.stream.VersionedBlockMessage -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetDefinitionId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetValueType -import jp.co.soramitsu.iroha2.generated.datamodel.isi.Instruction -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.Executable -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.Payload -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.VersionedValidTransaction +import jp.co.soramitsu.iroha2.generated.AssetDefinitionId +import jp.co.soramitsu.iroha2.generated.AssetValueType +import jp.co.soramitsu.iroha2.generated.CommittedBlock +import jp.co.soramitsu.iroha2.generated.Executable +import jp.co.soramitsu.iroha2.generated.InstructionBox +import jp.co.soramitsu.iroha2.generated.TransactionPayload +import jp.co.soramitsu.iroha2.generated.VersionedBlockMessage +import jp.co.soramitsu.iroha2.generated.VersionedCommittedBlock +import jp.co.soramitsu.iroha2.generated.VersionedValidTransaction import jp.co.soramitsu.iroha2.testengine.ALICE_ACCOUNT_ID import jp.co.soramitsu.iroha2.testengine.ALICE_KEYPAIR import jp.co.soramitsu.iroha2.testengine.BOB_ACCOUNT @@ -26,7 +26,6 @@ import jp.co.soramitsu.iroha2.testengine.GENESIS import jp.co.soramitsu.iroha2.testengine.IrohaTest import jp.co.soramitsu.iroha2.testengine.NewAccountWithMetadata import jp.co.soramitsu.iroha2.testengine.WithIroha -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Test import kotlin.test.assertEquals @@ -51,18 +50,13 @@ class BlockStreamTest : IrohaTest(account = ALICE_ACCOUNT_ID, keyP var blocks = mutableListOf() blocksResult.collect { block -> blocks.add(block) } - var instructions = checkBlockStructure( - blocks[0], - 1, - GENESIS, - GENESIS, - 4 - ) - val registerDomain = instructions[0].cast().extractDomain().id.name.string + val expectedSize = NewAccountWithMetadata().block.transactions.sumOf { it.size } + var instructions = checkBlockStructure(blocks[0], 1, GENESIS, GENESIS, expectedSize) + val registerDomain = instructions[0].cast().extractDomain().id.name.string assertEquals(DEFAULT_DOMAIN_ID.asString(), registerDomain) - assertEquals(ALICE_ACCOUNT_ID.asString(), instructions[1].cast().extractAccount().id.asString()) - assertEquals(BOB_ACCOUNT_ID.asString(), instructions[2].cast().extractAccount().id.asString()) - assertEquals("foo$ACCOUNT_ID_DELIMITER$DEFAULT_DOMAIN", instructions[3].cast().extractAccount().id.asString()) + assertEquals(ALICE_ACCOUNT_ID.asString(), instructions[1].cast().extractAccount().id.asString()) + assertEquals(BOB_ACCOUNT_ID.asString(), instructions[2].cast().extractAccount().id.asString()) + assertEquals("foo$ACCOUNT_ID_DELIMITER$DEFAULT_DOMAIN", instructions[3].cast().extractAccount().id.asString()) instructions = checkBlockStructure( blocks[1], @@ -71,7 +65,7 @@ class BlockStreamTest : IrohaTest(account = ALICE_ACCOUNT_ID, keyP BOB_ACCOUNT, 1 ) - var newAssetDefinition = instructions[0].cast().extractAssetDefinition() + var newAssetDefinition = instructions[0].cast().extractAssetDefinition() assertNotNull(newAssetDefinition) assertEquals(newAssetName, newAssetDefinition.id.name.string) assertEquals(DEFAULT_DOMAIN, newAssetDefinition.id.domainId.asString()) @@ -87,7 +81,7 @@ class BlockStreamTest : IrohaTest(account = ALICE_ACCOUNT_ID, keyP BOB_ACCOUNT, 1 ) - newAssetDefinition = instructions[0].cast().extractAssetDefinition() + newAssetDefinition = instructions[0].cast().extractAssetDefinition() assertNotNull(newAssetDefinition) assertEquals(newAssetName, newAssetDefinition.id.name.string) assertEquals(DEFAULT_DOMAIN, newAssetDefinition.id.domainId.asString()) @@ -98,7 +92,7 @@ class BlockStreamTest : IrohaTest(account = ALICE_ACCOUNT_ID, keyP .blockMessage.versionedCommittedBlock.cast().committedBlock } - private fun getInstructionPayload(committedBlock: CommittedBlock): Payload { + private fun getInstructionPayload(committedBlock: CommittedBlock): TransactionPayload { return committedBlock.transactions[0] .cast() .validTransaction @@ -111,7 +105,7 @@ class BlockStreamTest : IrohaTest(account = ALICE_ACCOUNT_ID, keyP instructionAccountDomain: String, instructionAccount: String, instructionSize: Int - ): List { + ): List { val committedBlock = getCommittedBlock(blockMessage) assertEquals(height, committedBlock.header.height.toLong()) val payload = getInstructionPayload(committedBlock) diff --git a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/InstructionsTest.kt b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/InstructionsTest.kt index 24e8a35b9..e9ccb0e52 100644 --- a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/InstructionsTest.kt +++ b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/InstructionsTest.kt @@ -7,26 +7,27 @@ import jp.co.soramitsu.iroha2.annotations.Permission import jp.co.soramitsu.iroha2.annotations.Sdk import jp.co.soramitsu.iroha2.annotations.SdkTestId import jp.co.soramitsu.iroha2.client.Iroha2Client -import jp.co.soramitsu.iroha2.generated.crypto.PublicKey -import jp.co.soramitsu.iroha2.generated.datamodel.Value -import jp.co.soramitsu.iroha2.generated.datamodel.account.AccountId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.Asset -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetDefinitionId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetValue -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetValueType -import jp.co.soramitsu.iroha2.generated.datamodel.domain.DomainId -import jp.co.soramitsu.iroha2.generated.datamodel.metadata.Metadata -import jp.co.soramitsu.iroha2.generated.datamodel.name.Name -import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.Token -import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.TokenId -import jp.co.soramitsu.iroha2.generated.datamodel.role.RoleId -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.VersionedSignedTransaction +import jp.co.soramitsu.iroha2.generated.AccountId +import jp.co.soramitsu.iroha2.generated.Asset +import jp.co.soramitsu.iroha2.generated.AssetDefinitionId +import jp.co.soramitsu.iroha2.generated.AssetId +import jp.co.soramitsu.iroha2.generated.AssetValue +import jp.co.soramitsu.iroha2.generated.AssetValueType +import jp.co.soramitsu.iroha2.generated.Metadata +import jp.co.soramitsu.iroha2.generated.Name +import jp.co.soramitsu.iroha2.generated.PermissionToken +import jp.co.soramitsu.iroha2.generated.PermissionTokenId +import jp.co.soramitsu.iroha2.generated.PublicKey +import jp.co.soramitsu.iroha2.generated.RoleId +import jp.co.soramitsu.iroha2.generated.Value +import jp.co.soramitsu.iroha2.generated.VersionedSignedTransaction import jp.co.soramitsu.iroha2.query.QueryBuilder import jp.co.soramitsu.iroha2.testengine.ALICE_ACCOUNT_ID import jp.co.soramitsu.iroha2.testengine.ALICE_KEYPAIR import jp.co.soramitsu.iroha2.testengine.AliceAndBobEachHave100Xor +import jp.co.soramitsu.iroha2.testengine.AliceAndBobHasPermissionToMintPublicKeys import jp.co.soramitsu.iroha2.testengine.AliceHas100XorAndPermissionToBurn +import jp.co.soramitsu.iroha2.testengine.AliceHasPermissionToUnregisterDomain import jp.co.soramitsu.iroha2.testengine.AliceHasRoleWithAccessToBobsMetadata import jp.co.soramitsu.iroha2.testengine.AliceWithTestAssets import jp.co.soramitsu.iroha2.testengine.BOB_ACCOUNT_ID @@ -79,7 +80,7 @@ class InstructionsTest : IrohaTest( @Story("Account registers a domain") @Permission("no_permission_required") @SdkTestId("register_domain") - fun `register domain instruction committed`(): Unit = runBlocking { + fun `register domain`(): Unit = runBlocking { val domainId = "new_domain_name".asDomainId() client.sendTransaction { account(ALICE_ACCOUNT_ID) @@ -107,7 +108,7 @@ class InstructionsTest : IrohaTest( @Story("Account registers an account") @Permission("no_permission_required") @SdkTestId("register_account") - fun `register account instruction committed`(): Unit = runBlocking { + fun `register account`(): Unit = runBlocking { val newAccountId = AccountId("foo".asName(), DEFAULT_DOMAIN_ID) client.sendTransaction { account(ALICE_ACCOUNT_ID) @@ -133,20 +134,28 @@ class InstructionsTest : IrohaTest( @Permission("no_permission_required") @SdkTestId("register_account") @SdkTestId("unregister_account") - fun `register and unregister account instruction committed`(): Unit = runBlocking { - val newAccountId = AccountId("foo".asName(), DEFAULT_DOMAIN_ID) - client.tx { registerAccount(newAccountId, listOf()) } + fun `register and unregister account`(): Unit = runBlocking { + val joeId = AccountId("foo".asName(), DEFAULT_DOMAIN_ID) + val joeKeyPair = generateKeyPair() + client.tx { registerAccount(joeId, listOf(joeKeyPair.public.toIrohaPublicKey())) } - QueryBuilder.findAccountById(newAccountId) + QueryBuilder.findAccountById(joeId) .account(ALICE_ACCOUNT_ID) .buildSigned(ALICE_KEYPAIR) .let { query -> client.sendQuery(query) } - .also { account -> assertEquals(account.id, newAccountId) } + .also { account -> assertEquals(account.id, joeId) } - client.tx { unregisterAccount(newAccountId) } + client.tx(joeId, joeKeyPair) { + grantPermissionToken( + Permissions.CanUnregisterAccount, + IdKey.AccountId.type.asName() to joeId.toValueId(), + ALICE_ACCOUNT_ID + ) + unregisterAccount(joeId) + } assertThrows { runBlocking { - QueryBuilder.findAccountById(newAccountId) + QueryBuilder.findAccountById(joeId) .account(ALICE_ACCOUNT_ID) .buildSigned(ALICE_KEYPAIR) .let { query -> client.sendQuery(query) } @@ -162,7 +171,7 @@ class InstructionsTest : IrohaTest( @Permission("no_permission_required") @SdkTestId("register_asset_definition") @SdkTestId("unregister_asset_definition") - fun `register and unregister asset instruction committed`(): Unit = runBlocking { + fun `register and unregister asset`(): Unit = runBlocking { val definitionId = AssetDefinitionId("XSTUSD".asName(), DEFAULT_DOMAIN_ID) client.tx { registerAssetDefinition(definitionId, AssetValueType.Quantity()) } @@ -187,28 +196,17 @@ class InstructionsTest : IrohaTest( } @Test - @WithIroha([DefaultGenesis::class]) + @WithIroha([AliceHasPermissionToUnregisterDomain::class]) @Feature("Domains") - @Story("Account registers a domain") @Story("Account unregisters a domain") - @Permission("no_permission_required") - @SdkTestId("register_domain") + @Permission("can_unregister_domain") @SdkTestId("unregister_domain") - fun `register and unregister domain instruction committed`(): Unit = runBlocking { - val newDomainId = DomainId("foo".asName()) - client.tx { registerDomain(newDomainId) } - - QueryBuilder.findDomainById(newDomainId) - .account(ALICE_ACCOUNT_ID) - .buildSigned(ALICE_KEYPAIR) - .let { query -> client.sendQuery(query) } - .also { domain -> assertEquals(newDomainId, domain.id) } - - client.tx { unregisterDomain(newDomainId) } + fun `unregister domain`(): Unit = runBlocking { + client.tx { unregisterDomain(AliceHasPermissionToUnregisterDomain.NEW_DOMAIN_ID) } assertThrows { runBlocking { - QueryBuilder.findDomainById(newDomainId) + QueryBuilder.findDomainById(AliceHasPermissionToUnregisterDomain.NEW_DOMAIN_ID) .account(ALICE_ACCOUNT_ID) .buildSigned(ALICE_KEYPAIR) .let { query -> client.sendQuery(query) } @@ -222,7 +220,7 @@ class InstructionsTest : IrohaTest( @Story("Account registers an account") @Permission("no_permission_required") @SdkTestId("register_account_with_metadata") - fun `register account with metadata instruction committed`(): Unit = runBlocking { + fun `register account with metadata`(): Unit = runBlocking { val newAccountId = AccountId("foo".asName(), DEFAULT_DOMAIN_ID) val addressKey = "address".asName() val phoneKey = "phone".asName() @@ -240,12 +238,10 @@ class InstructionsTest : IrohaTest( Pair(cityKey, cityValue), ), ) - val encodedTx = TransactionBuilder { account(ALICE_ACCOUNT_ID) registerAccount(newAccountId, listOf(), metadata) - }.buildSigned() - .let { VersionedSignedTransaction.encode(it) } + }.buildSigned().let { VersionedSignedTransaction.encode(it) } val decodedTx = encodedTx.let { VersionedSignedTransaction.decode(it) } val signedTx = decodedTx.appendSignatures(ALICE_KEYPAIR) @@ -277,7 +273,7 @@ class InstructionsTest : IrohaTest( @Story("Account registers an asset definition") @Permission("no_permission_required") @SdkTestId("DEPRECATE CANDIDATE") - fun `register asset instruction committed`(): Unit = runBlocking { + fun `register asset`(): Unit = runBlocking { client.sendTransaction { account(ALICE_ACCOUNT_ID) registerAssetDefinition(DEFAULT_ASSET_DEFINITION_ID, AssetValueType.Quantity()) @@ -302,7 +298,7 @@ class InstructionsTest : IrohaTest( @Story("Account registers an asset definition") @Permission("no_permission_required") @SdkTestId("register_asset_definition_with_store_value_type") - fun `store asset instruction committed`(): Unit = runBlocking { + fun `store asset`(): Unit = runBlocking { val pair1 = "key1".asName() to "bar".asValue() val pair2 = "key2".asName() to true.asValue() val pair3 = "key3".asName() to 12345.asValue() @@ -371,8 +367,11 @@ class InstructionsTest : IrohaTest( client.tx { registerAssetDefinition(aliceAssetId.definitionId, AssetValueType.Store()) // grant by Alice to Bob permissions to set key value in Asset.Store - registerPermissionToken(Permissions.CanSetKeyValueUserAssetsToken.type, IdKey.AssetId) - grantSetKeyValueAsset(aliceAssetId, BOB_ACCOUNT_ID) + grantPermissionToken( + Permissions.CanSetKeyValueUserAssetsToken, + IdKey.AssetId.type.asName() to aliceAssetId.asValue(), + BOB_ACCOUNT_ID + ) } client.tx(BOB_ACCOUNT_ID, BOB_KEYPAIR) { setKeyValue(aliceAssetId, "foo".asName(), "bar".asValue()) @@ -404,7 +403,7 @@ class InstructionsTest : IrohaTest( @Story("Account mints an asset") @Permission("no_permission_required") @SdkTestId("mint_asset_for_account_in_same_domain") - fun `mint asset instruction committed`(): Unit = runBlocking { + fun `mint asset`(): Unit = runBlocking { client.sendTransaction { account(ALICE_ACCOUNT_ID) registerAssetDefinition(DEFAULT_ASSET_DEFINITION_ID, AssetValueType.Quantity()) @@ -430,7 +429,7 @@ class InstructionsTest : IrohaTest( @Story("Account burn an asset") @Permission("no_permission_required") @SdkTestId("burn_asset_for_account_in_same_domain") - fun `burn asset instruction committed`(): Unit = runBlocking { + fun `burn asset`(): Unit = runBlocking { // check balance before burn val query = QueryBuilder.findAccountById(ALICE_ACCOUNT_ID) .account(ALICE_ACCOUNT_ID) @@ -455,8 +454,11 @@ class InstructionsTest : IrohaTest( client.tx { registerAssetDefinition(DEFAULT_ASSET_DEFINITION_ID, AssetValueType.Quantity()) mintAsset(DEFAULT_ASSET_ID, 100) - registerPermissionToken(Permissions.CanBurnAssetWithDefinition.type, IdKey.AssetDefinitionId) - grantBurnAssetWithDefinitionId(DEFAULT_ASSET_DEFINITION_ID, BOB_ACCOUNT_ID) + grantPermissionToken( + Permissions.CanBurnAssetWithDefinition, + IdKey.AssetDefinitionId.type.asName() to DEFAULT_ASSET_DEFINITION_ID.asValue(), + BOB_ACCOUNT_ID + ) } client.tx(BOB_ACCOUNT_ID, BOB_KEYPAIR) { burnAsset(DEFAULT_ASSET_ID, 50) } @@ -468,14 +470,14 @@ class InstructionsTest : IrohaTest( } @Test - @WithIroha([DefaultGenesis::class]) + @WithIroha([AliceAndBobHasPermissionToMintPublicKeys::class]) @Feature("Accounts") @Story("Account burn a public key") @Permission("no_permission_required") @SdkTestId("burn_one_of_several_public_keys") - fun `burn public key instruction committed`(): Unit = runBlocking { + fun `burn public key`(): Unit = runBlocking { // mint public key, because needs at least 2 public keys to burn one of them - mintPublicKey(ALICE_ACCOUNT_ID) + client.tx { mintPublicKey(ALICE_ACCOUNT_ID, generateKeyPair().public.toIrohaPublicKey()) } val alicePubKey = ALICE_KEYPAIR.public.toIrohaPublicKey() // check public key before burn it @@ -497,17 +499,20 @@ class InstructionsTest : IrohaTest( } @Test - @WithIroha([DefaultGenesis::class]) + @WithIroha([AliceAndBobHasPermissionToMintPublicKeys::class]) @Feature("Accounts") @Story("Account mints a public key") @Permission("no_permission_required") @SdkTestId("mint_public_key_after_burning_one_public_key") - fun `burn and mint public key instruction committed`(): Unit = runBlocking { - // mint public key, because needs at least 2 public keys to burn one of them - mintPublicKey(BOB_ACCOUNT_ID) + fun `burn and mint public key`(): Unit = runBlocking { + val keyPair = generateKeyPair() + // mint public key, because needs at least 2 public keys to burn one of them + client.tx(BOB_ACCOUNT_ID, BOB_KEYPAIR) { + mintPublicKey(BOB_ACCOUNT_ID, keyPair.public.toIrohaPublicKey()) + } // check Bob's public key before burn it - val bobPubKey = BOB_KEYPAIR.public.toIrohaPublicKey() + val bobPubKey = keyPair.public.toIrohaPublicKey() var query = QueryBuilder.findAccountById(BOB_ACCOUNT_ID) .account(ALICE_ACCOUNT_ID) .buildSigned(ALICE_KEYPAIR) @@ -515,7 +520,7 @@ class InstructionsTest : IrohaTest( assertEquals(2, signatories.size) // burn Bob's public key - client.tx { burnPublicKey(BOB_ACCOUNT_ID, bobPubKey) } + client.tx(BOB_ACCOUNT_ID, BOB_KEYPAIR) { burnPublicKey(BOB_ACCOUNT_ID, bobPubKey) } // check Bob's account has only 1 public key (was 2) query = QueryBuilder.findAccountById(BOB_ACCOUNT_ID) @@ -528,7 +533,7 @@ class InstructionsTest : IrohaTest( val newKeyPair = generateKeyPair() // change Bob's account public key - client.tx { mintPublicKey(BOB_ACCOUNT_ID, newKeyPair.public.toIrohaPublicKey()) } + client.tx(BOB_ACCOUNT_ID, BOB_KEYPAIR) { mintPublicKey(BOB_ACCOUNT_ID, newKeyPair.public.toIrohaPublicKey()) } // check public keys in Bob's account val newPubKeyQuery = QueryBuilder.findAccountById(BOB_ACCOUNT_ID) @@ -550,15 +555,18 @@ class InstructionsTest : IrohaTest( // grant permission to Alice to change Bob's account metadata client.sendTransaction { account(BOB_ACCOUNT_ID) - registerPermissionToken(Permissions.CanSetKeyValueInUserMetadata.type, IdKey.AccountId) - grantSetKeyValueMetadata(BOB_ACCOUNT_ID, ALICE_ACCOUNT_ID) + grantPermissionToken( + Permissions.CanSetKeyValueInUserAccount, + IdKey.AccountId.type.asName() to BOB_ACCOUNT_ID.asValue(), + ALICE_ACCOUNT_ID + ) buildSigned(BOB_KEYPAIR) }.also { d -> withTimeout(txTimeout) { d.await() } } - // add\update salt value in Bob's account metadata - val salt = "ABCDEFG".asValue() + // add/update salt value in Bob's account metadata + val salt = RandomStringUtils.random(10).asValue() client.tx { setKeyValue(BOB_ACCOUNT_ID, saltKey.asName(), salt) } // check new metadata in Bob's account @@ -575,20 +583,18 @@ class InstructionsTest : IrohaTest( @Story("Account transfers assets") @Permission("can_transfer_user_asset") @SdkTestId("transfer_asset") - fun `transfer asset instruction committed`(): Unit = runBlocking { + fun `transfer asset`(): Unit = runBlocking { val aliceAssetId = DEFAULT_ASSET_ID val bobAssetId = AliceAndBobEachHave100Xor.BOB_ASSET_ID assertEquals(100, getAccountAmount(ALICE_ACCOUNT_ID, aliceAssetId)) assertEquals(100, getAccountAmount(BOB_ACCOUNT_ID, bobAssetId)) - client.tx { transferAsset(aliceAssetId, 40, bobAssetId) } + client.tx { transferAsset(aliceAssetId, 40, BOB_ACCOUNT_ID) } assertEquals(60, getAccountAmount(ALICE_ACCOUNT_ID, aliceAssetId)) assertEquals(140, getAccountAmount(BOB_ACCOUNT_ID, bobAssetId)) - client.tx(BOB_ACCOUNT_ID, BOB_KEYPAIR) { - transferAsset(bobAssetId, 40, aliceAssetId) - } + client.tx(BOB_ACCOUNT_ID, BOB_KEYPAIR) { transferAsset(bobAssetId, 40, ALICE_ACCOUNT_ID) } assertEquals(100, getAccountAmount(ALICE_ACCOUNT_ID, aliceAssetId)) assertEquals(100, getAccountAmount(BOB_ACCOUNT_ID, bobAssetId)) @@ -600,11 +606,14 @@ class InstructionsTest : IrohaTest( registerAccount(joeId, joeKeyPair.public.toIrohaPublicKey()) client.tx { - registerPermissionToken(Permissions.CanTransferUserAssetsToken, IdKey.AssetId) - grantTransferUserAsset(aliceAssetId, joeId) + grantPermissionToken( + Permissions.CanTransferUserAssetsToken, + IdKey.AssetId.type.asName() to aliceAssetId.asValue(), + joeId + ) } client.tx(account = joeId, keyPair = joeKeyPair) { - transferAsset(aliceAssetId, 40, bobAssetId) + transferAsset(aliceAssetId, 40, BOB_ACCOUNT_ID) } assertEquals(60, getAccountAmount(ALICE_ACCOUNT_ID, aliceAssetId)) assertEquals(140, getAccountAmount(BOB_ACCOUNT_ID, bobAssetId)) @@ -636,7 +645,7 @@ class InstructionsTest : IrohaTest( @Story("Client sends a pair instructions within transaction") @Permission("no_permission_required") @SdkTestId("pair_instruction") - fun `pair instruction committed`(): Unit = runBlocking { + fun `pair`(): Unit = runBlocking { client.sendTransaction { account(ALICE_ACCOUNT_ID) pair( @@ -697,7 +706,7 @@ class InstructionsTest : IrohaTest( @Story("Account removes asset metadata") @Permission("no_permission_required") @SdkTestId("remove_asset_metadata") - fun `remove asset instruction committed`(): Unit = runBlocking { + fun `remove asset`(): Unit = runBlocking { val assetId = StoreAssetWithMetadata.ASSET_ID val assetKey = StoreAssetWithMetadata.ASSET_KEY @@ -753,7 +762,7 @@ class InstructionsTest : IrohaTest( .let { query -> client.sendQuery(query) } .let { account -> account.assets[DEFAULT_ASSET_ID]?.value } .let { value -> - value?.cast()?.fixed?.fixedPoint ?: BigDecimal.ZERO + value?.cast()?.fixed?.fixedPointOfI64 ?: BigDecimal.ZERO } .also { actualBalance -> assertTrue("expected value `$expectedBalance`, but was `$actualBalance`") { @@ -837,16 +846,14 @@ class InstructionsTest : IrohaTest( val roleId = RoleId("BOB_ASSET_ACCESS".asName()) client.tx(BOB_ACCOUNT_ID, BOB_KEYPAIR) { - registerPermissionToken(Permissions.CanSetKeyValueUserAssetsToken.type, IdKey.AssetId) - registerPermissionToken(Permissions.CanRemoveKeyValueInUserAssets.type, IdKey.AssetId) registerRole( roleId, - Token( - TokenId(Permissions.CanSetKeyValueUserAssetsToken.type), + PermissionToken( + PermissionTokenId(Permissions.CanSetKeyValueUserAssetsToken.type), mapOf(IdKey.AssetId.type.asName() to assetId.toValueId()), ), - Token( - TokenId(Permissions.CanRemoveKeyValueInUserAssets.type), + PermissionToken( + PermissionTokenId(Permissions.CanRemoveKeyValueInUserAssets.type), mapOf(IdKey.AssetId.type.asName() to assetId.toValueId()), ), ) @@ -971,10 +978,4 @@ class InstructionsTest : IrohaTest( client.sendQuery(query) } } - - private suspend fun mintPublicKey(accountId: AccountId) { - client.tx { - mintPublicKey(accountId, generateKeyPair().public.toIrohaPublicKey()) - } - } } diff --git a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/PeerTest.kt b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/PeerTest.kt index 3d8184d53..c5a10c8b2 100644 --- a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/PeerTest.kt +++ b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/PeerTest.kt @@ -8,7 +8,9 @@ import jp.co.soramitsu.iroha2.annotations.Permission import jp.co.soramitsu.iroha2.annotations.Sdk import jp.co.soramitsu.iroha2.annotations.SdkTestId import jp.co.soramitsu.iroha2.client.Iroha2Client -import jp.co.soramitsu.iroha2.generated.datamodel.peer.PeerId +import jp.co.soramitsu.iroha2.generated.PeerId +import jp.co.soramitsu.iroha2.generated.SocketAddr +import jp.co.soramitsu.iroha2.generated.SocketAddrHost import jp.co.soramitsu.iroha2.query.QueryBuilder import jp.co.soramitsu.iroha2.testengine.ALICE_ACCOUNT_ID import jp.co.soramitsu.iroha2.testengine.ALICE_KEYPAIR @@ -147,7 +149,8 @@ class PeerTest : IrohaTest() { .let { query -> client.sendQuery(query) }.any { peer -> - peer.id.address == address && peer.id.publicKey.payload.contentEquals(payload) + val peerAddr = peer.id.address.cast().socketAddrHost + "${peerAddr.host}:${peerAddr.port}" == address && peer.id.publicKey.payload.contentEquals(payload) } } @@ -180,7 +183,7 @@ class PeerTest : IrohaTest() { } private fun IrohaContainer.extractPeerId() = PeerId( - this.getP2pUrl().toString(), + SocketAddr.Host(SocketAddrHost(this.getP2pUrl().host, this.getP2pUrl().port)), this.config.keyPair.public.toIrohaPublicKey() ) } diff --git a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/QueriesTest.kt b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/QueriesTest.kt index cd58f8fb0..da80b2fea 100644 --- a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/QueriesTest.kt +++ b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/QueriesTest.kt @@ -8,22 +8,22 @@ import jp.co.soramitsu.iroha2.annotations.Query import jp.co.soramitsu.iroha2.annotations.Sdk import jp.co.soramitsu.iroha2.annotations.SdkTestId import jp.co.soramitsu.iroha2.client.Iroha2Client -import jp.co.soramitsu.iroha2.generated.datamodel.IdBox -import jp.co.soramitsu.iroha2.generated.datamodel.Value -import jp.co.soramitsu.iroha2.generated.datamodel.account.AccountId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetValueType -import jp.co.soramitsu.iroha2.generated.datamodel.metadata.Metadata -import jp.co.soramitsu.iroha2.generated.datamodel.name.Name -import jp.co.soramitsu.iroha2.generated.datamodel.pagination.Pagination -import jp.co.soramitsu.iroha2.generated.datamodel.predicate.GenericValuePredicateBox -import jp.co.soramitsu.iroha2.generated.datamodel.predicate.string.StringPredicate -import jp.co.soramitsu.iroha2.generated.datamodel.predicate.value.Container -import jp.co.soramitsu.iroha2.generated.datamodel.predicate.value.ValueOfKey -import jp.co.soramitsu.iroha2.generated.datamodel.predicate.value.ValuePredicate -import jp.co.soramitsu.iroha2.generated.datamodel.sorting.Sorting -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.TransactionValue -import jp.co.soramitsu.iroha2.generated.datamodel.transaction.VersionedSignedTransaction +import jp.co.soramitsu.iroha2.generated.AccountId +import jp.co.soramitsu.iroha2.generated.AssetId +import jp.co.soramitsu.iroha2.generated.AssetValueType +import jp.co.soramitsu.iroha2.generated.Container +import jp.co.soramitsu.iroha2.generated.GenericPredicateBox +import jp.co.soramitsu.iroha2.generated.IdBox +import jp.co.soramitsu.iroha2.generated.Metadata +import jp.co.soramitsu.iroha2.generated.Name +import jp.co.soramitsu.iroha2.generated.Pagination +import jp.co.soramitsu.iroha2.generated.Sorting +import jp.co.soramitsu.iroha2.generated.StringPredicate +import jp.co.soramitsu.iroha2.generated.TransactionValue +import jp.co.soramitsu.iroha2.generated.Value +import jp.co.soramitsu.iroha2.generated.ValueOfKey +import jp.co.soramitsu.iroha2.generated.ValuePredicate +import jp.co.soramitsu.iroha2.generated.VersionedSignedTransaction import jp.co.soramitsu.iroha2.query.QueryBuilder import jp.co.soramitsu.iroha2.testengine.ALICE_ACCOUNT_ID import jp.co.soramitsu.iroha2.testengine.ALICE_ACCOUNT_NAME @@ -354,7 +354,7 @@ class QueriesTest : IrohaTest(account = ALICE_ACCOUNT_ID, keyPair @SdkTestId("find_asset_by_metadata_filters") @Disabled // https://github.com/hyperledger/iroha/issues/2697 fun `find asset by metadata filters`(): Unit = runBlocking { - val filter = GenericValuePredicateBox.Raw( + val filter = GenericPredicateBox.Raw( ValuePredicate.Container( Container.ValueOfKey( ValueOfKey( @@ -574,7 +574,7 @@ class QueriesTest : IrohaTest(account = ALICE_ACCOUNT_ID, keyPair .account(ALICE_ACCOUNT_ID) .buildSigned(ALICE_KEYPAIR) .let { client.sendQuery(it) } - .also { trigger -> assertTrue { trigger.id == triggerId } } + .also { trigger -> assertTrue { trigger.id() == triggerId } } } @Test @@ -589,7 +589,7 @@ class QueriesTest : IrohaTest(account = ALICE_ACCOUNT_ID, keyPair .account(ALICE_ACCOUNT_ID) .buildSigned(ALICE_KEYPAIR) .let { client.sendQuery(it) } - .also { triggers -> assert(triggers.all { it.id.domainId == domainId }) } + .also { triggers -> assert(triggers.all { it.id().domainId == domainId }) } } @Test @@ -745,7 +745,7 @@ class QueriesTest : IrohaTest(account = ALICE_ACCOUNT_ID, keyPair .buildSigned(ALICE_KEYPAIR) .let { client.sendQuery(it) } .also { txs -> - assertTrue(txs.size == 6) // 5 + genesis tx + assertTrue(txs.size == 7) // 5 + 2 genesis txs } } @@ -826,10 +826,7 @@ class QueriesTest : IrohaTest(account = ALICE_ACCOUNT_ID, keyPair .buildSigned(ALICE_KEYPAIR) .let { query -> client.sendQuery(query) } .also { ids -> - assertContains( - ids, - AliceHasRoleWithAccessToBobsMetadata.ROLE_ID - ) + assertContains(ids, AliceHasRoleWithAccessToBobsMetadata.ROLE_ID) } } diff --git a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/TriggersTest.kt b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/TriggersTest.kt index 226b29395..bf1dd995f 100644 --- a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/TriggersTest.kt +++ b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/TriggersTest.kt @@ -6,24 +6,25 @@ import io.qameta.allure.Story import jp.co.soramitsu.iroha2.annotations.Sdk import jp.co.soramitsu.iroha2.annotations.SdkTestId import jp.co.soramitsu.iroha2.client.Iroha2Client +import jp.co.soramitsu.iroha2.generated.AccountId +import jp.co.soramitsu.iroha2.generated.AssetDefinitionEventFilter +import jp.co.soramitsu.iroha2.generated.AssetDefinitionId +import jp.co.soramitsu.iroha2.generated.AssetId +import jp.co.soramitsu.iroha2.generated.AssetValue +import jp.co.soramitsu.iroha2.generated.AssetValueType import jp.co.soramitsu.iroha2.generated.Duration -import jp.co.soramitsu.iroha2.generated.datamodel.account.AccountId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetDefinitionId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetValue -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetValueType -import jp.co.soramitsu.iroha2.generated.datamodel.events.data.events.asset.AssetDefinitionEventFilter -import jp.co.soramitsu.iroha2.generated.datamodel.isi.Instruction -import jp.co.soramitsu.iroha2.generated.datamodel.metadata.Metadata -import jp.co.soramitsu.iroha2.generated.datamodel.name.Name -import jp.co.soramitsu.iroha2.generated.datamodel.trigger.TriggerId -import jp.co.soramitsu.iroha2.generated.datamodel.trigger.action.Repeats +import jp.co.soramitsu.iroha2.generated.InstructionBox +import jp.co.soramitsu.iroha2.generated.Metadata +import jp.co.soramitsu.iroha2.generated.Name +import jp.co.soramitsu.iroha2.generated.Repeats +import jp.co.soramitsu.iroha2.generated.TriggerId import jp.co.soramitsu.iroha2.query.QueryBuilder import jp.co.soramitsu.iroha2.testengine.ALICE_ACCOUNT_ID import jp.co.soramitsu.iroha2.testengine.ALICE_ACCOUNT_NAME import jp.co.soramitsu.iroha2.testengine.ALICE_KEYPAIR import jp.co.soramitsu.iroha2.testengine.AliceAndBobEachHave100Xor import jp.co.soramitsu.iroha2.testengine.AliceHas100XorAndPermissionToBurn +import jp.co.soramitsu.iroha2.testengine.BOB_ACCOUNT_ID import jp.co.soramitsu.iroha2.testengine.DEFAULT_ASSET_ID import jp.co.soramitsu.iroha2.testengine.DEFAULT_DOMAIN_ID import jp.co.soramitsu.iroha2.testengine.IrohaTest @@ -149,7 +150,7 @@ class TriggersTest : IrohaTest() { val bobAssetId = AliceAndBobEachHave100Xor.BOB_ASSET_ID client.sendTransaction { account(ALICE_ACCOUNT_ID) - transferAsset(DEFAULT_ASSET_ID, 100, bobAssetId) + transferAsset(DEFAULT_ASSET_ID, 100, BOB_ACCOUNT_ID) buildSigned(ALICE_KEYPAIR) }.also { d -> withTimeout(txTimeout) { d.await() } @@ -349,7 +350,7 @@ class TriggersTest : IrohaTest() { private suspend fun sendAndAwaitTimeTrigger( triggerId: TriggerId, repeats: Repeats, - instruction: Instruction, + instruction: InstructionBox, accountId: AccountId = ALICE_ACCOUNT_ID, ) { client.sendTransaction { diff --git a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/testengine/Genesis.kt b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/testengine/Genesis.kt index a900dd08a..306255688 100644 --- a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/testengine/Genesis.kt +++ b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/testengine/Genesis.kt @@ -7,20 +7,19 @@ import jp.co.soramitsu.iroha2.asDomainId import jp.co.soramitsu.iroha2.asName import jp.co.soramitsu.iroha2.asValue import jp.co.soramitsu.iroha2.generateKeyPair -import jp.co.soramitsu.iroha2.generated.core.genesis.GenesisTransaction -import jp.co.soramitsu.iroha2.generated.core.genesis.RawGenesisBlock -import jp.co.soramitsu.iroha2.generated.datamodel.account.AccountId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetDefinitionId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetValueType -import jp.co.soramitsu.iroha2.generated.datamodel.domain.DomainId -import jp.co.soramitsu.iroha2.generated.datamodel.isi.Instruction -import jp.co.soramitsu.iroha2.generated.datamodel.metadata.Metadata -import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.Token -import jp.co.soramitsu.iroha2.generated.datamodel.permission.token.TokenId -import jp.co.soramitsu.iroha2.generated.datamodel.role.RoleId -import jp.co.soramitsu.iroha2.generated.datamodel.trigger.TriggerId -import jp.co.soramitsu.iroha2.generated.datamodel.trigger.action.Repeats +import jp.co.soramitsu.iroha2.generated.AccountId +import jp.co.soramitsu.iroha2.generated.AssetDefinitionId +import jp.co.soramitsu.iroha2.generated.AssetId +import jp.co.soramitsu.iroha2.generated.AssetValueType +import jp.co.soramitsu.iroha2.generated.DomainId +import jp.co.soramitsu.iroha2.generated.InstructionBox +import jp.co.soramitsu.iroha2.generated.Metadata +import jp.co.soramitsu.iroha2.generated.PermissionToken +import jp.co.soramitsu.iroha2.generated.PermissionTokenId +import jp.co.soramitsu.iroha2.generated.RawGenesisBlock +import jp.co.soramitsu.iroha2.generated.Repeats +import jp.co.soramitsu.iroha2.generated.RoleId +import jp.co.soramitsu.iroha2.generated.TriggerId import jp.co.soramitsu.iroha2.toIrohaPublicKey import jp.co.soramitsu.iroha2.transaction.Instructions import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils @@ -30,21 +29,49 @@ import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils */ open class DefaultGenesis : Genesis(rawGenesisBlock()) +open class AliceAndBobHasPermissionToMintPublicKeys : Genesis( + rawGenesisBlock( + Instructions.grantPermissionToken( + Permissions.CanMintUserPublicKeys, + mapOf(IdKey.AccountId.type.asName() to ALICE_ACCOUNT_ID.asValue()), + ALICE_ACCOUNT_ID + ), + Instructions.grantPermissionToken( + Permissions.CanMintUserPublicKeys, + mapOf(IdKey.AccountId.type.asName() to BOB_ACCOUNT_ID.asValue()), + BOB_ACCOUNT_ID + ) + ) +) + +open class AliceHasPermissionToUnregisterDomain : Genesis( + rawGenesisBlock( + Instructions.registerDomain(NEW_DOMAIN_ID), + Instructions.grantPermissionToken( + Permissions.CanUnregisterDomain, + mapOf(IdKey.DomainId.type.asName() to NEW_DOMAIN_ID.asValue()), + ALICE_ACCOUNT_ID + ) + ) +) { + companion object { + val NEW_DOMAIN_ID = DomainId("NEW_DOMAIN".asName()) + } +} + /** * Give Alice access to Bob's metadata */ open class AliceHasRoleWithAccessToBobsMetadata : Genesis( rawGenesisBlock( - Instructions.registerPermissionToken(Permissions.CanSetKeyValueInUserMetadata.type, IdKey.AccountId), - Instructions.registerPermissionToken(Permissions.CanRemoveKeyValueInUserMetadata.type, IdKey.AccountId), Instructions.registerRole( ROLE_ID, - Token( - TokenId(Permissions.CanSetKeyValueInUserMetadata.type), + PermissionToken( + PermissionTokenId(Permissions.CanSetKeyValueInUserAccount.type), mapOf(IdKey.AccountId.type.asName() to ALICE_ACCOUNT_ID.asValue()) ), - Token( - TokenId(Permissions.CanRemoveKeyValueInUserMetadata.type), + PermissionToken( + PermissionTokenId(Permissions.CanRemoveKeyValueInUserAccount.type), mapOf(IdKey.AccountId.type.asName() to ALICE_ACCOUNT_ID.asValue()) ) ), @@ -63,8 +90,11 @@ open class AliceHas100XorAndPermissionToBurn : Genesis( rawGenesisBlock( Instructions.registerAssetDefinition(DEFAULT_ASSET_DEFINITION_ID, AssetValueType.Quantity()), Instructions.mintAsset(DEFAULT_ASSET_ID, 100), - Instructions.registerPermissionToken(Permissions.CanBurnAssetWithDefinition.type, IdKey.AssetDefinitionId), - Instructions.grantBurnAssetWithDefinitionId(DEFAULT_ASSET_DEFINITION_ID, ALICE_ACCOUNT_ID) + Instructions.grantPermissionToken( + Permissions.CanMintUserAssetDefinitionsToken, + mapOf(IdKey.AssetDefinitionId.type.asName() to DEFAULT_ASSET_DEFINITION_ID.asValue()), + ALICE_ACCOUNT_ID + ) ) ) @@ -108,6 +138,16 @@ open class WithExecutableTrigger : Genesis( open class AliceAndBobEachHave100Xor : Genesis( rawGenesisBlock( Instructions.registerAssetDefinition(DEFAULT_ASSET_DEFINITION_ID, AssetValueType.Quantity()), + Instructions.grantPermissionToken( + Permissions.CanTransferAssetsWithDefinition, + mapOf(IdKey.AssetDefinitionId.type.asName() to DEFAULT_ASSET_DEFINITION_ID.asValue()), + ALICE_ACCOUNT_ID + ), + Instructions.grantPermissionToken( + Permissions.CanTransferAssetsWithDefinition, + mapOf(IdKey.AssetDefinitionId.type.asName() to DEFAULT_ASSET_DEFINITION_ID.asValue()), + BOB_ACCOUNT_ID + ), Instructions.mintAsset(DEFAULT_ASSET_ID, 100), Instructions.mintAsset(BOB_ASSET_ID, 100) ) @@ -140,11 +180,11 @@ open class StoreAssetWithMetadata : Genesis( open class AliceCanMintXor : Genesis( rawGenesisBlock( - Instructions.registerPermissionToken( - Permissions.CanMintUserAssetDefinitionsToken.type, - IdKey.AssetDefinitionId - ), - Instructions.grantMintUserAssetDefinitions(XOR_DEFINITION_ID, ALICE_ACCOUNT_ID) + Instructions.grantPermissionToken( + Permissions.CanMintUserAssetDefinitionsToken, + mapOf(IdKey.AssetDefinitionId.type.asName() to XOR_DEFINITION_ID.asValue()), + ALICE_ACCOUNT_ID + ) ) ) @@ -247,23 +287,29 @@ open class RubbishToTestMultipleGenesis : Genesis( /** * Return [RawGenesisBlock] with instructions to init genesis block */ -fun rawGenesisBlock(vararg isi: Instruction): RawGenesisBlock { - return RawGenesisBlock( - listOf( - GenesisTransaction( - listOf( - Instructions.registerDomain(DEFAULT_DOMAIN_ID), - Instructions.registerAccount( - ALICE_ACCOUNT_ID, - listOf(ALICE_KEYPAIR.public.toIrohaPublicKey()) - ), - Instructions.registerAccount( - BOB_ACCOUNT_ID, - listOf(BOB_KEYPAIR.public.toIrohaPublicKey()) - ), - *isi - ) - ) - ) - ) -} +fun rawGenesisBlock(vararg isi: InstructionBox) = RawGenesisBlock( + listOf( + Instructions.registerDomain(DEFAULT_DOMAIN_ID), + Instructions.registerAccount( + ALICE_ACCOUNT_ID, + listOf(ALICE_KEYPAIR.public.toIrohaPublicKey()) + ), + Instructions.registerAccount( + BOB_ACCOUNT_ID, + listOf(BOB_KEYPAIR.public.toIrohaPublicKey()) + ), + Instructions.registerPermissionToken(Permissions.CanUnregisterAccount, IdKey.AccountId), + Instructions.registerPermissionToken(Permissions.CanUnregisterDomain, IdKey.DomainId), + Instructions.registerPermissionToken(Permissions.CanSetKeyValueInUserAccount.type, IdKey.AccountId), + Instructions.registerPermissionToken(Permissions.CanRemoveKeyValueInUserAccount.type, IdKey.AccountId), + Instructions.registerPermissionToken(Permissions.CanBurnAssetWithDefinition.type, IdKey.AssetDefinitionId), + Instructions.registerPermissionToken(Permissions.CanMintUserAssetDefinitionsToken.type, IdKey.AssetDefinitionId), + Instructions.registerPermissionToken(Permissions.CanSetKeyValueUserAssetsToken.type, IdKey.AssetId), + Instructions.registerPermissionToken(Permissions.CanRemoveKeyValueInUserAssets.type, IdKey.AssetId), + Instructions.registerPermissionToken(Permissions.CanTransferAssetsWithDefinition, IdKey.AssetDefinitionId), + Instructions.registerPermissionToken(Permissions.CanTransferUserAssetsToken, IdKey.AssetId), + Instructions.registerPermissionToken(Permissions.CanMintUserPublicKeys, IdKey.AccountId), + *isi + ).let { listOf(it) }, + Genesis.validatorMode +) diff --git a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/testengine/TestConsts.kt b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/testengine/TestConsts.kt index 5f13fc34a..0c9ecc5c8 100644 --- a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/testengine/TestConsts.kt +++ b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/testengine/TestConsts.kt @@ -3,9 +3,9 @@ package jp.co.soramitsu.iroha2.testengine import jp.co.soramitsu.iroha2.asDomainId import jp.co.soramitsu.iroha2.asName import jp.co.soramitsu.iroha2.generateKeyPair -import jp.co.soramitsu.iroha2.generated.datamodel.account.AccountId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetDefinitionId -import jp.co.soramitsu.iroha2.generated.datamodel.asset.AssetId +import jp.co.soramitsu.iroha2.generated.AccountId +import jp.co.soramitsu.iroha2.generated.AssetDefinitionId +import jp.co.soramitsu.iroha2.generated.AssetId const val DEFAULT_DOMAIN = "wonderland" const val BOB_ACCOUNT = "bob" diff --git a/modules/client/src/test/resources/create_nft_for_every_user_smartcontract.wasm b/modules/client/src/test/resources/create_nft_for_every_user_smartcontract.wasm index ec6bd3559f8b22a6d382beb1920004a6673261f9..5b8a0b37ae3c0e97f84766c8f841ad1da6e25a56 100755 GIT binary patch literal 425366 zcmdSC3%F%fb?3Vtd#}CE+2@>E^`c%VYi~+Dl~9+;ZBs=HUt6ob_@M1h?C$&J%Xe?? z6+7{3av+96r0Kpk9HqokQA@>yBqml23K}1!qN1W6lo))Mi4ROrOT~mlG{l&QQN#WH z$Cz{Nwa=p{NGJ9!^z60fbIdWu9IyFU$9yJLg8Zthu1c@EAhW+6Uam?m zNJEOA5-X8hkWeVm%azKcp#66BOPRucd9W5JKnDNRu$AlCt0fG=W5{3EdJReWl~#wY zuB1b2f6KB`@s$qxf|tCYEpDMMc(W1zwtkwdp-S6|M;Oyr4O=i=qGn>w)X#yLhX(uZ(8tJD=Crh*0obq$&x^ynd zvRR&0EX`-jxg=ko&SqJbY?v!clGC|-a(3lQLBL4D0vj8e#3_K(EX{^h{oT?nMpLo1!mpn0Ob$pQk%N1@_+ z3snKW0iTCLZ$+`)!H$2MamR#E=MlVb4rxs*_WnrmNb?V3>tFcsmsS3DNz?y-eE

!#|S(>MLR#nJf`^Ox)B>jB$`K+6# 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

~ancn@s%8~$ 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%0DaWBlrPGzVPkG!>m>y#0Y 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 diff --git a/modules/client/src/test/resources/genesis.json b/modules/client/src/test/resources/genesis.json index 8d756cb88..668d8a8c0 100644 --- a/modules/client/src/test/resources/genesis.json +++ b/modules/client/src/test/resources/genesis.json @@ -1,83 +1,82 @@ { "transactions": [ - { - "isi": [ - { - "Register": { - "NewDomain": { - "id": "wonderland", - "logo": null, - "metadata": {} - } + [ + { + "Register": { + "NewDomain": { + "id": "wonderland", + "logo": null, + "metadata": {} } - }, - { - "Register": { - "NewAccount": { - "id": "alice@wonderland", - "signatories": [ - "ed0120cc25624d62896d3a0bfd8940f928dc2abf27cc57cefeb442aa96d9081aae58a1" - ], - "metadata": {} - } + } + }, + { + "Register": { + "NewAccount": { + "id": "alice@wonderland", + "signatories": [ + "ed0120cc25624d62896d3a0bfd8940f928dc2abf27cc57cefeb442aa96d9081aae58a1" + ], + "metadata": {} } - }, - { - "Register": { - "NewAccount": { - "id": "bob@wonderland", - "signatories": [ - "ed01207233bfc89dcbd68c19fde6ce6158225298ec1131b6a130d1aeb454c1ab5183c0" - ], - "metadata": {} - } + } + }, + { + "Register": { + "NewAccount": { + "id": "bob@wonderland", + "signatories": [ + "ed01207233bfc89dcbd68c19fde6ce6158225298ec1131b6a130d1aeb454c1ab5183c0" + ], + "metadata": {} } - }, - { - "Register": { - "PermissionTokenDefinition": { - "id": "can_set_key_value_in_user_metadata", - "params": { - "account_id": "Id" - } + } + }, + { + "Register": { + "PermissionTokenDefinition": { + "id": "can_set_key_value_in_user_metadata", + "params": { + "account_id": "Id" } } - }, - { - "Grant": { - "PermissionToken": { - "definition_id": "can_set_key_value_in_user_metadata", - "params": { - "account_id": { - "AccountId": "alice@wonderland" - } + } + }, + { + "Grant": { + "PermissionToken": { + "definition_id": "can_set_key_value_in_user_metadata", + "params": { + "account_id": { + "AccountId": "alice@wonderland" } - }, - "destination_id": { - "AccountId": "bob@wonderland" } + }, + "destination_id": { + "AccountId": "bob@wonderland" } - }, - { - "Register": { - "PermissionTokenDefinition": { - "id": "can_register_domains", - "params": {} - } + } + }, + { + "Register": { + "PermissionTokenDefinition": { + "id": "can_register_domains", + "params": {} } - }, - { - "Grant": { - "PermissionToken": { - "definition_id": "can_register_domains", - "params": {} - }, - "destination_id": { - "AccountId": "bob@wonderland" - } + } + }, + { + "Grant": { + "PermissionToken": { + "definition_id": "can_register_domains", + "params": {} + }, + "destination_id": { + "AccountId": "bob@wonderland" } } - ] - } - ] + } + ] + ], + "validator": "./validator.wasm" } \ No newline at end of file diff --git a/modules/client/src/test/resources/logback-test.xml b/modules/client/src/test/resources/logback-test.xml new file mode 100644 index 000000000..a6c3a859a --- /dev/null +++ b/modules/client/src/test/resources/logback-test.xml @@ -0,0 +1,27 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + + \ No newline at end of file diff --git a/modules/client/src/test/resources/validator.wasm b/modules/client/src/test/resources/validator.wasm new file mode 100644 index 0000000000000000000000000000000000000000..72feaff428792b9f5b8c2ec55eee736153c0b8b8 GIT binary patch literal 597151 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