Skip to content

Commit

Permalink
feature: added trigger deserialization (#417)
Browse files Browse the repository at this point in the history
* feature: added trigger deserialization

Signed-off-by: Timur Guskov <[email protected]>

* feature: refactoring of trigger's filter deserialization

Signed-off-by: Timur Guskov <[email protected]>

---------

Signed-off-by: Timur Guskov <[email protected]>
Co-authored-by: Timur Guskov <[email protected]>
  • Loading branch information
gv-timur and gv-timur authored May 16, 2024
1 parent ec1cac7 commit 6e7746e
Show file tree
Hide file tree
Showing 4 changed files with 327 additions and 12 deletions.
168 changes: 162 additions & 6 deletions modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import io.ipfs.multihash.Multihash
import io.ktor.util.toUpperCasePreservingASCIIRules
import jp.co.soramitsu.iroha2.DigestFunction.Ed25519
import jp.co.soramitsu.iroha2.generated.AccountId
import jp.co.soramitsu.iroha2.generated.ActionOfTriggeringFilterBox
import jp.co.soramitsu.iroha2.generated.Algorithm
import jp.co.soramitsu.iroha2.generated.Asset
import jp.co.soramitsu.iroha2.generated.AssetDefinitionId
Expand All @@ -30,7 +31,11 @@ import jp.co.soramitsu.iroha2.generated.AssetValueType
import jp.co.soramitsu.iroha2.generated.BlockHeader
import jp.co.soramitsu.iroha2.generated.BurnExpr
import jp.co.soramitsu.iroha2.generated.DomainId
import jp.co.soramitsu.iroha2.generated.Duration
import jp.co.soramitsu.iroha2.generated.EvaluatesTo
import jp.co.soramitsu.iroha2.generated.Executable
import jp.co.soramitsu.iroha2.generated.ExecuteTriggerEventFilter
import jp.co.soramitsu.iroha2.generated.ExecutionTime
import jp.co.soramitsu.iroha2.generated.ExecutorMode
import jp.co.soramitsu.iroha2.generated.Expression
import jp.co.soramitsu.iroha2.generated.Fixed
Expand Down Expand Up @@ -61,21 +66,27 @@ import jp.co.soramitsu.iroha2.generated.PublicKey
import jp.co.soramitsu.iroha2.generated.RawGenesisBlock
import jp.co.soramitsu.iroha2.generated.RegisterExpr
import jp.co.soramitsu.iroha2.generated.RegistrableBox
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.Schedule
import jp.co.soramitsu.iroha2.generated.SequenceExpr
import jp.co.soramitsu.iroha2.generated.SetKeyValueExpr
import jp.co.soramitsu.iroha2.generated.SignatureCheckCondition
import jp.co.soramitsu.iroha2.generated.SignedBlock
import jp.co.soramitsu.iroha2.generated.SocketAddr
import jp.co.soramitsu.iroha2.generated.StringWithJson
import jp.co.soramitsu.iroha2.generated.TimeEventFilter
import jp.co.soramitsu.iroha2.generated.TransactionLimits
import jp.co.soramitsu.iroha2.generated.TransactionQueryOutput
import jp.co.soramitsu.iroha2.generated.TransactionValue
import jp.co.soramitsu.iroha2.generated.TriggerId
import jp.co.soramitsu.iroha2.generated.TriggerOfTriggeringFilterBox
import jp.co.soramitsu.iroha2.generated.TriggeringFilterBox
import jp.co.soramitsu.iroha2.generated.Value
import jp.co.soramitsu.iroha2.generated.WasmSmartContract
import java.io.ByteArrayOutputStream
import java.math.BigInteger
import kotlin.reflect.KClass
import kotlin.reflect.full.createInstance
import kotlin.reflect.full.memberProperties
Expand All @@ -84,7 +95,7 @@ import kotlin.reflect.full.primaryConstructor
/**
* This JSON mapper is configured to serialise and deserialise `Genesis block` in a format compatible with Iroha 2 peer
*/
val JSON_SERDE by lazy {
public val JSON_SERDE by lazy {
ObjectMapper().also { mapper ->
val module = SimpleModule()

Expand Down Expand Up @@ -113,6 +124,8 @@ val JSON_SERDE by lazy {
module.addDeserializer(NewParameterExpr::class.java, NewParameterExprDeserializer)
module.addDeserializer(PermissionToken::class.java, PermissionTokenDeserializer)
module.addDeserializer(StringWithJson::class.java, StringWithJsonDeserializer)
module.addDeserializer(TriggerId::class.java, TriggerIdDeserializer)
module.addDeserializer(TriggerOfTriggeringFilterBox::class.java, TriggerOfTriggeringFilterBoxDeserializer)
module.addKeyDeserializer(AssetDefinitionId::class.java, AssetDefinitionIdKeyDeserializer)
module.addKeyDeserializer(AccountId::class.java, AccountIdKeyDeserializer)
module.addKeyDeserializer(AssetId::class.java, AssetIdKeyDeserializer)
Expand Down Expand Up @@ -142,6 +155,8 @@ val JSON_SERDE by lazy {
module.addSerializer(SequenceExpr::class.java, SequenceExprSerializer)
module.addSerializer(NewParameterExpr::class.java, NewParameterExprSerializer)
module.addSerializer(StringWithJson::class.java, StringWithJsonSerializer)
module.addSerializer(TimeEventFilter::class.java, TimeEventFilterSerializer)
module.addSerializer(Schedule::class.java, ScheduleSerializer)

mapper.registerModule(module)
mapper.registerModule(
Expand Down Expand Up @@ -170,7 +185,7 @@ object SequenceExprDeserializer : JsonDeserializer<SequenceExpr>() {
*/
object InstructionDeserializer : JsonDeserializer<InstructionExpr>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): InstructionExpr {
return sealedDeserializeInstruction(p, JSON_SERDE)
return sealedDeserializeInstruction(p.readValueAsTree(), JSON_SERDE)
}
}

Expand Down Expand Up @@ -245,6 +260,59 @@ object StringWithJsonDeserializer : JsonDeserializer<StringWithJson>() {
}
}

object TriggerOfTriggeringFilterBoxDeserializer : JsonDeserializer<TriggerOfTriggeringFilterBox>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): TriggerOfTriggeringFilterBox {
val node = p.readValueAsTree<JsonNode>()
val triggerName = node.get("id").asText()
val triggerAction = node.get("action")
val action = when (triggerAction.get("executable").get("Instructions") == null) {
true -> {
val wasm = triggerAction.get("executable").get("Wasm")
val executable = Executable.Wasm(WasmSmartContract(wasm.asText().toByteArray()))
val repeats = getTriggerRepeats(triggerAction)
val accountId = getTriggerAuthority(triggerAction)
val filter = getTriggerFilter(triggerAction)
ActionOfTriggeringFilterBox(
executable = executable,
repeats = repeats,
authority = accountId,
filter = filter,
metadata = Metadata(mapOf()),
)
}
false -> {
val instructions = triggerAction.get("executable").get("Instructions").map {
sealedDeserializeInstruction(it, JSON_SERDE)
}
val executable = Executable.Instructions(instructions)
val repeats = getTriggerRepeats(triggerAction)
val accountId = getTriggerAuthority(triggerAction)
val filter = getTriggerFilter(triggerAction)
ActionOfTriggeringFilterBox(
executable = executable,
repeats = repeats,
authority = accountId,
filter = filter,
metadata = Metadata(mapOf()),
)
}
}

val triggerId = getTriggerId(triggerName)
return TriggerOfTriggeringFilterBox(
id = triggerId,
action = action,
)
}
}

object TriggerIdDeserializer : JsonDeserializer<TriggerId>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): TriggerId {
val triggerName = p.readValueAsTree<JsonNode>().asText()
return getTriggerId(triggerName)
}
}

object PermissionTokenDeserializer : JsonDeserializer<PermissionToken>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): PermissionToken {
val jsonNode = p.readValueAsTree<JsonNode>()
Expand Down Expand Up @@ -305,7 +373,7 @@ object AssetValueTypeDeserializer : JsonDeserializer<AssetValueType>() {
object PublicKeyDeserializer : JsonDeserializer<PublicKey>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): PublicKey {
val key = p.readValueAs(String::class.java)
return PublicKey(Algorithm.Ed25519(), key.fromHex())
return PublicKey(Algorithm.Ed25519(), key.substring(6, key.length).fromHex())
}
}

