Skip to content
This repository has been archived by the owner on Sep 14, 2023. It is now read-only.

Commit

Permalink
feat: async encoding (#176)
Browse files Browse the repository at this point in the history
  • Loading branch information
harrysolovay authored Aug 3, 2022
1 parent e99a404 commit ceb0736
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 52 deletions.
7 changes: 6 additions & 1 deletion deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
"include": ["."]
},
"rules": {
"exclude": ["no-empty-interface", "no-explicit-any", "no-namespace"],
"exclude": [
"no-empty-interface",
"no-explicit-any",
"no-namespace",
"no-empty"
],
"tags": ["recommended"]
}
},
Expand Down
17 changes: 17 additions & 0 deletions effect/atoms/$ExtrinsicEncodeAsync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as M from "../../frame_metadata/mod.ts";
import { Hashers } from "../../hashers/mod.ts";
import { atomFactory } from "../sys/Atom.ts";

// TODO: remove this upon enabling async codecs / utilizing async sign in extrinsic codec
export const $extrinsicEncodeAsync = atomFactory("ExtrinsicEncodeAsync", async (
deriveCodec: M.DeriveCodec,
metadata: M.Metadata,
sign?: M.SignExtrinsic,
) => {
return M.$encodeAsync({
deriveCodec,
hashers: await Hashers(),
metadata,
sign: sign!,
});
});
1 change: 1 addition & 0 deletions effect/atoms/mod.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./$Extrinsic.ts";
export * from "./$ExtrinsicEncodeAsync.ts";
export * from "./$Key.ts";
export * from "./$StorageKey.ts";
export * from "./Codec.ts";
Expand Down
6 changes: 3 additions & 3 deletions effect/std/submitAndWatchExtrinsic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function sendAndWatchExtrinsic<
) {
const metadata = a.metadata(config);
const deriveCodec = a.deriveCodec(metadata);
const $extrinsic = a.$extrinsic(deriveCodec, metadata, sign);
const $extrinsic = a.$extrinsicEncodeAsync(deriveCodec, metadata, sign);
const runtimeVersion = a.rpcCall(config, "state_getRuntimeVersion", []);
const senderSs58 = sys.anon([sender], async (sender) => {
const ss58 = await Ss58();
Expand All @@ -55,7 +55,7 @@ export function sendAndWatchExtrinsic<
runtimeVersion,
accountNextIndex,
genesisHash,
], (
], async (
$extrinsic,
sender,
methodName,
Expand All @@ -65,7 +65,7 @@ export function sendAndWatchExtrinsic<
{ result: genesisHashHex },
) => {
const genesisHash = U.hex.decode(genesisHashHex);
const extrinsicBytes = $extrinsic.encode({
const extrinsicBytes = await $extrinsic({
protocolVersion: 4, // TODO: grab this from elsewhere
palletName,
methodName,
Expand Down
133 changes: 85 additions & 48 deletions frame_metadata/Extrinsic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface Signature {
value: Uint8Array;
}

export type SignExtrinsic = (message: Uint8Array) => Signature;
export type SignExtrinsic = (message: Uint8Array) => Signature | Promise<Signature>;

export interface Extrinsic {
protocolVersion: number;
Expand Down Expand Up @@ -67,8 +67,8 @@ function encodeCall(
});
}

export function $extrinsic(props: ExtrinsicCodecProps): $.Codec<Extrinsic> {
const { metadata, sign, deriveCodec, hashers: { Blake2_256 } } = props;
function setup(props: ExtrinsicCodecProps) {
const { metadata, deriveCodec } = props;
const { signedExtensions } = metadata.extrinsic;
const $sig = deriveCodec(findExtrinsicTypeParam("Signature")!);
const $address = deriveCodec(findExtrinsicTypeParam("Address")!);
Expand All @@ -78,48 +78,23 @@ export function $extrinsic(props: ExtrinsicCodecProps): $.Codec<Extrinsic> {
const $call = deriveCodec(callTyI);
const $extra = getExtrasCodec(signedExtensions.map((x) => x.ty));
const $additional = getExtrasCodec(signedExtensions.map((x) => x.additionalSigned));

return { $sig, $address, callTy, $call, $extra, $additional };

function findExtrinsicTypeParam(name: string) {
return metadata.tys[metadata.extrinsic.ty]?.params.find((x) => x.name === name)?.ty;
}
function getExtrasCodec(is: number[]) {
return $.tuple(...is.map((i) => deriveCodec(i)).filter((x) => x !== $null));
}
}

export function $extrinsic(props: ExtrinsicCodecProps): $.Codec<Extrinsic> {
const { $address, $call, $extra, $sig, callTy, $additional } = setup(props);
const $baseExtrinsic: $.Codec<Extrinsic> = $.createCodec({
_metadata: null,
_staticSize: 1 + $.tuple($address, $sig, $extra, $call)._staticSize,
_encode(buffer, extrinsic) {
const firstByte = (+!!extrinsic.signature << 7) | extrinsic.protocolVersion;
buffer.array[buffer.index++] = firstByte;
const call = {
type: extrinsic.palletName,
value: {
type: extrinsic.methodName,
...extrinsic.args,
},
};
const { signature } = extrinsic;
if (signature) {
if ("additional" in signature) {
$address._encode(buffer, signature.address);
const toSignBuffer = new $.EncodeBuffer($.tuple($sig, $extra, $additional)._staticSize);
// TODO: swap out with use of `$call._encode` upon fixing https://github.com/paritytech/parity-scale-codec-ts/issues/53
encodeCall(toSignBuffer, callTy, extrinsic, metadata, deriveCodec);
const callEnd = toSignBuffer.finishedSize + toSignBuffer.index;
$extra._encode(toSignBuffer, signature.extra);
const extraEnd = toSignBuffer.finishedSize + toSignBuffer.index;
$additional._encode(toSignBuffer, signature.additional);
const toSignEncoded = toSignBuffer.finish();
const callEncoded = toSignEncoded.subarray(0, callEnd);
const extraEncoded = toSignEncoded.subarray(callEnd, extraEnd);
const toSign = toSignEncoded.length > 256 ? Blake2_256(toSignEncoded) : toSignEncoded;
const sig = sign(toSign);
$sig._encode(buffer, sig);
buffer.insertArray(extraEncoded);
buffer.insertArray(callEncoded);
} else {
$address._encode(buffer, signature.address);
$sig._encode(buffer, signature.sig);
$extra._encode(buffer, signature.extra);
$call._encode(buffer, call);
}
} else {
$call._encode(buffer, call);
}
},
_encode: _Encode(props, $address, $call, $extra, $sig, $additional, callTy),
_decode(buffer) {
const firstByte = buffer.array[buffer.index++]!;
const hasSignature = firstByte & (1 << 7);
Expand Down Expand Up @@ -150,11 +125,73 @@ export function $extrinsic(props: ExtrinsicCodecProps): $.Codec<Extrinsic> {
return $baseExtrinsic.decode(buffer.array.subarray(buffer.index, buffer.index += length));
},
});
}

function getExtrasCodec(is: number[]) {
return $.tuple(...is.map((i) => deriveCodec(i)).filter((x) => x !== $null));
}
function findExtrinsicTypeParam(name: string) {
return metadata.tys[metadata.extrinsic.ty]?.params.find((x) => x.name === name)?.ty;
}
// TODO: clean this up upon introducing async support to SCALE
export function $encodeAsync(props: ExtrinsicCodecProps) {
const { $address, $call, $extra, $sig, callTy, $additional } = setup(props);
const _encode = _Encode(props, $address, $call, $extra, $sig, $additional, callTy);
const baseStaticSize = 1 + $.tuple($address, $sig, $extra, $call)._staticSize;

return async (extrinsic: Extrinsic): Promise<Uint8Array> => {
const baseBuf = new $.EncodeBuffer(baseStaticSize);
await _encode(baseBuf, extrinsic);
const baseEncoded = baseBuf.finish();
const finalBuf = new $.EncodeBuffer($.nCompact._staticSize);
$.nCompact._encode(finalBuf, baseEncoded.length);
finalBuf.insertArray(baseEncoded);
return finalBuf.finish();
};
}

function _Encode(
props: ExtrinsicCodecProps,
$address: $.Codec<unknown>,
$call: $.Codec<unknown>,
$extra: $.Codec<unknown[]>,
$sig: $.Codec<unknown>,
$additional: $.Codec<unknown[]>,
callTy: UnionTyDef,
) {
return async (buffer: $.EncodeBuffer, extrinsic: Extrinsic) => {
const firstByte = (+!!extrinsic.signature << 7) | extrinsic.protocolVersion;
buffer.array[buffer.index++] = firstByte;
const call = {
type: extrinsic.palletName,
value: {
type: extrinsic.methodName,
...extrinsic.args,
},
};
const { signature } = extrinsic;
if (signature) {
if ("additional" in signature) {
$address._encode(buffer, signature.address);
const toSignBuffer = new $.EncodeBuffer($.tuple($sig, $extra, $additional)._staticSize);
// TODO: swap out with use of `$call._encode` upon fixing https://github.com/paritytech/parity-scale-codec-ts/issues/53
encodeCall(toSignBuffer, callTy, extrinsic, props.metadata, props.deriveCodec);
const callEnd = toSignBuffer.finishedSize + toSignBuffer.index;
$extra._encode(toSignBuffer, signature.extra);
const extraEnd = toSignBuffer.finishedSize + toSignBuffer.index;
$additional._encode(toSignBuffer, signature.additional);
const toSignEncoded = toSignBuffer.finish();
const callEncoded = toSignEncoded.subarray(0, callEnd);
const extraEncoded = toSignEncoded.subarray(callEnd, extraEnd);
const toSign = toSignEncoded.length > 256
? props.hashers.Blake2_256(toSignEncoded)
: toSignEncoded;
const sig = await props.sign(toSign);
$sig._encode(buffer, sig);
buffer.insertArray(extraEncoded);
buffer.insertArray(callEncoded);
} else {
$address._encode(buffer, signature.address);
$sig._encode(buffer, signature.sig);
$extra._encode(buffer, signature.extra);
$call._encode(buffer, call);
}
} else {
$call._encode(buffer, call);
}
};
}

0 comments on commit ceb0736

Please sign in to comment.