Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support automatic runtime apis detection for metadata v15 and above #99

Merged
merged 3 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.2.2'
versionName = '2.3.0'
versionCode = 1

// SDK and tools
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.novasama.substrate_sdk_android.runtime.definitions.types.Type
import io.novasama.substrate_sdk_android.runtime.definitions.types.TypeReference
import io.novasama.substrate_sdk_android.runtime.definitions.types.resolvedOrNull
import io.novasama.substrate_sdk_android.runtime.definitions.types.skipAliases
import java.math.BigInteger

interface RequestPreprocessor {

Expand All @@ -25,6 +26,10 @@ class TypeRegistry(
return typeRef?.value
}

operator fun get(typeIndex: BigInteger): Type<*>? {
return get(typeIndex.toString())
}

inline operator fun <reified R> get(key: String): R? {
return get(key)?.let { it as? R }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,9 @@ class ExtrinsicBuilder(
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
checkMetadataHash.toSignedExtensionValue().includedInSignature
)

val custom = _customSignedExtensions.mapValues { (_, extensionValues) ->
Expand Down Expand Up @@ -208,7 +208,7 @@ class ExtrinsicBuilder(
DefaultSignedExtensions.CHECK_TX_PAYMENT to tip,
DefaultSignedExtensions.CHECK_NONCE to runtime.encodeNonce(nonce.nonce),
DefaultSignedExtensions.CHECK_METADATA_HASH to
checkMetadataHash.toSignedExtensionValue().includedInExtrinsic
checkMetadataHash.toSignedExtensionValue().includedInExtrinsic
)

val custom = _customSignedExtensions.mapValues { (_, extensionValues) ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.novasama.substrate_sdk_android.runtime.metadata
import io.novasama.substrate_sdk_android.runtime.definitions.types.RuntimeType
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Null
import io.novasama.substrate_sdk_android.runtime.metadata.module.Module
import io.novasama.substrate_sdk_android.runtime.metadata.module.RuntimeApi
import java.math.BigInteger

interface WithName {
Expand All @@ -14,7 +15,9 @@ fun <T : WithName> List<T>.groupByName() = associateBy(WithName::name).toMap()
class RuntimeMetadata(
val metadataVersion: Int,
val modules: Map<String, Module>,
val extrinsic: ExtrinsicMetadata
val extrinsic: ExtrinsicMetadata,
// Present in v15+ metadata. null otherwise
val apis: List<RuntimeApi>?
)

class ExtrinsicMetadata(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@ import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot
import io.novasama.substrate_sdk_android.runtime.definitions.types.RuntimeType
import io.novasama.substrate_sdk_android.runtime.definitions.types.bytes
import io.novasama.substrate_sdk_android.runtime.definitions.types.errors.EncodeDecodeException
import io.novasama.substrate_sdk_android.runtime.definitions.types.fromHex
import io.novasama.substrate_sdk_android.runtime.definitions.types.useScaleWriter
import io.novasama.substrate_sdk_android.runtime.metadata.module.ErrorMetadata
import io.novasama.substrate_sdk_android.runtime.metadata.module.Event
import io.novasama.substrate_sdk_android.runtime.metadata.module.MetadataFunction
import io.novasama.substrate_sdk_android.runtime.metadata.module.Module
import io.novasama.substrate_sdk_android.runtime.metadata.module.RuntimeApi
import io.novasama.substrate_sdk_android.runtime.metadata.module.RuntimeApiMethod
import io.novasama.substrate_sdk_android.runtime.metadata.module.StorageEntry
import io.novasama.substrate_sdk_android.runtime.metadata.module.StorageEntryType
import io.novasama.substrate_sdk_android.wsrpc.request.runtime.state.StateCallRequest
import java.io.ByteArrayOutputStream

/**
Expand Down Expand Up @@ -138,7 +143,7 @@ fun StorageEntry.storageKey(runtime: RuntimeSnapshot, vararg arguments: Any?): S
val argumentType = argumentsTypes[index]
val argumentHasher = argumentsHashers[index]

val keyEncoded = argumentType?.bytes(runtime, key) ?: typeNotResolved(fullName)
val keyEncoded = argumentType?.bytes(runtime, key) ?: storageTypeNotResolved(fullName)

keyOutputStream.write(argumentHasher.hashingFunction(keyEncoded))
}
Expand All @@ -154,7 +159,10 @@ fun StorageEntry.storageKey(runtime: RuntimeSnapshot, vararg arguments: Any?): S
* @throws IllegalStateException if some of types used for encoding cannot be resolved
* @throws EncodeDecodeException if error happened during encoding
*/
fun StorageEntry.storageKeys(runtime: RuntimeSnapshot, keysArguments: List<List<Any?>>): List<String> {
fun StorageEntry.storageKeys(
runtime: RuntimeSnapshot,
keysArguments: List<List<Any?>>
): List<String> {
val argumentsTypes = this.keys
val argumentsHashers = this.hashers

Expand All @@ -173,7 +181,7 @@ fun StorageEntry.storageKeys(runtime: RuntimeSnapshot, keysArguments: List<List<
val argumentType = argumentsTypes[index]
val argumentHasher = argumentsHashers[index]

val keyEncoded = argumentType?.bytes(runtime, key) ?: typeNotResolved(fullName)
val keyEncoded = argumentType?.bytes(runtime, key) ?: storageTypeNotResolved(fullName)

keyOutputStream.write(argumentHasher.hashingFunction(keyEncoded))
}
Expand Down Expand Up @@ -232,7 +240,68 @@ fun Module.fullNameOf(withName: WithName): String {
val StorageEntry.fullName
get() = "$moduleName.$name"

private fun typeNotResolved(entryName: String): Nothing =
fun RuntimeMetadata.runtimeApi(name: String): RuntimeApi {
return runtimeApiOrNull(name) ?: error("Runtime Api $name is not found")
}

fun RuntimeMetadata.runtimeApiOrNull(name: String): RuntimeApi? {
if (apis == null) {
error("This version of metadata does not support auto-detection of runtime apis")
}

return apis.find { it.name == name }
}

fun RuntimeApi.methodOrNull(name: String): RuntimeApiMethod? {
return methods.find { it.name == name }
}

fun RuntimeApi.method(name: String): RuntimeApiMethod {
return methodOrNull(name) ?: error("Method $name is not found in runtime api ${this.name}")
}

fun RuntimeApiMethod.createRequest(
runtime: RuntimeSnapshot,
inputValues: Map<String, Any?>
): StateCallRequest {
return StateCallRequest(
runtimeRpcName = "${apiName}_$name",
encodedArguments = encodeInputs(runtime, inputValues)
)
}

fun RuntimeApiMethod.encodeInputs(
runtime: RuntimeSnapshot,
inputValues: Map<String, Any?>
): String {
return useScaleWriter {
inputs.forEach { methodParam ->
val inputValue = inputValues.getValue(methodParam.name)

val type = methodParam.type ?: runtimeMethodInputNotResolved(name, methodParam.name)
type.encodeUnsafe(this, runtime, inputValue)
}
}.toHexString(withPrefix = true)
}

fun RuntimeApiMethod.decodeOutput(
runtime: RuntimeSnapshot,
outputEncoded: String
): Any? {
val outputType = output ?: runtimeMethodOutputNotResolved(name)

return outputType.fromHex(runtime, outputEncoded)
}

private fun runtimeMethodInputNotResolved(methodName: String, argumentName: String): Nothing {
error("Cannot resolve type for input $argumentName of $methodName method")
}

private fun runtimeMethodOutputNotResolved(methodName: String): Nothing {
error("Cannot resolve type for output of $methodName method")
}

private fun storageTypeNotResolved(entryName: String): Nothing =
throw IllegalStateException("Cannot resolve key or value type for storage entry `$entryName`")

private fun wrongEntryType(): Nothing =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import io.emeraldpay.polkaj.scale.ScaleCodecReader
import io.novasama.substrate_sdk_android.extensions.fromHex
import io.novasama.substrate_sdk_android.runtime.metadata.v14.PostV14MetadataSchema
import io.novasama.substrate_sdk_android.runtime.metadata.v14.RuntimeMetadataSchemaV14
import io.novasama.substrate_sdk_android.runtime.metadata.v14.RuntimeMetadataSchemaV15
import io.novasama.substrate_sdk_android.runtime.metadata.v15.RuntimeMetadataSchemaV15
import io.novasama.substrate_sdk_android.scale.EncodableStruct
import io.novasama.substrate_sdk_android.scale.Schema
import io.novasama.substrate_sdk_android.scale.uint32
Expand All @@ -15,12 +15,12 @@ object Magic : Schema<Magic>() {
val runtimeVersion by uint8()
}

@Suppress("UNCHECKED_CAST")
class RuntimeMetadataReader private constructor(
val metadataVersion: Int,
val metadata: EncodableStruct<*>
) {

@Suppress("UNCHECKED_CAST")
val metadataPostV14: EncodableStruct<PostV14MetadataSchema<*>>
get() {
require(metadata.schema is PostV14MetadataSchema<*>) {
Expand All @@ -30,6 +30,15 @@ class RuntimeMetadataReader private constructor(
return metadata as EncodableStruct<PostV14MetadataSchema<*>>
}

val metadataV15: EncodableStruct<RuntimeMetadataSchemaV15>
get() {
require(metadata.schema is RuntimeMetadataSchemaV15) {
"Metadata is not v15"
}

return metadata as EncodableStruct<RuntimeMetadataSchemaV15>
}

companion object {

fun read(metadaScale: String): RuntimeMetadataReader {
Expand Down Expand Up @@ -57,7 +66,6 @@ class RuntimeMetadataReader private constructor(
return read(scaleCoderReader)
}

@OptIn(ExperimentalUnsignedTypes::class)
private fun read(reader: ScaleCodecReader): RuntimeMetadataReader {
val runtimeVersion = Magic.read(reader)[Magic.runtimeVersion].toInt()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface RuntimeBuilder {
fun buildMetadata(
reader: RuntimeMetadataReader,
typeRegistry: TypeRegistry,
knownSignedExtensions: List<SignedExtensionMetadata> = DefaultSignedExtensions.ALL,
fallbackSignedExtensions: List<SignedExtensionMetadata> = DefaultSignedExtensions.ALL,
): RuntimeMetadata
}

Expand All @@ -20,11 +20,12 @@ object VersionedRuntimeBuilder : RuntimeBuilder {
override fun buildMetadata(
reader: RuntimeMetadataReader,
typeRegistry: TypeRegistry,
knownSignedExtensions: List<SignedExtensionMetadata>,
fallbackSignedExtensions: List<SignedExtensionMetadata>,
): RuntimeMetadata {
return when (reader.metadataVersion) {
14, 15 -> PostV14RuntimeBuilder.buildMetadata(reader, typeRegistry, knownSignedExtensions)
else -> V13RuntimeBuilder.buildMetadata(reader, typeRegistry, knownSignedExtensions)
15 -> V15RuntimeBuilder.buildMetadata(reader, typeRegistry, fallbackSignedExtensions)
14 -> V14RuntimeBuilder.buildMetadata(reader, typeRegistry, fallbackSignedExtensions)
else -> V13RuntimeBuilder.buildMetadata(reader, typeRegistry, fallbackSignedExtensions)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,12 @@ import io.novasama.substrate_sdk_android.runtime.metadata.module.StorageEntry
import io.novasama.substrate_sdk_android.runtime.metadata.module.StorageEntryType
import io.novasama.substrate_sdk_android.scale.EncodableStruct

@OptIn(ExperimentalUnsignedTypes::class)
internal object V13RuntimeBuilder : RuntimeBuilder {

override fun buildMetadata(
reader: RuntimeMetadataReader,
typeRegistry: TypeRegistry,
knownSignedExtensions: List<SignedExtensionMetadata>
fallbackSignedExtensions: List<SignedExtensionMetadata>
): RuntimeMetadata {
val metadataStruct = reader.metadata

Expand All @@ -45,10 +44,11 @@ internal object V13RuntimeBuilder : RuntimeBuilder {
return RuntimeMetadata(
extrinsic = buildExtrinsic(
struct = metadataStruct[RuntimeMetadataSchema.extrinsic],
knownSignedExtensions = knownSignedExtensions
knownSignedExtensions = fallbackSignedExtensions
),
modules = buildModules(metadataStruct[RuntimeMetadataSchema.modules], typeRegistry),
metadataVersion = reader.metadataVersion
metadataVersion = reader.metadataVersion,
apis = null
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,12 @@ import io.novasama.substrate_sdk_android.runtime.metadata.v14.StorageMetadataV14
import io.novasama.substrate_sdk_android.scale.EncodableStruct
import java.math.BigInteger

@OptIn(ExperimentalUnsignedTypes::class)
object PostV14RuntimeBuilder : RuntimeBuilder {
object V14RuntimeBuilder : RuntimeBuilder {

override fun buildMetadata(
reader: RuntimeMetadataReader,
typeRegistry: TypeRegistry,
knownSignedExtensions: List<SignedExtensionMetadata>,
fallbackSignedExtensions: List<SignedExtensionMetadata>,
): RuntimeMetadata {
val metadataStruct = reader.metadata

Expand All @@ -53,7 +52,8 @@ object PostV14RuntimeBuilder : RuntimeBuilder {
typeRegistry
),
modules = buildModules(metadataStruct[schema.pallets], typeRegistry),
metadataVersion = reader.metadataVersion
metadataVersion = reader.metadataVersion,
apis = null
)
}

Expand Down Expand Up @@ -122,7 +122,7 @@ object PostV14RuntimeBuilder : RuntimeBuilder {
moduleIndex: Int,
): Map<String, MetadataFunction> {

val type = typeRegistry[callsRaw[PalletCallMetadataV14.type].toString()]
val type = typeRegistry[callsRaw[PalletCallMetadataV14.type]]

if (type !is DictEnum) return emptyMap()

Expand All @@ -144,7 +144,7 @@ object PostV14RuntimeBuilder : RuntimeBuilder {
moduleIndex: Int,
): Map<String, Event> {

val type = typeRegistry[eventsRaw[PalletEventMetadataV14.type].toString()]
val type = typeRegistry[eventsRaw[PalletEventMetadataV14.type]]

if (type !is DictEnum) return emptyMap()

Expand Down Expand Up @@ -180,7 +180,7 @@ object PostV14RuntimeBuilder : RuntimeBuilder {
): Map<String, Constant> {

return constantsRaw.map { constantStruct ->
val typeIndex = constantStruct[PalletConstantMetadataV14.type].toString()
val typeIndex = constantStruct[PalletConstantMetadataV14.type]

Constant(
name = constantStruct[PalletConstantMetadataV14.name],
Expand All @@ -196,7 +196,7 @@ object PostV14RuntimeBuilder : RuntimeBuilder {
errorsRaw: EncodableStruct<PalletErrorMetadataV14>,
): Map<Int, ErrorMetadata> {

val type = typeRegistry[errorsRaw[PalletErrorMetadataV14.type].toString()]
val type = typeRegistry[errorsRaw[PalletErrorMetadataV14.type]]

if (type !is DictEnum) return emptyMap()

Expand All @@ -215,16 +215,17 @@ object PostV14RuntimeBuilder : RuntimeBuilder {
): StorageEntryType {
return when (enumValue) {
is BigInteger -> {
StorageEntryType.Plain(typeRegistry[enumValue.toString()])
StorageEntryType.Plain(typeRegistry[enumValue])
}

is EncodableStruct<*> -> {
requireOrException(enumValue.schema is MapTypeV14) {
cannotConstructStorageEntry(enumValue)
}

val hashers = enumValue[MapTypeV14.hashers]

val type = typeRegistry[enumValue[MapTypeV14.key].toString()]
val type = typeRegistry[enumValue[MapTypeV14.key]]
?: cannotConstructStorageEntry(enumValue)

val keys = if (hashers.size == 1) {
Expand All @@ -244,9 +245,10 @@ object PostV14RuntimeBuilder : RuntimeBuilder {
StorageEntryType.NMap(
keys,
hashers,
typeRegistry[enumValue[MapTypeV14.value].toString()]
typeRegistry[enumValue[MapTypeV14.value]]
)
}

else -> cannotConstructStorageEntry(enumValue)
}
}
Expand All @@ -262,8 +264,8 @@ object PostV14RuntimeBuilder : RuntimeBuilder {
signedExtensions = struct[schema.signedExtensions].map {
SignedExtensionMetadata(
id = it[SignedExtensionMetadataV14.identifier],
includedInExtrinsic = typeRegistry[it[SignedExtensionMetadataV14.type].toString()],
includedInSignature = typeRegistry[it[SignedExtensionMetadataV14.additionalSigned].toString()]
includedInExtrinsic = typeRegistry[it[SignedExtensionMetadataV14.type]],
includedInSignature = typeRegistry[it[SignedExtensionMetadataV14.additionalSigned]]
)
}
)
Expand Down
Loading
Loading