Expand Down Expand Up @@ -515,7 +583,14 @@ object SequenceExprSerializer : JsonSerializer<SequenceExpr>() {
override fun serialize(value: SequenceExpr, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeStartArray()
value.instructions.forEach { parameter ->
serializeSingleMember(gen, parameter)
when (parameter) {
is InstructionExpr.Grant -> parameter.serialize(gen)
is InstructionExpr.Burn -> parameter.serialize(gen)
is InstructionExpr.Mint -> parameter.serialize(gen)
is InstructionExpr.SetKeyValue -> parameter.serialize(gen)
is InstructionExpr.Register -> parameter.serialize(gen)
else -> serializeSingleMember(gen, parameter)
}
}
gen.writeEndArray()
}
Expand Down Expand Up @@ -547,6 +622,27 @@ object StringWithJsonSerializer : JsonSerializer<StringWithJson>() {
}
}

/**
* Serializer for [TimeEventFilter]
*/
object TimeEventFilterSerializer : JsonSerializer<TimeEventFilter>() {
override fun serialize(value: TimeEventFilter, gen: JsonGenerator, serializers: SerializerProvider) {
value.serializeEnum(gen)
}
}

/**
* Serializer for [Schedule]
*/
object ScheduleSerializer : JsonSerializer<Schedule>() {
override fun serialize(value: Schedule, gen: JsonGenerator, serializers: SerializerProvider) {
val start = value.start.let { mapOf(Pair("secs", it.u64), Pair("nanos", it.u32)) }
val period = value.period?.let { mapOf(Pair("secs", it.u64), Pair("nanos", it.u32)) }
val schedule = mapOf(Pair("start", start), Pair("period", period))
gen.writeObject(schedule)
}
}

