3

enter image description here

How to use the scale decoder to parse extrinsics?

enter image description here

I tried to get the timestamp of this block in this way, but the result was not correct.

Which friend has a specific example of analysis for me? thank you

If you just tell me to use the scale parser or JS method. It's no use to me. Please don't answer.

I want detailed examples!!!!

1
  • Do you solve the problem? I need also decoder to parse extrinsics ,can you help me ?
    – Rose
    Commented Mar 16, 2023 at 12:17

3 Answers 3

7

The general format for extrinsics are -

  • length of extrinsic as a Compact<u32>
  • version byte + signed flag
  • sender address (only included if signed, as per flag above)
  • sender signature (only included if signed, as per flag above)
  • additional signed extension values as per metadata (only included if signed, as per flag above)
  • pallet index for call (as per metadata)
  • method index for call (as per metadata)
  • call data (as per metadata types)

Decoding the 0x280402000b919bbc3b8001 extrinsic manually, i.e. with a manual lookup against what we know the metadata to be -

  • 28 first byte, as per the format above it is the compact length. Since lower 2 bits are 0b00, we know the data here is contained in a single byte. So in this case 40 >> 2 (decimal, shifting as per rules for 0b00) yields 10 bytes in length for the extrinsic (this excludes the length prefix, only the data bytes are counted)
  • 04 is next, containing the version byte + signed flag. The top-most bit is unset, this means the transaction is unsigned. In this case 04 determines the extrinsic version

Since we now determined it is unsigned with the version byte, here we skip the decoding of address, signature & signed extensions, and jump straight into the call part -

  • 02 the pallet index (on Kusama this is timestamp, it will be different for other chains and could possibly change between upgrades)
  • 00 the call index inside the pallet (on Kusama this points to timestamp.set for the current metadata, can also change when upgraded)

The variable call data therefore is the remaining 0b919bbc3b8001. As per the metadata for this call on the current Kusama runtime, this is a single Compact<u64> representing now. Looking at the first byte, 0b (hex), this is 0b0000_1011 in binary, the lower two bits are both set, which means this byte contains the length of the data to follow. (00, 01 and 10 has different rules)

Converting this, we know 0b (hex) is 11 in decimal, where the length of the data would therefore be (11 >> 2) + 4 bytes following for this type of flag, i.e. a calculated 6 bytes data should follow this indicator, all containing the data.

So this is a simple form of compact encoding, no additional shifting is needed and we can simply convert the remaining 6 bytes into LE u64. The next 6 bytes 919bbc3b8001 (all the remaining data) is [145, 155, 188, 59, 128, 1] in base-10 array form. Converting this to LE (once again manually, sticking with the approach followed thus far) -

(((((((((1 * 256) + 128) * 256) + 59) * 256) + 188) * 256) + 155) * 256) + 145 = 1650269658001

Converting to to unix epoch time this represents Mon Apr 18 2022 08:14:18 GMT+0000

4
  • Thank you, but I didn't quite understand what you said. I have to study it. But is your calculation is wrong? Should it be 1651825968 2022-05-06 16:32:48?
    – Andy
    Commented May 18, 2022 at 13:29
  • The value decoded as 1650269658001 above is correct, see it here decoded. That value indeed converts to Mon Apr 18 2022 08:14:18 GMT+0000 using epochconvert.com as well as epochtimestamp.com (GMT times given since it is universal)
    – Jaco
    Commented May 18, 2022 at 14:04
  • ... Also adding the Westend block apps UI and on subscan where the timestamps (match, taking UTC into account), here is also the decoding apps UI link and on subscan
    – Jaco
    Commented May 18, 2022 at 14:28
  • Still wrong, the time of this block should be May 6
    – Andy
    Commented May 23, 2022 at 14:57
3

Thank you @Jaco!!! It was entertaining following along with the description! Retrieved both the block you linked and a new one and checked it against Subscan based on the answer.

// https://westend.subscan.io/block/0x3a0ab3cfddfed654683749546ddc7c4d71e73ee46b32f7575aca314f164d32b8
// https://westend.subscan.io/extrinsic/10463935-0
// ms=1650269658001 timestamp=2022-04-18T08:14:18.001Z
decode('0x280402000b919bbc3b8001')

// https://westend.subscan.io/block/0xdbde06feb935a5b1734e5d8af455511bfb577cd9c790b92aada6d0518a68a7e1
// https://westend.subscan.io/extrinsic/10978912-0
// ms=1653374694003 timestamp=2022-05-24T06:44:54.003Z
decode('0x280402000b73b2cff48001')

Probably not the best approach ever, but coded up as described.

