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

Enrich sendable extrinsic #100

Merged
merged 5 commits into from
Dec 8, 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.3.0'
versionName = '2.4.0'
versionCode = 1

// SDK and tools
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,24 @@ package io.novasama.substrate_sdk_android.runtime.definitions.types.generics
import io.emeraldpay.polkaj.scale.ScaleCodecReader
import io.emeraldpay.polkaj.scale.ScaleCodecWriter
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.Type
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.toByteArray
import io.novasama.substrate_sdk_android.runtime.definitions.types.useScaleWriter
import io.novasama.substrate_sdk_android.scale.dataType.byte
import io.novasama.substrate_sdk_android.scale.dataType.compactInt
import io.novasama.substrate_sdk_android.scale.dataType.toByteArray
import io.novasama.substrate_sdk_android.scale.utils.directWrite

private val SIGNED_MASK = 0b1000_0000.toUByte()

private const val TYPE_ADDRESS = "Address"
private const val TYPE_SIGNATURE = "ExtrinsicSignature"

@OptIn(ExperimentalUnsignedTypes::class)
object Extrinsic : RuntimeType<Extrinsic.EncodingInstance, Extrinsic.DecodedInstance>("ExtrinsicsDecoder") {
object Extrinsic : Type<Extrinsic.Instance>("ExtrinsicsDecoder") {

class EncodingInstance(
val signature: Signature?,
val callRepresentation: CallRepresentation
) {
sealed class CallRepresentation {

class Instance(val call: GenericCall.Instance) : CallRepresentation()

class Bytes(val bytes: ByteArray) : CallRepresentation()
}
}

class DecodedInstance(
class Instance(
val signature: Signature?,
val call: GenericCall.Instance
)
Expand All @@ -56,7 +45,7 @@ object Extrinsic : RuntimeType<Extrinsic.EncodingInstance, Extrinsic.DecodedInst
override fun decode(
scaleCodecReader: ScaleCodecReader,
runtime: RuntimeSnapshot
): DecodedInstance {
): Instance {
val length = compactInt.read(scaleCodecReader)

val extrinsicVersion = byte.read(scaleCodecReader).toUByte()
Expand All @@ -73,30 +62,34 @@ object Extrinsic : RuntimeType<Extrinsic.EncodingInstance, Extrinsic.DecodedInst

val call = GenericCall.decode(scaleCodecReader, runtime)

return DecodedInstance(signature, call)
return Instance(signature, call)
}

override fun encode(
scaleCodecWriter: ScaleCodecWriter,
runtime: RuntimeSnapshot,
value: EncodingInstance
value: Instance
) {
val callBytes = when (value.callRepresentation) {
is EncodingInstance.CallRepresentation.Instance ->
GenericCall.toByteArray(runtime, value.callRepresentation.call)

is EncodingInstance.CallRepresentation.Bytes -> value.callRepresentation.bytes
}
encode(scaleCodecWriter, runtime, value, encodeLength = true)
}

encode(scaleCodecWriter, runtime, value.signature, callBytes)
fun encodeWithoutLength(
scaleCodecWriter: ScaleCodecWriter,
runtime: RuntimeSnapshot,
value: Instance
) {
encode(scaleCodecWriter, runtime, value, encodeLength = false)
}

private fun encode(
scaleCodecWriter: ScaleCodecWriter,
runtime: RuntimeSnapshot,
signature: Signature?,
callBytes: ByteArray
value: Instance,
encodeLength: Boolean,
) {
val signature = value.signature
val callBytes = GenericCall.toByteArray(runtime, value.call)

val isSigned = signature != null

val extrinsicVersion = runtime.metadata.extrinsic.version.toInt().toUByte()
Expand All @@ -116,11 +109,15 @@ object Extrinsic : RuntimeType<Extrinsic.EncodingInstance, Extrinsic.DecodedInst

val extrinsicBodyBytes = byteArrayOf(encodedVersion) + signatureWrapperBytes + callBytes

Bytes.encode(scaleCodecWriter, runtime, extrinsicBodyBytes)
if (encodeLength) {
Bytes.encode(scaleCodecWriter, runtime, extrinsicBodyBytes)
} else {
scaleCodecWriter.directWrite(extrinsicBodyBytes)
}
}

override fun isValidInstance(instance: Any?): Boolean {
return instance is EncodingInstance
return instance is Instance
}

private fun encodedVersion(version: UByte, isSigned: Boolean): UByte {
Expand All @@ -144,3 +141,7 @@ object Extrinsic : RuntimeType<Extrinsic.EncodingInstance, Extrinsic.DecodedInst
throw EncodeDecodeException("Cannot resolve $name type, which is required to work with Extrinsic")
}
}

fun Extrinsic.toBytesWithoutLength(runtime: RuntimeSnapshot, value: Extrinsic.Instance): ByteArray {
return useScaleWriter { encodeWithoutLength(this, runtime, value) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,3 @@ fun multiAddressFromId(addressId: ByteArray): DictEnum.Entry<ByteArray> {
value = addressId
)
}

fun Extrinsic.EncodingInstance(
signature: Extrinsic.Signature?,
call: GenericCall.Instance
): Extrinsic.EncodingInstance {
return Extrinsic.EncodingInstance(
signature,
Extrinsic.EncodingInstance.CallRepresentation.Instance(call)
)
}

fun Extrinsic.EncodingInstance(
signature: Extrinsic.Signature?,
callBytes: ByteArray
): Extrinsic.EncodingInstance {
return Extrinsic.EncodingInstance(
signature,
Extrinsic.EncodingInstance.CallRepresentation.Bytes(callBytes)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,11 @@ import io.novasama.substrate_sdk_android.runtime.definitions.types.RuntimeType
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.DefaultSignedExtensions
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Era
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Extrinsic
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Extrinsic.EncodingInstance.CallRepresentation
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.ExtrinsicPayloadExtrasInstance
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.new
import io.novasama.substrate_sdk_android.runtime.definitions.types.instances.AddressInstanceConstructor
import io.novasama.substrate_sdk_android.runtime.definitions.types.instances.SignatureInstanceConstructor
import io.novasama.substrate_sdk_android.runtime.definitions.types.toHex
import io.novasama.substrate_sdk_android.runtime.definitions.types.toHexUntyped
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.SendableExtrinsic
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.SignedExtrinsic
import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.Signer
Expand Down Expand Up @@ -114,14 +111,7 @@ class ExtrinsicBuilder(
batchMode: BatchMode = BatchMode.BATCH
): SendableExtrinsic {
val call = maybeWrapInBatch(batchMode)
return buildSendableExtrinsic(CallRepresentation.Instance(call))
}

suspend fun buildExtrinsic(
rawCallBytes: ByteArray
): SendableExtrinsic {
requireNotMixingBytesAndInstanceCalls()
return buildSendableExtrinsic(CallRepresentation.Bytes(rawCallBytes))
return buildSendableExtrinsic(call)
}

private fun maybeWrapInBatch(batchMode: BatchMode): GenericCall.Instance {
Expand All @@ -134,13 +124,11 @@ class ExtrinsicBuilder(
}
}

private suspend fun buildSendableExtrinsic(
callRepresentation: CallRepresentation
): SendableExtrinsic {
private suspend fun buildSendableExtrinsic(call: GenericCall.Instance): SendableExtrinsic {
val signerPayload = SignerPayloadExtrinsic(
runtime = runtime,
accountId = accountId,
call = callRepresentation,
call = call,
signedExtras = SignerPayloadExtrinsic.SignedExtras(
includedInExtrinsic = buildIncludedInExtrinsic(),
includedInSignature = buildIncludedInSignature()
Expand All @@ -150,8 +138,25 @@ class ExtrinsicBuilder(
)

val signedExtrinsic = signer.signExtrinsic(signerPayload)
val instance = buildEncodingExtrinsic(signedExtrinsic)

return SendableExtrinsic(runtime, instance)
}

return RealSendableExtrinsic(signedExtrinsic)
private fun buildEncodingExtrinsic(signedExtrinsic: SignedExtrinsic): Extrinsic.Instance {
val address = buildEncodableAddressInstance(signedExtrinsic.payload.accountId)
val multiSignature = signatureConstructor.constructInstance(
runtime.typeRegistry,
signedExtrinsic.signatureWrapper
)
return Extrinsic.Instance(
signature = Extrinsic.Signature.new(
accountIdentifier = address,
signature = multiSignature,
signedExtras = signedExtrinsic.payload.signedExtras.includedInExtrinsic
),
call = signedExtrinsic.payload.call
)
}

private fun buildIncludedInSignature(): Map<String, Any?> {
Expand Down Expand Up @@ -223,41 +228,4 @@ class ExtrinsicBuilder(
"Cannot mix instance and raw bytes calls"
}
}

private inner class RealSendableExtrinsic(
private val signedExtrinsic: SignedExtrinsic
) : SendableExtrinsic {

private val multiSignature = signatureConstructor.constructInstance(
runtime.typeRegistry,
signedExtrinsic.signatureWrapper
)

override val extrinsicHex by lazy {
createExtrinsicHex()
}

override val signatureHex by lazy {
createSignatureHex()
}

private fun createExtrinsicHex(): String {
val address = buildEncodableAddressInstance(signedExtrinsic.payload.accountId)
val extrinsic = Extrinsic.EncodingInstance(
signature = Extrinsic.Signature.new(
accountIdentifier = address,
signature = multiSignature,
signedExtras = signedExtrinsic.payload.signedExtras.includedInExtrinsic
),
callRepresentation = signedExtrinsic.payload.call
)

return Extrinsic.toHex(runtime, extrinsic)
}

private fun createSignatureHex(): String {
val signatureType = Extrinsic.signatureType(runtime)
return signatureType.toHexUntyped(runtime, multiSignature)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,57 @@
package io.novasama.substrate_sdk_android.runtime.extrinsic.signer

import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Extrinsic
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.toBytesWithoutLength
import io.novasama.substrate_sdk_android.runtime.definitions.types.toHex
import io.novasama.substrate_sdk_android.runtime.definitions.types.toHexUntyped

interface SendableExtrinsic {

val extrinsic: Extrinsic.Instance

val signatureHex: String

val extrinsicHex: String

val bytesWithoutLength: ByteArray
}

fun SendableExtrinsic(
runtime: RuntimeSnapshot,
extrinsic: Extrinsic.Instance
): SendableExtrinsic {
return RealSendableExtrinsic(runtime, extrinsic)
}

private class RealSendableExtrinsic(
private val runtime: RuntimeSnapshot,
override val extrinsic: Extrinsic.Instance
) : SendableExtrinsic {

override val extrinsicHex by lazy {
createExtrinsicHex()
}

override val bytesWithoutLength: ByteArray by lazy {
Extrinsic.toBytesWithoutLength(runtime, extrinsic)
}

override val signatureHex by lazy {
createSignatureHex()
}

private fun createExtrinsicHex(): String {
return Extrinsic.toHex(runtime, extrinsic)
}

private fun createSignatureHex(): String {
requireNotNull(extrinsic.signature) {
"Extrinsic is unsigned"
}

val signatureType = Extrinsic.signatureType(runtime)

return signatureType.toHexUntyped(runtime, extrinsic.signature.signature)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import io.novasama.substrate_sdk_android.encrypt.SignatureWrapper
import io.novasama.substrate_sdk_android.extensions.fromHex
import io.novasama.substrate_sdk_android.runtime.AccountId
import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Extrinsic.EncodingInstance.CallRepresentation
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall
import io.novasama.substrate_sdk_android.runtime.extrinsic.Nonce

class SignedExtrinsic(
Expand All @@ -28,7 +28,7 @@ class SignerPayloadRaw(
data class SignerPayloadExtrinsic(
val runtime: RuntimeSnapshot,
val accountId: AccountId,
val call: CallRepresentation,
val call: GenericCall.Instance,
val signedExtras: SignedExtras,
val nonce: Nonce,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import io.novasama.substrate_sdk_android.hash.Hasher.blake2b256
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.useScaleWriter
import io.novasama.substrate_sdk_android.scale.utils.directWrite

private const val PAYLOAD_HASH_THRESHOLD = 256

Expand All @@ -18,13 +16,7 @@ fun bytesOf(vararg writers: (ScaleCodecWriter) -> Unit) = useScaleWriter {
}

fun SignerPayloadExtrinsic.encodeCallDataTo(writer: ScaleCodecWriter) {
when (call) {
is Extrinsic.EncodingInstance.CallRepresentation.Bytes ->
writer.directWrite(call.bytes)

is Extrinsic.EncodingInstance.CallRepresentation.Instance ->
GenericCall.encode(writer, runtime, call.call)
}
GenericCall.encode(writer, runtime, call)
}

fun SignerPayloadExtrinsic.encodedCallData() = bytesOf(::encodeCallDataTo)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import io.novasama.substrate_sdk_android.extensions.toHexString
import io.novasama.substrate_sdk_android.runtime.RealRuntimeProvider
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum
import io.novasama.substrate_sdk_android.runtime.definitions.types.fromHex
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Extrinsic.EncodingInstance.*
import io.novasama.substrate_sdk_android.runtime.definitions.types.instances.AddressInstanceConstructor
import io.novasama.substrate_sdk_android.runtime.definitions.types.instances.SignatureInstanceConstructor
import io.novasama.substrate_sdk_android.runtime.definitions.types.toHex
Expand Down Expand Up @@ -73,7 +72,7 @@ class ExtrinsicTest {
value = SignatureWrapper.Sr25519(signatureInHex.fromHex())
)

val extrinsic = Extrinsic.EncodingInstance(
val extrinsic = Extrinsic.Instance(
signature = Extrinsic.Signature.new(
accountIdentifier = AddressInstanceConstructor.constructInstance(
typeRegistry = runtime.typeRegistry,
Expand All @@ -82,7 +81,7 @@ class ExtrinsicTest {
signature = signature,
signedExtras = signedExtras
),
callRepresentation = CallRepresentation.Instance(call)
call = call
)

val encoded = Extrinsic.toHex(runtime, extrinsic)
Expand Down
Loading
Loading