/**
* Serializer for [Name] as key
*/
Expand Down Expand Up @@ -840,8 +936,8 @@ private fun sealedDeserializeSequenceExpr(p: JsonParser, mapper: ObjectMapper):
return SequenceExpr(instructions)
}

private fun sealedDeserializeInstruction(p: JsonParser, mapper: ObjectMapper): InstructionExpr {
val node = p.readValueAsTree<JsonNode>().fields().next()
private fun sealedDeserializeInstruction(jsonNode: JsonNode, mapper: ObjectMapper): InstructionExpr {
val node = jsonNode.fields().next()
val param = node.key

val subtype = InstructionExpr::class.nestedClasses.find { clazz ->
Expand Down Expand Up @@ -1144,3 +1240,63 @@ private fun String.toNumericValue(): NumericValue {
else -> throw IllegalArgumentException("Number out of range")
}
}

private fun getTriggerAuthority(triggerAction: JsonNode): AccountId {
return triggerAction.get("authority").asText().asAccountId()
}

private fun getTriggerId(triggerName: String): TriggerId {
return when (triggerName.contains("$")) {
true -> {
val triggerNameWithDomain = triggerName.split("$")
TriggerId(name = triggerNameWithDomain[0].asName(), domainId = triggerNameWithDomain[1].asDomainId())
}
false -> TriggerId(name = triggerName.asName())
}
}

private fun getTriggerRepeats(triggerAction: JsonNode): Repeats {
val repeatsNodeFields = triggerAction.get("repeats").fields()
return when (repeatsNodeFields.hasNext()) {
true -> Repeats.Exactly(repeatsNodeFields.next().value.asLong())
false -> Repeats.Indefinitely()
}
}

private fun getTriggerFilter(triggerAction: JsonNode): TriggeringFilterBox {
val filterNode = triggerAction.get("filter").fields().next()
return when (filterNode.key) {
"Data" -> {
throw IrohaSdkException("${filterNode.key} is not supported")
}
"Time" -> {
val scheduleNode = filterNode.value.get("Schedule")
val start = scheduleNode.get("start")
val period = scheduleNode.get("period")
val periodDuration = when (period.isNull) {
true -> null
false -> Duration(u64 = BigInteger.valueOf(period.get("secs").asLong()), u32 = period.get("nanos").asLong())
}
TriggeringFilterBox.Time(
TimeEventFilter(
ExecutionTime.Schedule(
Schedule(
Duration(u64 = BigInteger.valueOf(start.get("secs").asLong()), u32 = start.get("nanos").asLong()),
periodDuration,
),
),
),
)
}
"ExecuteTrigger" -> {
val executeTrigger = JSON_SERDE.convertValue(filterNode.value, ExecuteTriggerEventFilter::class.java)
TriggeringFilterBox.ExecuteTrigger(executeTrigger)
}
"Pipeline" -> {
throw IrohaSdkException("${filterNode.key} is not supported")
}
else -> {
throw IrohaSdkException("${filterNode.key} is not supported")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ class DeserializerTest {
val block = JSON_SERDE.convertValue(node, RawGenesisBlock::class.java)

assert(block.transactions.isNotEmpty())
// genesis.json has 8 instructions ("isi")
// genesis.json has 10 instructions ("isi")
// Register -> NewDomain
// Register -> NewAccount (2)
// Register -> NewAssetDefinition
// Grant -> PermissionToken (2)
// Mint -> AssetId (2)
assert(block.transactions.flatten().size == 8)
// Register -> Trigger (2)
assert(block.transactions.flatten().size == 10)

val genesis = Genesis(block)
val newJson = removeWhiteSpaceAndReplacePubKey(genesis.asJson())
Expand Down
Loading

0 comments on commit 6e7746e

Please sign in to comment.