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

Fails to create Call from OpaqueCall argument for asMulti #2753

Closed
emostov opened this issue Oct 21, 2020 · 4 comments
Closed

Fails to create Call from OpaqueCall argument for asMulti #2753

emostov opened this issue Oct 21, 2020 · 4 comments
Labels
Support Tracks issues or requests related to troubleshooting, answering questions, and user assistance.

Comments

@emostov
Copy link
Contributor

emostov commented Oct 21, 2020

In the extrinsic asMulti, one of the parameters is a scale encoded call, which has a type alias of OpaqueCall. In Sidecar we decode the call to improve the readability. Recently though I found there are some successful asMulti call arguments on Polkadot that polkadot-js cannot decode (failing with a createType error).

Below is some code that shows both successful decoding and non-successful. The main commonality I found is that the ones that do not decode are utility.batch Calls, but I am not sure if this is just a coincidence:

import { ApiPromise, WsProvider } from '@polkadot/api';
import { Struct } from '@polkadot/types';
import { Block } from '@polkadot/types/interfaces';

async function main() {
	const provider = new WsProvider('wss://rpc.polkadot.io');
	const api = new ApiPromise({ provider });

	const hashBlock2106024 = await api.rpc.chain.getBlockHash(2106024);
	const { block: block2106024 } = await api.rpc.chain.getBlock(
		hashBlock2106024
	);
	// https://polkascan.io/polkadot/transaction/0x2127f7c1d119aed55d32893442d80abdb055b39966e5ee2a25b28a089b982b59
	console.log('Block 2106024:');
	// Works (balances.transfer_keep_alive)
	decodeOpaqueCall(block2106024, api);

	const hashBlock2104065 = await api.rpc.chain.getBlockHash(2104065);
	const { block: block2104065 } = await api.rpc.chain.getBlock(
		hashBlock2104065
	);
	// https://polkascan.io/polkadot/transaction/0x93b3013da76ddd94a54fb2fc53ec99e0d6e92c702e02edc80cb7355dad6c2498
	console.log('Block 2104065');
	// Works (proxy.proxy)
	decodeOpaqueCall(block2104065, api);

	const hashBlock871651 = await api.rpc.chain.getBlockHash(871651);
	const { block: block871651 } = await api.rpc.chain.getBlock(
		hashBlock871651
	);
	// https://polkascan.io/polkadot/transaction/0xf5f743643c3d2f88d976087beb630ec1794697aef684e70d389e6dd5d2f6adc8
	console.log('Block 871651:');
	// Does not work (utility.batch)
	decodeOpaqueCall(block871651, api);

	const hashBlock860023 = await api.rpc.chain.getBlockHash(860023);
	const { block: block860023 } = await api.rpc.chain.getBlock(
		hashBlock860023
	);
	// https://polkascan.io/polkadot/transaction/0x9fd043a8af67e3d01429d91ea2c1d882766bdfcca9fdd0fa598a8d7a19ffbf8d
	console.log('Block 860023:');
	// Does not work (utility.batch)
	decodeOpaqueCall(block860023, api);

	const hashBlock860406 = await api.rpc.chain.getBlockHash(860406);
	const { block: block860406 } = await api.rpc.chain.getBlock(
		hashBlock860406
	);
	// https://polkascan.io/polkadot/transaction/0x43775ad4d76364112ae992376a1069f97bcdebc253c5994efc46bee18dbadc20
	console.log('Block 860406:');
	// Does not work (utility.batch)
	decodeOpaqueCall(block860406, api);

	process.exit();
}

function decodeOpaqueCall(block: Block, api: ApiPromise): void {
	for (const ext of block.extrinsics) {
		// Find the asMulti transaction
		if (ext.method.methodName !== 'asMulti') continue;
		const callArgs = ext.method.get('args') as Struct;
		for (const paramName of callArgs.defKeys) {
			// Get the 'call' argument, which is an opqaque call
			if (paramName !== 'call') continue;
			const opaqueCall = callArgs.get(paramName);
			try {
				// Try and create a polkadot-js call from the encoded call
				const decoded = api.createType('Call', opaqueCall);
				console.log(decoded.toHuman());
			} catch {
				// If createType fails log a message
				console.log('Failed to create call from OpaqueCall');
			}
		}
	}

	console.log('\n');
}

main().catch(console.log);

Which results in the following to the terminal:

Block 2106024:
{
  args: [
    '16hwghdH4aofCB2zrUApHnzWsNuZa4WFXdyrVdkoRELtjouT',
    '10.0000 mDOT'
  ],
  method: 'transferKeepAlive',
  section: 'balances'
}


Block 2104065
{
  args: [
    '14jtNyurHjGCPkvMGnFA4npijraG3qGGtVYSA4vBcZkbU6kP',
    null,
    { args: [Array], method: 'vote', section: 'electionsPhragmen' }
  ],
  method: 'proxy',
  section: 'proxy'
}


Block 871651:
Failed to create call from OpaqueCall


Block 860023:
Failed to create call from OpaqueCall


Block 860406:
Failed to create call from OpaqueCall

For an example of the exact error that polkadot-js gives, for block 871651 the error is:

Error: createType(Call):: Struct: failed on 'args':: Struct: failed on 'args':: findMetaCall: Unable to find Call with index 0x1b03/[27,3]

Not sure how to trouble shoot without trying to account for every byte in the encoded Call.

@jacogr
Copy link
Member

jacogr commented Oct 21, 2020

So your issue is you are trying to create types using the API registry which will point to the current metadata. Extrinsics move around, modules move around, methods are added/removed between upgrade.

So each type has a registry attached based on the types and metadata at that point. So use the registry used for the block to create any new types with metadata at that point in time.

TL;DR Do this at the appropriate place -

// any type registry, could be opaqueCall as well
// (but using block since it is passed from the parent)
const decoded = block.registry.createType('Call', opaqueCall);
console.log(JSON.stringify(decoded.toHuman(), null, 2));

@jacogr jacogr added the Support Tracks issues or requests related to troubleshooting, answering questions, and user assistance. label Oct 21, 2020
@emostov
Copy link
Contributor Author

emostov commented Oct 21, 2020

Great, thank you! This works.

To clarify, when you say the API registry points to the current metadata, is it the newest metadata for the chain? I was under the impression it would point to the metadata at block height the most recent query was made at, hence why I was trying to use the API registry to decode.

@jacogr
Copy link
Member

jacogr commented Oct 21, 2020

Always newest, independent of queries made. Any types created during an .at query will have a registry getter for a registry appropriate for that point in time. (i.e. all types has the registry used in their creation, normally won't make a difference, but Call needs to be decoded)

@polkadot-js-bot
Copy link

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue if you think you have a related problem or query.

@polkadot-js polkadot-js locked as resolved and limited conversation to collaborators Jun 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Support Tracks issues or requests related to troubleshooting, answering questions, and user assistance.
Projects
None yet
Development

No branches or pull requests

3 participants