I'm trying to generate an extrinsic offline based on this example. It worked, but the signed transaction is different.
Call Data
As you can see in the output the Call Hash is the same.
Signed Transaction
My code
use serde::{Serialize, Deserialize};
use sp_keyring::AccountKeyring;
use sp_runtime::{MultiAddress, MultiSignature, generic::Era};
use parity_scale_codec::{ Compact, Encode };
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct SystemSyncState {
current_block: u32,
starting_block: u32,
}
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
struct SystemSyncStateSuccess {
jsonrpc: String,
result: SystemSyncState,
id: u8,
}
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
struct SystemAccountNextIndexSuccess {
jsonrpc: String,
result: u32,
id: u8,
}
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
struct ChainGetBlockHashSuccess {
jsonrpc: String,
result: String,
id: u8,
}
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum StateGetRuntimeVersionApis {
Int(u32),
Str(String),
}
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct StateGetRuntimeVersion {
apis: Vec<[StateGetRuntimeVersionApis;2]>,
authoring_version: u32,
impl_name: String,
impl_version: u32,
spec_name: String,
spec_version: u32,
transaction_version: u32,
}
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
struct StateGetRuntimeVersionSuccess {
jsonrpc: String,
result: StateGetRuntimeVersion,
id: u8,
}
#[derive(Serialize)]
struct NodeRequest {
id: u8,
jsonrpc: String,
method: String,
params: Vec<String>,
}
pub struct StorageKeyPrefix(Vec<u8>);
async fn get_nonce(account: &sp_runtime::AccountId32) -> u32 {
let client = reqwest::Client::new();
let req = NodeRequest {
id: 1,
jsonrpc: "2.0".to_string(),
method: "system_accountNextIndex".to_string(),
params: vec![account.to_string()],
};
let res = client
.post("http://localhost:9933")
.json(&serde_json::json!(req))
.send()
.await;
match res {
Ok(res) => {
match res.status() {
reqwest::StatusCode::OK => {
match res.json::<SystemAccountNextIndexSuccess>().await {
Ok(parsed) => {
// println!("NONCE {:#?}", parsed.result);
parsed.result
},
Err(e) => {
println!("ERR {:?}", e);
0
},
}
},
_ => {
0
}
}
},
Err(_e) => {
0
}
}
}
async fn get_genesis_hash() -> [u8; 32] {
let client = reqwest::Client::new();
let req = NodeRequest {
id: 1,
jsonrpc: "2.0".to_string(),
method: "chain_getBlockHash".to_string(),
params: vec!["0".to_string()]
};
let res = client
.post("http://localhost:9933")
.json(&serde_json::json!(req))
.send()
.await;
match res {
Ok(res) => {
match res.status() {
reqwest::StatusCode::OK => {
match res.json::<ChainGetBlockHashSuccess>().await {
Ok(parsed) => {
println!("GENESIS HASH {:#?}", parsed.result);
sp_core::hashing::sha2_256(parsed.result.as_bytes())
},
Err(e) => {
println!("ERR {:?}", e);
sp_core::hashing::sha2_256(&[0])
},
}
},
_ => {
sp_core::hashing::sha2_256(&[0])
}
}
},
Err(_e) => {
sp_core::hashing::sha2_256(&[0])
}
}
}
/// Fetch runtime information from the node.
async fn get_runtime_version() -> Result<StateGetRuntimeVersion, Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let req = NodeRequest {
id: 1,
jsonrpc: "2.0".to_string(),
method: "state_getRuntimeVersion".to_string(),
params: vec![]
};
let res = client
.post("http://localhost:9933")
.json(&serde_json::json!(req))
.send()
.await;
match res {
Ok(res) => {
match res.status() {
reqwest::StatusCode::OK => {
match res.json::<StateGetRuntimeVersionSuccess>().await {
Ok(parsed) => {
Ok(parsed.result)
},
Err(e) => Err(String::from(e.to_string()).into())
}
},
_ => {
Err(String::from("Something went wrong").into())
}
}
},
Err(e) => Err(String::from(e.to_string()).into())
}
}
fn encode_extrinsic<S: Encode ,C: Encode>(signature: Option<S>, call: C) -> Vec<u8> {
let mut tmp: Vec<u8> = vec![];
const EXTRINSIC_VERSION: u8 = 4;
match signature.as_ref() {
Some(s) => {
tmp.push(EXTRINSIC_VERSION | 0b1000_0000);
s.encode_to(&mut tmp);
},
None => {
tmp.push(EXTRINSIC_VERSION & 0b0111_1111);
},
}
call.encode_to(&mut tmp);
let compact_len = Compact(tmp.len() as u32);
let mut output: Vec<u8> = vec![];
compact_len.encode_to(&mut output);
output.extend(tmp);
output
}
#[async_std::main]
async fn main() {
let storage_key = "4d4c14c40d1b7ecb942455794693fa68";
let pallet_index = 8;
let method_index = 0;
let call_index: [u8; 2] = [pallet_index, method_index];
let address: Vec<u8> = storage_key.as_bytes().to_vec().encode();
let owner = [0];
let call_data = [call_index.to_vec(), address, owner.to_vec()].concat();
let call_hex = hex::encode(call_data.clone());
println!("CALL HEX {:?}", call_hex);
let hash = sp_core::blake2_256(call_data.as_ref());
println!("CALL HASH {:?}", hex::encode(hash));
let from = AccountKeyring::Alice.to_account_id();
let alice_nonce = get_nonce(&from).await;
let runtime_version = get_runtime_version().await.unwrap();
let genesis_hash = get_genesis_hash().await;
let extra = (
Era::Immortal,
Compact(alice_nonce),
Compact(0u128)
);
let additional = (
runtime_version.spec_version,
runtime_version.transaction_version,
genesis_hash,
genesis_hash,
);
let signature = {
let full_unsigned_payload = (&call_data, &extra, &additional);
let full_unsigned_payload_scale_bytes = full_unsigned_payload.encode();
if call_data.len() > 256 {
AccountKeyring::Alice.sign(&sp_core::blake2_256(&full_unsigned_payload_scale_bytes)[..])
} else {
AccountKeyring::Alice.sign(&full_unsigned_payload_scale_bytes)
}
};
let signature_to_encode = Some((
MultiAddress::Id::<_,u32>(from),
MultiSignature::Sr25519(signature),
extra
));
let payload_scale_encoded = encode_extrinsic(
signature_to_encode,
call_data
);
let payload_hex = format!("0x{}", hex::encode(&payload_scale_encoded));
println!("PAYLOAD HEX {:?}", payload_hex);
println!("TARGET BYTES {:?}", hex::decode("29028400d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01264459d771430ea6f806df70c10f91c783cb31ee8007751a4906fa92ddf70313431871e435f7fbddfb3974ece95c82f3d59cb8abe98d78f47cc150ae99c1b089001400080080346434633134633430643162376563623934323435353739343639336661363800").unwrap());
println!("RESULT BYTES {:?}", payload_scale_encoded);
}
The output
CALL HEX "080080346434633134633430643162376563623934323435353739343639336661363800"
CALL HASH "9f7038a86de92cd63b1d3edce42730ffeca300c6e590e1a457e675add24ff706"
GENESIS HASH "0xeba2ea8340d05a42a50ddd5c8cbf5ac91de372cff7c4785bcab4ebbdf388584f"
PAYLOAD HEX "0x2d028400d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01268cb1fc4beb651a7ca1b0944fe22070e702bfb9ba455391a25f5fbefc2cd22b4b4defc1df9c8b946f9be936a0b823f339fd5eed23755dfd4cca467b595be38900140090080080346434633134633430643162376563623934323435353739343639336661363800"
TARGET BYTES [41, 2, 132, 0, 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, 1, 38, 68, 89, 215, 113, 67, 14, 166, 248, 6, 223, 112, 193, 15, 145, 199, 131, 203, 49, 238, 128, 7, 117, 26, 73, 6, 250, 146, 221, 247, 3, 19, 67, 24, 113, 228, 53, 247, 251, 221, 251, 57, 116, 236, 233, 92, 130, 243, 213, 156, 184, 171, 233, 141, 120, 244, 124, 193, 80, 174, 153, 193, 176, 137, 0, 20, 0, 8, 0, 128, 52, 100, 52, 99, 49, 52, 99, 52, 48, 100, 49, 98, 55, 101, 99, 98, 57, 52, 50, 52, 53, 53, 55, 57, 52, 54, 57, 51, 102, 97, 54, 56, 0]
RESULT BYTES [45, 2, 132, 0, 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, 1, 38, 140, 177, 252, 75, 235, 101, 26, 124, 161, 176, 148, 79, 226, 32, 112, 231, 2, 191, 185, 186, 69, 83, 145, 162, 95, 95, 190, 252, 44, 210, 43, 75, 77, 239, 193, 223, 156, 139, 148, 111, 155, 233, 54, 160, 184, 35, 243, 57, 253, 94, 237, 35, 117, 93, 253, 76, 202, 70, 123, 89, 91, 227, 137, 0, 20, 0, 144, 8, 0, 128, 52, 100, 52, 99, 49, 52, 99, 52, 48, 100, 49, 98, 55, 101, 99, 98, 57, 52, 50, 52, 53, 53, 55, 57, 52, 54, 57, 51, 102, 97, 54, 56, 0]
The "Result Bytes" is the byte array that I generate. The "Target Bytes" array is generated by the Polkadot web client. Call Hex and Call Hash are correct and match the ones from the Polkadot web client.
Query result
If I try to execute the result Payload Hex I get an error:
Bytes difference
I've highlighted the bytes that differ:
TARGET BYTES [41
, 2, 132, 0, 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, 1, 38, 68, 89, 215, 113, 67, 14, 166, 248, 6, 223, 112, 193, 15, 145, 199, 131, 203, 49, 238, 128, 7, 117, 26, 73, 6, 250, 146, 221, 247, 3, 19, 67, 24, 113, 228, 53, 247, 251, 221, 251, 57, 116, 236, 233, 92, 130, 243, 213, 156, 184, 171, 233, 141, 120, 244, 124, 193, 80, 174, 153, 193, 176, 137, 0, 20, 0
, 8, 0, 128, 52, 100, 52, 99, 49, 52, 99, 52, 48, 100, 49, 98, 55, 101, 99, 98, 57, 52, 50, 52, 53, 53, 55, 57, 52, 54, 57, 51, 102, 97, 54, 56, 0]
RESULT BYTES [45
, 2, 132, 0, 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, 1, 38, 140, 177, 252, 75, 235, 101, 26, 124, 161, 176, 148, 79, 226, 32, 112, 231, 2, 191, 185, 186, 69, 83, 145, 162, 95, 95, 190, 252, 44, 210, 43, 75, 77, 239, 193, 223, 156, 139, 148, 111, 155, 233, 54, 160, 184, 35, 243, 57, 253, 94, 237, 35, 117, 93, 253, 76, 202, 70, 123, 89, 91, 227, 137, 0, 20, 0, 144
, 8, 0, 128, 52, 100, 52, 99, 49, 52, 99, 52, 48, 100, 49, 98, 55, 101, 99, 98, 57, 52, 50, 52, 53, 53, 55, 57, 52, 54, 57, 51, 102, 97, 54, 56, 0]
Would appreciate any help on finding the reasons of the difference in results. Thanks!
Update
This is the last version of the code
use serde::{Serialize, Deserialize};
use sp_keyring::AccountKeyring;
use sp_runtime::{MultiAddress, MultiSignature, generic::Era};
use parity_scale_codec::{ Compact, Encode };
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct SystemSyncState {
current_block: u32,
starting_block: u32,
}
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
struct SystemSyncStateSuccess {
jsonrpc: String,
result: SystemSyncState,
id: u8,
}
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
struct SystemAccountNextIndexSuccess {
jsonrpc: String,
result: u32,
id: u8,
}
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
struct ChainGetBlockHashSuccess {
jsonrpc: String,
result: String,
id: u8,
}
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum StateGetRuntimeVersionApis {
Int(u32),
Str(String),
}
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct StateGetRuntimeVersion {
apis: Vec<[StateGetRuntimeVersionApis;2]>,
authoring_version: u32,
impl_name: String,
impl_version: u32,
spec_name: String,
spec_version: u32,
transaction_version: u32,
}
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
struct StateGetRuntimeVersionSuccess {
jsonrpc: String,
result: StateGetRuntimeVersion,
id: u8,
}
#[derive(Serialize)]
struct NodeRequest {
id: u8,
jsonrpc: String,
method: String,
params: Vec<String>,
}
pub struct StorageKeyPrefix(Vec<u8>);
async fn get_nonce(account: &sp_runtime::AccountId32) -> u32 {
let client = reqwest::Client::new();
let req = NodeRequest {
id: 1,
jsonrpc: "2.0".to_string(),
method: "system_accountNextIndex".to_string(),
params: vec![account.to_string()],
};
let res = client
.post("http://localhost:9933")
.json(&serde_json::json!(req))
.send()
.await;
match res {
Ok(res) => {
match res.status() {
reqwest::StatusCode::OK => {
match res.json::<SystemAccountNextIndexSuccess>().await {
Ok(parsed) => {
// println!("NONCE {:#?}", parsed.result);
parsed.result
},
Err(e) => {
println!("ERR {:?}", e);
0
},
}
},
_ => {
0
}
}
},
Err(_e) => {
0
}
}
}
async fn get_genesis_hash() -> [u8; 32] {
let client = reqwest::Client::new();
let req = NodeRequest {
id: 1,
jsonrpc: "2.0".to_string(),
method: "chain_getBlockHash".to_string(),
params: vec!["0".to_string()]
};
let res = client
.post("http://localhost:9933")
.json(&serde_json::json!(req))
.send()
.await;
match res {
Ok(res) => {
match res.status() {
reqwest::StatusCode::OK => {
match res.json::<ChainGetBlockHashSuccess>().await {
Ok(parsed) => {
println!("GENESIS HASH {:#?}", parsed.result);
sp_core::hashing::sha2_256(parsed.result.as_bytes())
},
Err(e) => {
println!("ERR {:?}", e);
sp_core::hashing::sha2_256(&[0])
},
}
},
_ => {
sp_core::hashing::sha2_256(&[0])
}
}
},
Err(_e) => {
sp_core::hashing::sha2_256(&[0])
}
}
}
async fn get_runtime_version() -> Result<StateGetRuntimeVersion, Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let req = NodeRequest {
id: 1,
jsonrpc: "2.0".to_string(),
method: "state_getRuntimeVersion".to_string(),
params: vec![]
};
let res = client
.post("http://localhost:9933")
.json(&serde_json::json!(req))
.send()
.await;
match res {
Ok(res) => {
match res.status() {
reqwest::StatusCode::OK => {
match res.json::<StateGetRuntimeVersionSuccess>().await {
Ok(parsed) => {
Ok(parsed.result)
},
Err(e) => Err(String::from(e.to_string()).into())
}
},
_ => {
Err(String::from("Something went wrong").into())
}
}
},
Err(e) => Err(String::from(e.to_string()).into())
}
}
fn encode_extrinsic<S: Encode ,C: Encode>(signature: Option<S>, call: C) -> Vec<u8> {
let mut tmp: Vec<u8> = vec![];
const EXTRINSIC_VERSION: u8 = 4;
match signature.as_ref() {
Some(s) => {
tmp.push(EXTRINSIC_VERSION | 0b1000_0000);
s.encode_to(&mut tmp);
},
None => {
tmp.push(EXTRINSIC_VERSION & 0b0111_1111);
},
}
call.encode_to(&mut tmp);
let compact_len = Compact(tmp.len() as u32);
let mut output: Vec<u8> = vec![];
compact_len.encode_to(&mut output);
output.extend(tmp);
output
}
#[async_std::main]
async fn main() {
let storage_key = "4d4c14c40d1b7ecb942455794693fa68";
let pallet_index: u8 = 8;
let method_index: u8 = 0;
let address = storage_key.as_bytes().to_owned();
let owner: [u8; 1] = [0];
let call_data = (
pallet_index,
method_index,
address,
owner
);
let from = AccountKeyring::Alice.to_account_id();
let alice_nonce = get_nonce(&from).await;
let runtime_version = get_runtime_version().await.unwrap();
let genesis_hash = get_genesis_hash().await;
let extra = (
Era::Immortal,
Compact(alice_nonce),
Compact(0u128)
);
let additional = (
runtime_version.spec_version,
runtime_version.transaction_version,
genesis_hash,
genesis_hash,
);
let signature = {
let full_unsigned_payload = (&call_data, &extra, &additional);
let full_unsigned_payload_scale_bytes = full_unsigned_payload.encode();
if full_unsigned_payload_scale_bytes.len() > 256 {
AccountKeyring::Alice.sign(&sp_core::blake2_256(&full_unsigned_payload_scale_bytes)[..])
} else {
AccountKeyring::Alice.sign(&full_unsigned_payload_scale_bytes)
}
};
let signature_to_encode = Some((
MultiAddress::Id::<_,u32>(from),
MultiSignature::Sr25519(signature),
extra
));
let payload_scale_encoded = encode_extrinsic(
signature_to_encode,
call_data
);
let payload_hex = format!("0x{}", hex::encode(&payload_scale_encoded));
println!("TARGET HEX {:?}", "0x29028400d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d014c6d14a111568aa33472f6061d31763a0acc15fe32a53f5cd7c0e411bcf7a272636de5498a328c34087140f329780afd9aac3e6ed94213e9215e979771173c87001400080080346434633134633430643162376563623934323435353739343639336661363800");
println!("RESULT HEX {:?}", payload_hex);
If I understand correctly the only problem right now is the incorrect signature.
Bytes difference
TARGET BYTES [41, 2, 132, 0, 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, 1, 76, 109, 20, 161, 17, 86, 138, 163, 52, 114, 246, 6, 29, 49, 118, 58, 10, 204, 21, 254, 50, 165, 63, 92, 215, 192, 228, 17, 188, 247, 162, 114, 99, 109, 229, 73, 138, 50, 140, 52, 8, 113, 64, 243, 41, 120, 10, 253, 154, 172, 62, 110, 217, 66, 19, 233, 33, 94, 151, 151, 113, 23, 60, 135
, 0, 20, 0, 8, 0, 128, 52, 100, 52, 99, 49, 52, 99, 52, 48, 100, 49, 98, 55, 101, 99, 98, 57, 52, 50, 52, 53, 53, 55, 57, 52, 54, 57, 51, 102, 97, 54, 56, 0]
RESULT BYTES [41, 2, 132, 0, 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, 1, 154, 207, 39, 54, 83, 45, 188, 252, 156, 12, 35, 43, 195, 150, 79, 241, 36, 120, 168, 73, 139, 81, 80, 155, 228, 224, 9, 41, 214, 157, 87, 38, 237, 169, 110, 227, 254, 140, 230, 192, 147, 148, 160, 32, 252, 169, 237, 183, 74, 105, 32, 63, 77, 36, 200, 207, 76, 121, 217, 250, 176, 135, 97, 134
, 0, 20, 0, 8, 0, 128, 52, 100, 52, 99, 49, 52, 99, 52, 48, 100, 49, 98, 55, 101, 99, 98, 57, 52, 50, 52, 53, 53, 55, 57, 52, 54, 57, 51, 102, 97, 54, 56, 0]