Skip to content

Commit

Permalink
Merge pull request #90 from novasamatech/feature/check_metadata_exten…
Browse files Browse the repository at this point in the history
…sion

Feature/check metadata extension
  • Loading branch information
valentunn authored May 24, 2024
2 parents 3f87d96 + 34fd02b commit 2dd0ad6
Show file tree
Hide file tree
Showing 19 changed files with 179 additions and 86 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
buildscript {
ext {
// App version
versionName = '2.0.3'
versionName = '2.1.0'
versionCode = 1

// SDK and tools
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package io.novasama.substrate_sdk_android.runtime.definitions.types.generics

import io.novasama.substrate_sdk_android.runtime.definitions.types.TypeReference
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.Option
import io.novasama.substrate_sdk_android.runtime.definitions.types.primitives.Compact
import io.novasama.substrate_sdk_android.runtime.definitions.types.primitives.FixedByteArray
import io.novasama.substrate_sdk_android.runtime.definitions.types.primitives.u32
import io.novasama.substrate_sdk_android.runtime.definitions.types.primitives.u8
import io.novasama.substrate_sdk_android.runtime.metadata.SignedExtensionMetadata
import io.novasama.substrate_sdk_android.runtime.metadata.SignedExtensionMetadata.Companion.onlyAdditional
import io.novasama.substrate_sdk_android.runtime.metadata.SignedExtensionMetadata.Companion.onlySigned
import io.novasama.substrate_sdk_android.runtime.metadata.SignedExtensionMetadata.Companion.onlyInExtrinsic
import io.novasama.substrate_sdk_android.runtime.metadata.SignedExtensionMetadata.Companion.onlyInSignature

object DefaultSignedExtensions {

Expand All @@ -14,13 +18,30 @@ object DefaultSignedExtensions {
const val CHECK_GENESIS = "CheckGenesis"
const val CHECK_SPEC_VERSION = "CheckSpecVersion"
const val CHECK_TX_VERSION = "CheckTxVersion"
const val CHECK_METADATA_HASH = "CheckMetadataHash"

val ALL = listOf(
SignedExtensionMetadata(CHECK_MORTALITY, type = EraType, additionalSigned = H256),
onlySigned(CHECK_NONCE, Compact("Compact<Index>")),
onlySigned(CHECK_TX_PAYMENT, Compact("Compact<u32>")),
onlyAdditional(CHECK_GENESIS, H256),
onlyAdditional(CHECK_SPEC_VERSION, u32),
onlyAdditional(CHECK_TX_VERSION, u32),
SignedExtensionMetadata(
id = CHECK_MORTALITY,
includedInExtrinsic = EraType,
includedInSignature = H256
),
onlyInExtrinsic(CHECK_NONCE, Compact("Compact<Index>")),
onlyInExtrinsic(CHECK_TX_PAYMENT, Compact("Compact<u32>")),
onlyInSignature(CHECK_GENESIS, H256),
onlyInSignature(CHECK_SPEC_VERSION, u32),
onlyInSignature(CHECK_TX_VERSION, u32),

// This one should not be included in any pre-v14 runtime
// which is the use-case for this hard-coded list
// But we support it anyway just in case
SignedExtensionMetadata(
id = CHECK_METADATA_HASH,
includedInExtrinsic = u8,
includedInSignature = Option(
"OptionMetadataHash",
TypeReference(FixedByteArray("MetadataHash", 32))
)
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ object Extrinsic : RuntimeType<Extrinsic.EncodingInstance, Extrinsic.DecodedInst
Signature(
accountIdentifier = addressType(runtime).decode(scaleCodecReader, runtime),
signature = signatureType(runtime).decode(scaleCodecReader, runtime),
signedExtras = SignedExtras.decode(scaleCodecReader, runtime)
signedExtras = ExtrasIncludedInExtrinsic.decode(scaleCodecReader, runtime)
)
} else {
null
Expand Down Expand Up @@ -107,7 +107,7 @@ object Extrinsic : RuntimeType<Extrinsic.EncodingInstance, Extrinsic.DecodedInst

val addressBytes = addressType(runtime).bytes(runtime, signature.accountIdentifier)
val signatureBytes = signatureType(runtime).bytes(runtime, signature.signature)
val signedExtrasBytes = SignedExtras.bytes(runtime, signature.signedExtras)
val signedExtrasBytes = ExtrasIncludedInExtrinsic.bytes(runtime, signature.signedExtras)

addressBytes + signatureBytes + signedExtrasBytes
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,23 @@ import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.isE
import io.novasama.substrate_sdk_android.runtime.metadata.SignedExtensionId
import io.novasama.substrate_sdk_android.runtime.metadata.SignedExtensionMetadata

object SignedExtras : ExtrinsicPayloadExtras("ExtrinsicPayloadExtras.SignedExtras") {
/**
* @see [SignedExtensionMetadata.includedInExtrinsic]
*/
object ExtrasIncludedInExtrinsic : ExtrinsicPayloadExtras("ExtrinsicPayloadExtras.ExtrasIncludedInExtrinsic") {

override fun getTypeFrom(signedExtension: SignedExtensionMetadata): Type<*>? {
return signedExtension.type
return signedExtension.includedInExtrinsic
}
}

object AdditionalSignedExtras :
ExtrinsicPayloadExtras("ExtrinsicPayloadExtras.AdditionalSignedExtras") {
/**
* @see [SignedExtensionMetadata.includedInSignature]
*/
object ExtrasIncludedInSignature :
ExtrinsicPayloadExtras("ExtrinsicPayloadExtras.ExtrasIncludedInSignature") {
override fun getTypeFrom(signedExtension: SignedExtensionMetadata): Type<*>? {
return signedExtension.additionalSigned
return signedExtension.includedInSignature
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ private fun knownReplacements(): PathMatchTypeMapping {
"*runtime.Event" to eventAlias,

"pallet_identity.types.Data" to AliasTo("Data"),
"sp_runtime.generic.era.Era" to AliasTo("Era")
"sp_runtime.generic.era.Era" to AliasTo("Era"),
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.novasama.substrate_sdk_android.runtime.extrinsic

import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.Struct
import io.novasama.substrate_sdk_android.runtime.metadata.SignedExtensionValue

sealed class CheckMetadataHash {

object Disabled : CheckMetadataHash()

class Enabled(val hash: ByteArray) : CheckMetadataHash()
}

internal fun CheckMetadataHash.toSignedExtensionValue(): SignedExtensionValue {
return when (this) {
CheckMetadataHash.Disabled -> SignedExtensionValue(
includedInExtrinsic = modeStructOf(enabled = false),
includedInSignature = null
)
is CheckMetadataHash.Enabled -> SignedExtensionValue(
includedInExtrinsic = modeStructOf(enabled = true),
includedInSignature = hash
)
}
}

private fun modeStructOf(enabled: Boolean): Struct.Instance {
val mode = if (enabled) {
DictEnum.Entry("Enabled", null)
} else {
DictEnum.Entry("Disabled", null)
}

return Struct.Instance(mapOf("mode" to mode))
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class ExtrinsicBuilder(
private val blockHash: ByteArray = genesisHash,
private val era: Era = Era.Immortal,
private val tip: BigInteger = DEFAULT_TIP,
private val checkMetadataHash: CheckMetadataHash = CheckMetadataHash.Disabled,
customSignedExtensions: Map<SignedExtensionId, SignedExtensionValue> = emptyMap(),
private val addressInstanceConstructor: RuntimeType.InstanceConstructor<AccountId> = AddressInstanceConstructor,
private val signatureConstructor: RuntimeType.InstanceConstructor<SignatureWrapper> = SignatureInstanceConstructor
Expand Down Expand Up @@ -142,7 +143,7 @@ class ExtrinsicBuilder(
signature = Extrinsic.Signature.new(
accountIdentifier = buildEncodableAddressInstance(signedExtrinsic.payload.accountId),
signature = multiSignature,
signedExtras = signedExtrinsic.payload.signedExtras
signedExtras = signedExtrinsic.payload.signedExtras.includedInExtrinsic
),
callRepresentation = signedExtrinsic.payload.call
)
Expand Down Expand Up @@ -173,32 +174,34 @@ class ExtrinsicBuilder(
}

private suspend fun buildSignedExtrinsic(callRepresentation: CallRepresentation): SignedExtrinsic {
val signedExtrasInstance = buildSignedExtras()
val additionalSignedInstance = buildAdditionalSigned()

val signerPayload = SignerPayloadExtrinsic(
runtime = runtime,
accountId = accountId,
call = callRepresentation,
signedExtras = signedExtrasInstance,
additionalSignedExtras = additionalSignedInstance,
signedExtras = SignerPayloadExtrinsic.SignedExtras(
includedInExtrinsic = buildIncludedInExtrinsic(),
includedInSignature = buildIncludedInSignature()

),
nonce = nonce
)

return signer.signExtrinsic(signerPayload)
}

private fun buildAdditionalSigned(): Map<String, Any?> {
private fun buildIncludedInSignature(): Map<String, Any?> {
val default = mapOf(
DefaultSignedExtensions.CHECK_MORTALITY to blockHash,
DefaultSignedExtensions.CHECK_GENESIS to genesisHash,
DefaultSignedExtensions.CHECK_SPEC_VERSION to runtimeVersion.specVersion.toBigInteger(),
DefaultSignedExtensions.CHECK_TX_VERSION to
runtimeVersion.transactionVersion.toBigInteger()
runtimeVersion.transactionVersion.toBigInteger(),
DefaultSignedExtensions.CHECK_METADATA_HASH to
checkMetadataHash.toSignedExtensionValue().includedInSignature
)

val custom = _customSignedExtensions.mapValues { (_, extensionValues) ->
extensionValues.additionalSigned
extensionValues.includedInSignature
}

return default + custom
Expand Down Expand Up @@ -227,15 +230,17 @@ class ExtrinsicBuilder(
return addressInstanceConstructor.constructInstance(runtime.typeRegistry, accountId)
}

private fun buildSignedExtras(): ExtrinsicPayloadExtrasInstance {
private fun buildIncludedInExtrinsic(): ExtrinsicPayloadExtrasInstance {
val default = mapOf(
DefaultSignedExtensions.CHECK_MORTALITY to era,
DefaultSignedExtensions.CHECK_TX_PAYMENT to tip,
DefaultSignedExtensions.CHECK_NONCE to runtime.encodeNonce(nonce.nonce)
DefaultSignedExtensions.CHECK_NONCE to runtime.encodeNonce(nonce.nonce),
DefaultSignedExtensions.CHECK_METADATA_HASH to
checkMetadataHash.toSignedExtensionValue().includedInExtrinsic
)

val custom = _customSignedExtensions.mapValues { (_, extensionValues) ->
extensionValues.signedExtra
extensionValues.includedInExtrinsic
}

return default + custom
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,14 @@ import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.Struct
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.DefaultSignedExtensions
import io.novasama.substrate_sdk_android.runtime.definitions.types.skipAliases
import io.novasama.substrate_sdk_android.runtime.metadata.SignedExtensionId
import io.novasama.substrate_sdk_android.runtime.metadata.SignedExtensionValue
import io.novasama.substrate_sdk_android.runtime.metadata.findSignedExtension
import java.math.BigInteger

fun ExtrinsicBuilder.signedExtra(id: SignedExtensionId, value: Any?) {
signedExtension(id, SignedExtensionValue(signedExtra = value))
}

fun ExtrinsicBuilder.additionalSigned(id: SignedExtensionId, value: Any?) {
signedExtension(id, SignedExtensionValue(additionalSigned = value))
}

fun RuntimeSnapshot.encodeNonce(nonce: BigInteger): Any {
val nonceExtension = metadata.extrinsic
.findSignedExtension(DefaultSignedExtensions.CHECK_NONCE) ?: return nonce

val nonceType = nonceExtension.type?.skipAliases()
val nonceType = nonceExtension.includedInExtrinsic?.skipAliases()

return when {
nonceType is Struct && nonceType.mapping.size == 1 -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,15 @@ data class SignerPayloadExtrinsic(
val runtime: RuntimeSnapshot,
val accountId: AccountId,
val call: CallRepresentation,
val signedExtras: Map<String, Any?>,
val additionalSignedExtras: Map<String, Any?>,
val signedExtras: SignedExtras,
val nonce: Nonce,
)
) {

data class SignedExtras(
val includedInExtrinsic: Map<String, Any?>,
val includedInSignature: Map<String, Any?>,
)
}

fun SignerPayloadRaw.Companion.fromUtf8(
utf8Message: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package io.novasama.substrate_sdk_android.runtime.extrinsic.signer

import io.emeraldpay.polkaj.scale.ScaleCodecWriter
import io.novasama.substrate_sdk_android.hash.Hasher.blake2b256
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.AdditionalSignedExtras
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.DefaultSignedExtensions
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.ExtrasIncludedInExtrinsic
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.ExtrasIncludedInSignature
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Extrinsic
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.SignedExtras
import io.novasama.substrate_sdk_android.runtime.definitions.types.useScaleWriter
import io.novasama.substrate_sdk_android.scale.utils.directWrite

Expand All @@ -30,8 +30,8 @@ fun SignerPayloadExtrinsic.encodeCallDataTo(writer: ScaleCodecWriter) {
fun SignerPayloadExtrinsic.encodedCallData() = bytesOf(::encodeCallDataTo)

fun SignerPayloadExtrinsic.encodeExtensionsTo(writer: ScaleCodecWriter) {
SignedExtras.encode(writer, runtime, signedExtras)
AdditionalSignedExtras.encode(writer, runtime, additionalSignedExtras)
ExtrasIncludedInExtrinsic.encode(writer, runtime, signedExtras.includedInExtrinsic)
ExtrasIncludedInSignature.encode(writer, runtime, signedExtras.includedInSignature)
}

fun SignerPayloadExtrinsic.encodedExtensions() = bytesOf(::encodeExtensionsTo)
Expand All @@ -52,4 +52,4 @@ fun SignerPayloadExtrinsic.encodedSignaturePayload(hashBigPayloads: Boolean = tr
}

val SignerPayloadExtrinsic.genesisHash
get() = additionalSignedExtras[DefaultSignedExtensions.CHECK_GENESIS] as ByteArray
get() = signedExtras.includedInSignature[DefaultSignedExtensions.CHECK_GENESIS] as ByteArray
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface WithName {
fun <T : WithName> List<T>.groupByName() = associateBy(WithName::name).toMap()

class RuntimeMetadata(
val runtimeVersion: BigInteger,
val metadataVersion: Int,
val modules: Map<String, Module>,
val extrinsic: ExtrinsicMetadata
)
Expand All @@ -25,34 +25,42 @@ class ExtrinsicMetadata(
typealias SignedExtensionId = String

class SignedExtensionValue(
val signedExtra: Any? = null,
val additionalSigned: Any? = null
val includedInExtrinsic: Any? = null,
val includedInSignature: Any? = null,
)

class SignedExtensionMetadata(
val id: SignedExtensionId,
val type: RuntimeType<*, *>?,
val additionalSigned: RuntimeType<*, *>?

/**
* Additional information that is included both into extrinsic and signature payload
* Those values are configurable by the user and can be extracted from signed extrinsic open decoding
*
* Examples: tip, mortality, nonce
*/
val includedInExtrinsic: RuntimeType<*, *>?,

/**
* Additional information, that is only included into signature
* Those values are non-configurable by the user and should always be equal to those used by runtime that verifies the signature
* They cannot be extracted from the signed extrinsic
*
* Examples: genesis hash, runtime version
*/
val includedInSignature: RuntimeType<*, *>?
) {

companion object {

/**
* SignedExtras is signature params that are both signed
* and put separately in payload for verification
* Examples: tip, mortality
*/
fun onlySigned(id: String, type: RuntimeType<*, *>): SignedExtensionMetadata {
return SignedExtensionMetadata(id, type, Null)
fun onlyInExtrinsic(id: String, includedInExtrinsic: RuntimeType<*, *>): SignedExtensionMetadata {
return SignedExtensionMetadata(id, includedInExtrinsic, Null)
}

/**
* AdditionalSigned is signature params that are signed
* and that are verified by runtime based on-chain state
* Examples: genesis hash, runtime version
*/
fun onlyAdditional(id: String, additionalSigned: RuntimeType<*, *>): SignedExtensionMetadata {
return SignedExtensionMetadata(id, Null, additionalSigned)
fun onlyInSignature(
id: String,
includedInSignature: RuntimeType<*, *>
): SignedExtensionMetadata {
return SignedExtensionMetadata(id, Null, includedInSignature)
}
}
}
Expand Down
Loading

0 comments on commit 2dd0ad6

Please sign in to comment.