function read_compact (bin) {
    // retrieve the compact flag to determine logic
    const flag = bin[0] & 0b00000011

    if (flag === 0b00000000) {
        // this is only a single byte
        return [bin[0] >>> 2, 1]
    } else if (flag === 0b00000011) {
        // the number of bytes we need to use
        const index = (bin[0] >>> 2) + 4
        let result = 0

        // this is LE, so we start from the back
        for (let i = index; i > 0; i--) {
            result = (result * 256) + bin[i]
        }

        return [result, index + 1]
    }

    // TODO we need to handle 0b00000001 & 0x00000010
    throw new Error("FATAL: Unhandled compact flag " + flag)
}

function read_version (bin) {
    // one byte only, the signed flag is the top-most bit
    return [(bin[0] & 0b1000000) === 0b1000000, bin[0] & 0b01111111];
}

function read_tx_index (bin) {
    // the first byte is the moodule index, the second byte is the
    // method index inside the module
    return [bin[0], bin[1]];
}

function decode (hex) {
    // ppor man's version of hex to bytes
    let bin = hex.split(/(?=(?:..)*$)/).slice(1).map((x) => parseInt(x, 16))

    // read the overall length of the extrinsic
    const [ex_length, offset] = read_compact(bin)

    console.log("extrinsic length=" + ex_length)

    // we are done with these bytes, next
    bin = bin.slice(offset)

    // read the version and signed flag
    const [signed, version] = read_version(bin)

    console.log("version=" + version + " signed=" + signed)

    // we only handle decoding of unsigned extrinsics
    if (signed) {
        throw new Error("FATAL: Currently only dealing with unsigned transactions")
    }

    // we are done with the version, next
    bin = bin.slice(1)

    // read the transaction index
    const [mod_idx, met_idx] = read_tx_index(bin)

    console.log("Module=" + mod_idx + " method=" + met_idx)

    // yes, I know these should come from metadata
    if (mod_idx !== 2 || met_idx !== 0) {
        throw new Error("FATAL: We can only deal with timestamp.set")
    }

    // we are done with the method indexes, next
    bin = bin.slice(2)

    // read the timestamp compact
    const [ts_ms] = read_compact(bin)

    console.log("ms=" + ts_ms + " timestamp=" + new Date(ts_ms).toISOString())
}

// https://westend.subscan.io/block/0x3a0ab3cfddfed654683749546ddc7c4d71e73ee46b32f7575aca314f164d32b8
// https://westend.subscan.io/extrinsic/10463935-0
// ms=1650269658001 timestamp=2022-04-18T08:14:18.001Z
decode('0x280402000b919bbc3b8001')

// https://westend.subscan.io/block/0xdbde06feb935a5b1734e5d8af455511bfb577cd9c790b92aada6d0518a68a7e1
// https://westend.subscan.io/extrinsic/10978912-0
// ms=1653374694003 timestamp=2022-05-24T06:44:54.003Z
decode('0x280402000b73b2cff48001')

Will have to post another question around the other 2 flags in compact as of now I'm sure there will be compacts the above cannot read as of yet. This is purely for entertainment purposes, at least I learned a bit of what makes up the transactions here

1
  • Thank you for writing so much code. But it would be nice to have a java version. I can't understand this JS.
    – Andy
    Commented May 26, 2022 at 12:10
0

The input your looking at is hexadecimal so as well as taking the 0x off the front you need to convert it to binary. Something like this would do the trick.

Once you've got it as binary you need to decode it from the SCALE encoding. You could use something like this: https://github.com/usetech-llc/polkadot_api_dotnet/blob/2b0021f8403525358d1f4721c9a64f838a74cd90/Polkadot/src/Utils/Scale.cs

but that project looks a tad out of date. I would probably compile the rust libs to a dll and then call them from c# using the c ffi pinvoke.

5
  • But that's all hard mode stuff. Desub might help you as strong typing probably isn't your friend here. Maybe the easier route might be to call sidecar rest urls for the data you need - that gives json back: paritytech.github.io/substrate-api-sidecar/dist
    – Squirrel
    Commented May 18, 2022 at 8:40
  • public static void main(String[] args) { //TODO // Correct result: 1651825968 UInt32Reader reader = new UInt32Reader(); String str = "0x280402000b919bbc3b8001"; String replace = str.replace("0x", ""); BigInteger ten = new BigInteger(replace, 16); String two = ten.toString(2); ScaleCodecReader codec = new ScaleCodecReader(two.getBytes()); codec.hasNext(); Long read = codec.read(reader); System.out.println("timestamp:"+read); }
    – Andy
    Commented May 18, 2022 at 9:25
  • The result is still wrong. The correct result should be 1651825968. What went wrong?
    – Andy
    Commented May 18, 2022 at 9:26
  • replace needs to be turned into bin from the hex string encoding that it is whereas you're trying to read it as a big integer. After the 0x there's 28 - that's 2x16 + 8 which is the byte 40 (a u8 in rust speak). You need to call Convert.FromHexString(replace) to get the scale binary and then you can decode that.
    – Squirrel
    Commented May 18, 2022 at 10:06
  • ( String two = ten.toString(2); ) The result is binary
    – Andy
    Commented May 18, 2022 at 13:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.