Solana Raw Tx Breakdown
Table of Contents
1. Solana 交易
在 Solana 区块链中,通过 RPC sendTransaction 可以把签名后的 Tx 提交到链上。提交 Tx 时,可以使用 base58(已经不推荐)或者 base64 编码的 Tx。
2. Tx 解析实例 1(一对一转账)
下面以 Tx tg7QzizXN6LpK2pEtzeKHG5DmUkgz7DiEsAmtPxvaYxEUEYNV2qKF6gaSEG4Qm5uZ7DeTT5F1CTmsEABN1DWnU7 为例介绍一下这个 Tx 的细节。
这个 Solana Devnet 上的 Tx 是通过 RPC sendTransaction 提交上链的,具体参数如下:
$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' { "jsonrpc": "2.0", "id": 1, "method": "sendTransaction", "params": [ "ASyP3WwO1k00+UjmLS0RQ6I4A3SLmGagC49rPD8NqXXRezFZ9HH0QM5WYy0GRmzdmfM3Zr2r/kfY9Yzdj18D2AABAAEDjGW0vs4E7Y0JrlRS/FbfremUacCBVRSJl3WGWvd4P50G2indv8FqFG+2/Ieu9DifmgpE2oztoOfk3Y7YtJFgnQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK4RSMkONmm1NSkWHnNwtxr2vM5D5wXDojP+9vb4UJvIBAgIAAQwCAAAAQEIPAAAAAAA=", { "encoding": "base64" } ] } ' {"jsonrpc":"2.0","result":"tg7QzizXN6LpK2pEtzeKHG5DmUkgz7DiEsAmtPxvaYxEUEYNV2qKF6gaSEG4Qm5uZ7DeTT5F1CTmsEABN1DWnU7","id":1}
这个 Tx 的功能是从地址 AT42Zu5kD6V27rgXdkoTpE4mqvsDxsitzWYDr1iStu3r 转移 0.001 SOL(即 1000000 Lamports) 到地址 TkPgkcDQYDUSDBfwTokwkZpn8spz94u4fvXii9U7CWY 中。
AT42Zu5kD6V27rgXdkoTpE4mqvsDxsitzWYDr1iStu3r ------ 1000000 Lamports ------> TkPgkcDQYDUSDBfwTokwkZpn8spz94u4fvXii9U7CWY
为了便于分析,我们把 base64 编码的 Tx 转换为 Hex String 形式:
012c8fdd6c0ed64d34f948e62d2d1143a23803748b9866a00b8f6b3c3f0da975d17b3159f471f440ce56632d06466cdd99f33766bdabfe47d8f58cdd8f5f03d800010001038c65b4bece04ed8d09ae5452fc56dfade99469c0815514899775865af7783f9d06da29ddbfc16a146fb6fc87aef4389f9a0a44da8ceda0e7e4dd8ed8b491609d00000000000000000000000000000000000000000000000000000000000000002b845232438d9a6d4d4a45879cdc2dc6bdaf3390f9c170e88cffbdbdbe1426f201020200010c0200000040420f0000000000
Solana Tx 的格式如图 1 所示。
Figure 1: Solana Tx(摘自:https://solana.com/docs/intro/dev#transactions )
关于 Tx 的细节可参考:https://solana.com/docs/core/transactions 。按照格式定义,可把上面 Tx 的每个字节含义解释如下:
+------------+--------------------------------------------------------+------------------------------------------------------------------+ | | signatures length | 01 | | signatures +--------------------------------------------------------+------------------------------------------------------------------+ | | signature 0 | 2c8fdd6c0ed64d34f948e62d2d1143a23803748b9866a00b8f6b3c3f0da975d1 | | | | 7b3159f471f440ce56632d06466cdd99f33766bdabfe47d8f58cdd8f5f03d800 | +------------+-------------------+------------------------------------+------------------------------------------------------------------+ | | | num required signatures | 01 | | | +------------------------------------+------------------------------------------------------------------+ | | message header | num readonly signed accounts | 00 | | | +------------------------------------+------------------------------------------------------------------+ | | | num readonly unsigned accounts | 01 | | +-------------------+------------------------------------+------------------------------------------------------------------+ | | | length | 03 | | | +------------------------------------+------------------------------------------------------------------+ | | | address 0 | 8c65b4bece04ed8d09ae5452fc56dfade99469c0815514899775865af7783f9d | | | account addresses +------------------------------------+------------------------------------------------------------------+ | | | address 1 | 06da29ddbfc16a146fb6fc87aef4389f9a0a44da8ceda0e7e4dd8ed8b491609d | | | +------------------------------------+------------------------------------------------------------------+ | | | address 2 | 0000000000000000000000000000000000000000000000000000000000000000 | | +-------------------+------------------------------------+------------------------------------------------------------------+ | | recent blockhash | 2b845232438d9a6d4d4a45879cdc2dc6bdaf3390f9c170e88cffbdbdbe1426f2 | | message +-------------------+------------------------------------+------------------------------------------------------------------+ | | | length | 01 | | | +-------+----------------------------+------------------------------------------------------------------+ | | | | program id index | 02 | | | | +------------------+---------+------------------------------------------------------------------+ | | | | | length | 02 | | | instructions | | account address +---------+------------------------------------------------------------------+ | | | | indexes | index 0 | 00 | | | | | +---------+------------------------------------------------------------------+ | | | ins 0 | | index 1 | 01 | | | | +------------------+---------+------------------------------------------------------------------+ | | | | | length | 0c | | | | | instruction data +---------+------------------------------------------------------------------+ | | | | | data | 0200000040420f0000000000 | +------------+-------------------+-------+------------------+---------+------------------------------------------------------------------+
- 上面的 signatures length 等表示长度的字段,并不是固定 1 字节大小,它可能是 1/2/3 字节大小,参考:https://github.com/solana-labs/solana/blob/a16f982169eb197fad0eb8c58c307fb069f69d8f/sdk/program/src/short_vec.rs
- 在 Solana 中,地址就是 Ed25519 公钥的 base58 编码。上面 Tx 中的 address 0 和 address 1 分别对应的地址就是 AT42Zu5kD6V27rgXdkoTpE4mqvsDxsitzWYDr1iStu3r 和 TkPgkcDQYDUSDBfwTokwkZpn8spz94u4fvXii9U7CWY。
- 上面 Tx 中 recent blockhash 为 2b845232438d9a6d4d4a45879cdc2dc6bdaf3390f9c170e88cffbdbdbe1426f2,其对应的 base58 编码为 3vsZGraLdzja3DUMfLr3NWPGeHcbCawGvCGj7nMn7tFT。
- Instructions 中 program id index 对应的是 account addresses 的元素下标,这个例子中是 02,即地址 0000000000000000000000000000000000000000000000000000000000000000,这个十六进制对应的 base58 地址就是 11111111111111111111111111111111,即 System Program 的地址。 在 Solana 中,System Program 是所有帐户钱包的 Owner,由于只有 Owner(对于帐户钱包来说 Owner 就是 System Program)才能减少帐户的余额,所以 Solana 中原生币转账其实就是调用 System Program 的方法。
- 0200000040420f0000000000 是 SystemInstruction Transfer 1000000 的编码(Solana 的 instruction data 采用 bincode 编码或者 borsh 编码,推导使用 borsh 编码)。其中 02000000 表示 Transfer,而 40420f0000000000 则是 1000000 的小端格式。
2.1. 签名
待签名数据就是图 1 中的 Message 部分(即去掉其中的 Signatures 部分)。它是 Ed25519 签名中 SHA512(R||A||M)
中的 M
。
具体到前面的例子,待签名数据就是:
010001038c65b4bece04ed8d09ae5452fc56dfade99469c0815514899775865af7783f9d06da29ddbfc16a146fb6fc87aef4389f9a0a44da8ceda0e7e4dd8ed8b491609d00000000000000000000000000000000000000000000000000000000000000002b845232438d9a6d4d4a45879cdc2dc6bdaf3390f9c170e88cffbdbdbe1426f201020200010c0200000040420f0000000000
前面交易的功能是从地址 AT42Zu5kD6V27rgXdkoTpE4mqvsDxsitzWYDr1iStu3r 往其它地址转移 0.001 SOL,所以需要 AT42Zu5kD6V27rgXdkoTpE4mqvsDxsitzWYDr1iStu3r 所对应的私钥进行签名。
AT42Zu5kD6V27rgXdkoTpE4mqvsDxsitzWYDr1iStu3r 的 Ed25519 私钥是
aa5e711d133aca45d6ec97116de54a24a421a823385fc42fd342d5ecb17ce7d0
使用这个私钥对前面的数据进行签名后,得到签名数据为:
2c8fdd6c0ed64d34f948e62d2d1143a23803748b9866a00b8f6b3c3f0da975d17b3159f471f440ce56632d06466cdd99f33766bdabfe47d8f58cdd8f5f03d800
标准的 Ed25519 签名是确定性,重复运行签名都会得到相同的签名结果。
上面的 Hex String 转换为 base58 编码就是:tg7QzizXN6LpK2pEtzeKHG5DmUkgz7DiEsAmtPxvaYxEUEYNV2qKF6gaSEG4Qm5uZ7DeTT5F1CTmsEABN1DWnU7。这恰好就是 Txid,这是因为 在 Solana 中,Txid 就是 Tx 中的首个签名。
3. Tx 解析实例 2(两笔一对一转账)
下面考虑一个更加复杂的 Tx VGJxEj15eQaiQXwjvg7kYt3otikuZb4by2riKfFW8xoivAGn7tRDpAiWxppRbv8tvuTQ4FcWu5kmd9zdVeLjCjW,它同时有两笔转账:
7sCi23YDXsc3g4gRVMiaP2D2rFij4TQZV4T7xD2Jqh3o ------ 1000000 Lamports ------> TkPgkcDQYDUSDBfwTokwkZpn8spz94u4fvXii9U7CWY 4rRuNMdh8em2S5P8Ks9psJhCkKLhspDyFtfJoWxkpg8F ------ 2000000 Lamports ------> 4HvKrJtm7nrsa66n639ooe6kcY1U4FRBpQvxz81pLhYA
而且 Fee 由另一个帐户 AT42Zu5kD6V27rgXdkoTpE4mqvsDxsitzWYDr1iStu3r 来支付。
这个 Solana Devnet 上的 Tx 是通过 RPC sendTransaction 提交上链的,具体参数如下:
$ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d ' { "jsonrpc": "2.0", "id": 1, "method": "sendTransaction", "params": [ "Axhfrv3KFApUmAuq4SvFaBXEnm5Qke8NsVgcky+iuKveD/JOmzkOJPO2+vwKoz1yAK1IpfjqvsMt5q5hji6Xrw2H1k+eOq+tKie0q9awu2sroNZ6iymYPQN9K8uk8DlBgoXii9jVY0lROTHXw05Xhs1qjNCYEHg5EGyQIOkVknYOe6PHszYnyA8W3vEYQLI3YlWbkuyH9o4at52/0tsC3f5NMyewl2wIAx9UK72+z4ofcv4svuEj7jr5R8VL1sCUCgMAAQaMZbS+zgTtjQmuVFL8Vt+t6ZRpwIFVFImXdYZa93g/nTk8rZ/+r9cx4CD1u1EwRlAv7njiYkhtHILxF0jB0OfIZgJmPpu08QQoRk9FqB7+NcSnM92gHZ2+1vyfE9Iu6/IG2indv8FqFG+2/Ieu9DifmgpE2oztoOfk3Y7YtJFgnTDopgPjdwNGv+okX7P1Hzy3xsKYJeH/EiF9J0+5gPrXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADdGkp+x27zNcira/9SmZHuA9x8HbaGHyK6YDwS0kdtUwIFAgIDDAIAAABAQg8AAAAAAAUCAQQMAgAAAICEHgAAAAAA", { "encoding": "base64" } ] } ' {"jsonrpc":"2.0","result":"VGJxEj15eQaiQXwjvg7kYt3otikuZb4by2riKfFW8xoivAGn7tRDpAiWxppRbv8tvuTQ4FcWu5kmd9zdVeLjCjW","id":1}
为了便于分析,我们把 base64 编码的 Tx 转换为 Hex String 形式:
03185faefdca140a54980baae12bc56815c49e6e5091ef0db1581c932fa2b8abde0ff24e9b390e24f3b6fafc0aa33d7200ad48a5f8eabec32de6ae618e2e97af0d87d64f9e3aafad2a27b4abd6b0bb6b2ba0d67a8b29983d037d2bcba4f039418285e28bd8d56349513931d7c34e5786cd6a8cd098107839106c9020e91592760e7ba3c7b33627c80f16def11840b23762559b92ec87f68e1ab79dbfd2db02ddfe4d3327b0976c08031f542bbdbecf8a1f72fe2cbee123ee3af947c54bd6c0940a030001068c65b4bece04ed8d09ae5452fc56dfade99469c0815514899775865af7783f9d393cad9ffeafd731e020f5bb513046502fee78e262486d1c82f11748c1d0e7c86602663e9bb4f10428464f45a81efe35c4a733dda01d9dbed6fc9f13d22eebf206da29ddbfc16a146fb6fc87aef4389f9a0a44da8ceda0e7e4dd8ed8b491609d30e8a603e3770346bfea245fb3f51f3cb7c6c29825e1ff12217d274fb980fad70000000000000000000000000000000000000000000000000000000000000000dd1a4a7ec76ef335c8ab6bff529991ee03dc7c1db6861f22ba603c12d2476d5302050202030c0200000040420f0000000000050201040c0200000080841e0000000000
下面再把它解释为更方便阅读的形式:
+------------+--------------------------------------------------------+------------------------------------------------------------------+ | | signatures length | 03 | | +--------------------------------------------------------+------------------------------------------------------------------+ | | signature 0 | 185faefdca140a54980baae12bc56815c49e6e5091ef0db1581c932fa2b8abde | | | | 0ff24e9b390e24f3b6fafc0aa33d7200ad48a5f8eabec32de6ae618e2e97af0d | | signatures +--------------------------------------------------------+------------------------------------------------------------------+ | | signature 1 | 87d64f9e3aafad2a27b4abd6b0bb6b2ba0d67a8b29983d037d2bcba4f0394182 | | | | 85e28bd8d56349513931d7c34e5786cd6a8cd098107839106c9020e91592760e | | +--------------------------------------------------------+------------------------------------------------------------------+ | | signature 2 | 7ba3c7b33627c80f16def11840b23762559b92ec87f68e1ab79dbfd2db02ddfe | | | | 4d3327b0976c08031f542bbdbecf8a1f72fe2cbee123ee3af947c54bd6c0940a | +------------+--------------------------------------------------------+------------------------------------------------------------------+ | | | num required signatures | 03 | | | +------------------------------------+------------------------------------------------------------------+ | | message header | num readonly signed accounts | 00 | | | +------------------------------------+------------------------------------------------------------------+ | | | num readonly unsigned accounts | 01 | | +-------------------+------------------------------------+------------------------------------------------------------------+ | | | length | 06 | | | +------------------------------------+------------------------------------------------------------------+ | | | address 0 | 8c65b4bece04ed8d09ae5452fc56dfade99469c0815514899775865af7783f9d | | | +------------------------------------+------------------------------------------------------------------+ | | | address 1 | 393cad9ffeafd731e020f5bb513046502fee78e262486d1c82f11748c1d0e7c8 | | | +------------------------------------+------------------------------------------------------------------+ | | account addresses | address 2 | 6602663e9bb4f10428464f45a81efe35c4a733dda01d9dbed6fc9f13d22eebf2 | | | +------------------------------------+------------------------------------------------------------------+ | | | address 3 | 06da29ddbfc16a146fb6fc87aef4389f9a0a44da8ceda0e7e4dd8ed8b491609d | | | +------------------------------------+------------------------------------------------------------------+ | | | address 4 | 30e8a603e3770346bfea245fb3f51f3cb7c6c29825e1ff12217d274fb980fad7 | | | +------------------------------------+------------------------------------------------------------------+ | | | address 5 | 0000000000000000000000000000000000000000000000000000000000000000 | | +-------------------+------------------------------------+------------------------------------------------------------------+ | | recent blockhash | dd1a4a7ec76ef335c8ab6bff529991ee03dc7c1db6861f22ba603c12d2476d53 | | message +-------------------+------------------------------------+------------------------------------------------------------------+ | | | length | 02 | | | +-------+----------------------------+------------------------------------------------------------------+ | | | | program id index | 05 | | | | +------------------+---------+------------------------------------------------------------------+ | | | | | length | 02 | | | | | account address +---------+------------------------------------------------------------------+ | | | | indexes | index 0 | 02 | | | | | +---------+------------------------------------------------------------------+ | | | ins 0 | | index 1 | 03 | | | | +------------------+---------+------------------------------------------------------------------+ | | | | | length | 0c | | | | | instruction data +---------+------------------------------------------------------------------+ | | | | | data | 0200000040420f0000000000 | | | instructions +-------+------------------+---------+------------------------------------------------------------------+ | | | | program id index | 05 | | | | +------------------+---------+------------------------------------------------------------------+ | | | | | length | 02 | | | | | account address +---------+------------------------------------------------------------------+ | | | | indexes | index 0 | 01 | | | | | +---------+------------------------------------------------------------------+ | | | ins 1 | | index 1 | 04 | | | | +------------------+---------+------------------------------------------------------------------+ | | | | | length | 0c | | | | | instruction data +---------+------------------------------------------------------------------+ | | | | | data | 0200000080841e0000000000 | +------------+-------------------+-------+------------------+---------+------------------------------------------------------------------+
注:Instructions 的 program id index 对应的是 account addresses 的元素下标。上面例子中,两个 program id index 都是 05,即地址 0000000000000000000000000000000000000000000000000000000000000000,这个十六进制对应的 base58 地址就是 11111111111111111111111111111111,即 System Program 的地址。
3.1. 签名
待签名数据就是图 1 中的 Message 部分(即去掉其中的 Signatures 部分)。它是 Ed25519 签名中 SHA512(R||A||M)
中的 M
。
具体到前面的例子,待签名数据就是:
030001068c65b4bece04ed8d09ae5452fc56dfade99469c0815514899775865af7783f9d393cad9ffeafd731e020f5bb513046502fee78e262486d1c82f11748c1d0e7c86602663e9bb4f10428464f45a81efe35c4a733dda01d9dbed6fc9f13d22eebf206da29ddbfc16a146fb6fc87aef4389f9a0a44da8ceda0e7e4dd8ed8b491609d30e8a603e3770346bfea245fb3f51f3cb7c6c29825e1ff12217d274fb980fad70000000000000000000000000000000000000000000000000000000000000000dd1a4a7ec76ef335c8ab6bff529991ee03dc7c1db6861f22ba603c12d2476d5302050202030c0200000040420f0000000000050201040c0200000080841e0000000000
需要 3 个参与者对它进行签名:
- 支付交易 Fee 的 AT42Zu5kD6V27rgXdkoTpE4mqvsDxsitzWYDr1iStu3r
- 往其它地址转出 1000000 Lamports 的 7sCi23YDXsc3g4gRVMiaP2D2rFij4TQZV4T7xD2Jqh3o
- 往其它地址转出 2000000 Lamports 的 4rRuNMdh8em2S5P8Ks9psJhCkKLhspDyFtfJoWxkpg8F
下面只介绍首个签名。AT42Zu5kD6V27rgXdkoTpE4mqvsDxsitzWYDr1iStu3r 的 Ed25519 私钥是
aa5e711d133aca45d6ec97116de54a24a421a823385fc42fd342d5ecb17ce7d0
使用这个私钥对前面的数据进行签名后,得到签名数据为:
185faefdca140a54980baae12bc56815c49e6e5091ef0db1581c932fa2b8abde0ff24e9b390e24f3b6fafc0aa33d7200ad48a5f8eabec32de6ae618e2e97af0d
上面的 Hex String 转换为 base58 编码就是:VGJxEj15eQaiQXwjvg7kYt3otikuZb4by2riKfFW8xoivAGn7tRDpAiWxppRbv8tvuTQ4FcWu5kmd9zdVeLjCjW。这恰好就是 Txid,这是因为 在 Solana 中,Txid 就是 Tx 中的首个签名。
3.2. Rust 实例
打包上面交易的 Rust 程序如下:
use base64; use bincode; use hex; use solana_program::hash::Hash; use solana_program::system_instruction; use solana_sdk::signature::{Keypair, Signer}; use solana_sdk::transaction::Transaction; use std::str::FromStr; fn main() { let payer_keypair_bytes: [u8; 64] = [170,94,113,29,19,58,202,69,214,236,151,17,109,229,74,36,164,33,168,35,56,95,196,47,211,66,213,236,177,124,231,208,140,101,180,190,206,4,237,141,9,174,84,82,252,86,223,173,233,148,105,192,129,85,20,137,151,117,134,90,247,120,63,157]; let payer_keypair = Keypair::from_bytes(&payer_keypair_bytes).unwrap(); println!("payer seckey {}", hex::encode(payer_keypair.secret().to_bytes())); let payer_pubkey = Signer::pubkey(&payer_keypair); println!("payer_pubkey {}", payer_pubkey.to_string()); // AT42Zu5kD6V27rgXdkoTpE4mqvsDxsitzWYDr1iStu3r let from_keypair_bytes: [u8; 64] = [10,178,29,4,179,245,97,5,183,90,15,188,22,32,116,195,134,44,159,5,226,5,98,67,98,110,240,79,52,106,175,221,102,2,102,62,155,180,241,4,40,70,79,69,168,30,254,53,196,167,51,221,160,29,157,190,214,252,159,19,210,46,235,242]; let from_keypair = Keypair::from_bytes(&from_keypair_bytes).unwrap(); println!("from seckey {}", hex::encode(from_keypair.secret().to_bytes())); let from_pubkey = Signer::pubkey(&from_keypair); println!("from_pubkey {}", from_pubkey.to_string()); // 7sCi23YDXsc3g4gRVMiaP2D2rFij4TQZV4T7xD2Jqh3o let from2_keypair_bytes: [u8; 64] = [38,101,100,119,58,71,219,79,52,219,72,137,101,25,58,229,156,215,79,222,39,131,151,220,160,191,119,117,229,89,87,8,57,60,173,159,254,175,215,49,224,32,245,187,81,48,70,80,47,238,120,226,98,72,109,28,130,241,23,72,193,208,231,200]; let from2_keypair = Keypair::from_bytes(&from2_keypair_bytes).unwrap(); println!("from2 seckey {}", hex::encode(from2_keypair.secret().to_bytes())); let from2_pubkey = Signer::pubkey(&from2_keypair); println!("from2_pubkey {}", from2_pubkey.to_string()); // 4rRuNMdh8em2S5P8Ks9psJhCkKLhspDyFtfJoWxkpg8F let to_keypair_bytes: [u8; 64] = [195,70,54,2,213,99,33,240,169,173,231,145,119,25,230,97,200,117,254,168,54,28,200,241,55,1,190,169,49,12,190,232,6,218,41,221,191,193,106,20,111,182,252,135,174,244,56,159,154,10,68,218,140,237,160,231,228,221,142,216,180,145,96,157]; let to_keypair = Keypair::from_bytes(&to_keypair_bytes).unwrap(); let to_pubkey = Signer::pubkey(&to_keypair); println!("to_pubkey {}", to_pubkey.to_string()); // TkPgkcDQYDUSDBfwTokwkZpn8spz94u4fvXii9U7CWY let to2_keypair_bytes: [u8; 64] = [90,49,225,138,1,223,59,108,196,75,251,191,171,182,89,126,28,184,47,27,226,140,180,230,206,219,137,165,17,31,98,157,48,232,166,3,227,119,3,70,191,234,36,95,179,245,31,60,183,198,194,152,37,225,255,18,33,125,39,79,185,128,250,215]; let to2_keypair = Keypair::from_bytes(&to2_keypair_bytes).unwrap(); let to2_pubkey = Signer::pubkey(&to2_keypair); println!("to2_pubkey {}", to2_pubkey.to_string()); // 4HvKrJtm7nrsa66n639ooe6kcY1U4FRBpQvxz81pLhYA // Creating the transfer sol instruction let ix0 = system_instruction::transfer(&from_pubkey, &to_pubkey, 1_000_000); let ix1 = system_instruction::transfer(&from2_pubkey, &to2_pubkey, 2_000_000); let ins = [ix0, ix1]; // Putting the transfer sol instruction into a transaction // let rpc_url = String::from("https://api.devnet.solana.com"); // let connection = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed()); // let recent_blockhash = connection.get_latest_blockhash().expect("Failed to get latest blockhash."); let recent_blockhash = Hash::from_str("Ft6MUpKNyyjyb1Trv1oXK75eAiGYfVN5VjUnGVbdXDkv").unwrap(); // dd1a4a7ec76ef335c8ab6bff529991ee03dc7c1db6861f22ba603c12d2476d53 println!("recent_blockhash {}", recent_blockhash); println!("recent_blockhash {}", hex::encode(recent_blockhash.to_bytes())); let txn = Transaction::new_signed_with_payer(&ins, Some(&payer_pubkey), &[&payer_keypair, &from_keypair, &from2_keypair], recent_blockhash); let preimage = txn.message.serialize(); println!("preimage = {}", hex::encode(preimage)); let tx_vec = bincode::serialize(&txn).unwrap(); println!("hex tx: {}", hex::encode(&tx_vec)); println!("base64 tx: {}", base64::encode(&tx_vec)); }
4. Tx 解析实例 3(Token 2022 转账)
下面以 Tx 3FLzsDWEyERGWyeQvX5CQgSuYAycErPyvvvEFHw9hn1hubySvVoix8C6LTjAveTP3e8LDCrWLZPpub4kyJsFAfJL 为例介绍一下这个 Tx 的细节。
这个 Solana Mainnet 上的 Tx 是通过 RPC sendTransaction 提交上链的,具体参数如下:
$ curl https://api.mainnet-beta.solana.com -X POST -H "Content-Type: application/json" -d ' {"method":"sendTransaction","jsonrpc":"2.0","params":["AXBnhpJ4Y5GJ5uzEJkf4uWaIvpedL3cwyAOGE/De8ZSj1ac5PFDxdW1XnBSGOYeL74FminSVFIcuqzAA2WLpGg0BAAMGoyt1UmcbIpl8MwxTrJHqaNBHKdDL3XHkiTRBblLseNsUmkTrYwsPXTC7AzjBvZtaYJFJPNrtJLB67ToQRzcbTq+QnyOd/QYbgbjugrDHeLse9zlsmi5O2c7dpT8AbhBqqDijZLhcKuVLVBmL6Ve9S9DvSU7kn1XtkCnOtnDXGjADBkZv5SEXMv/srbpyw5vnvIzlu8X3EmssQ5s6QAAAAAbd9uHudY/eGEJdvORszdq2GvxNg7kNJ/69+SjYoYv8nXMENVJdGaxITQAKrAwgTRwOlBQHmWGi9xKCOKVr0WUDBAAJA7X0DAAAAAAABAAFAgEuAAAFBQEDAgAACgx7AAAAAAAAAAU=",{"encoding":"base64","preflightCommitment":"confirmed"}],"id":"a3c4f431-0f2a-4632-91ae-d3cafaaceb63"} ' {"jsonrpc": "2.0", "result": "3FLzsDWEyERGWyeQvX5CQgSuYAycErPyvvvEFHw9hn1hubySvVoix8C6LTjAveTP3e8LDCrWLZPpub4kyJsFAfJL", "id": "a3c4f431-0f2a-4632-91ae-d3cafaaceb63"}
这个 Tx 的功能是从地址 BywtbATeVZvzjeiK9fuNtRwkKFzTxyL5nJVPNktRY3cn 转移 0.00123 BERN(属于 Token 2022 代币)到地址 J1nW8Vu6jPxYQMqZuHDGUPNaU3sNZvonXTTtDftFZxrM 中:
BywtbATeVZvzjeiK9fuNtRwkKFzTxyL5nJVPNktRY3cn ------ 0.00123 BERN ------> J1nW8Vu6jPxYQMqZuHDGUPNaU3sNZvonXTTtDftFZxrM ^ ^ | | | owner | owner | | | | 2PRbFDUUw8QXLHr4m5io6A3aU7bAGbsFiUTjuYTbDgyb (associated token account) CpLFa6WpP9nmmqYUtewthV4o3Lo8YWWoq6kdEqPvZep1 (associated token account)
为了便于分析,我们把 base64 编码的 Tx 转换为 Hex String 形式:
017067869278639189e6ecc42647f8b96688be979d2f7730c8038613f0def194a3d5a7393c50f1756d579c148639878bef81668a749514872eab3000d962e91a0d01000306a32b7552671b22997c330c53ac91ea68d04729d0cbdd71e48934416e52ec78db149a44eb630b0f5d30bb0338c1bd9b5a6091493cdaed24b07aed3a1047371b4eaf909f239dfd061b81b8ee82b0c778bb1ef7396c9a2e4ed9cedda53f006e106aa838a364b85c2ae54b54198be957bd4bd0ef494ee49f55ed9029ceb670d71a300306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006ddf6e1ee758fde18425dbce46ccddab61afc4d83b90d27febdf928d8a18bfc9d730435525d19ac484d000aac0c204d1c0e9414079961a2f7128238a56bd1650304000903b5f40c000000000004000502012e0000050501030200000a0c7b0000000000000005
可把上面 Tx 的每个字节含义解释如下:
signatures |
signatures length | 01 | ||||
signature 0 |
7067869278639189e6ecc42647f8b96688be979d2f7730c8038613f0def194a3 d5a7393c50f1756d579c148639878bef81668a749514872eab3000d962e91a0d |
|
||||
message |
message header |
num required signatures | 01 | |||
num readonly signed accounts | 00 | |||||
num readonly unsigned accounts | 03 | |||||
account addresses |
length | 06 | ||||
address 0 | a32b7552671b22997c330c53ac91ea68d04729d0cbdd71e48934416e52ec78db | sender: BywtbATeVZvzjeiK9fuNtRwkKFzTxyL5nJVPNktRY3cn | ||||
address 1 | 149a44eb630b0f5d30bb0338c1bd9b5a6091493cdaed24b07aed3a1047371b4e | sender token acnt: 2PRbFDUUw8QXLHr4m5io6A3aU7bAGbsFiUTjuYTbDgyb | ||||
address 2 | af909f239dfd061b81b8ee82b0c778bb1ef7396c9a2e4ed9cedda53f006e106a | recipient token acnt: CpLFa6WpP9nmmqYUtewthV4o3Lo8YWWoq6kdEqPvZep1 | ||||
address 3 | a838a364b85c2ae54b54198be957bd4bd0ef494ee49f55ed9029ceb670d71a30 | Token 2022 mint addr: CKfatsPMUf8SkiURsDXs7eK6GWb4Jsd6UDbs7twMCWxo | ||||
address 4 | 0306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a40000000 | ComputeBudget111111111111111111111111111111 | ||||
address 5 | 06ddf6e1ee758fde18425dbce46ccddab61afc4d83b90d27febdf928d8a18bfc | Token 2022 addr: TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb | ||||
recent blockhash | 9d730435525d19ac484d000aac0c204d1c0e9414079961a2f7128238a56bd165 | |||||
instructions |
length | 03 | ||||
ins 0 |
program id index | 04 | 这是设置手续费相关的 | |||
account address indexes |
length |
00 |
|
|||
instruction data |
length | 09 | ||||
data | 03b5f40c0000000000 | |||||
ins 1 |
program id index | 04 | 这是设置手续费相关的 | |||
account address indexes |
length |
00 |
|
|||
instruction data |
length | 05 | ||||
data | 02012e0000 | |||||
ins 2 |
program id index | 05 | 这个索引对应的地址是 TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb | |||
account address indexes |
length | 05 | 入口函数 process 的 accounts 参数 | |||
index 0 | 01 | source_account_info | ||||
index 1 | 03 | expected_mint_info | ||||
index 2 | 02 | destination_account_info | ||||
index 3 | 00 | authority_info | ||||
index 4 | 00 | |||||
instruction data |
length | 0a | 入口函数 process 的 input 参数 | |||
data | 0c7b0000000000000005 | 0c(transferChecked), 7b00000000000000(amount), 05(decimals) |
最后一行数据 0c7b0000000000000005 可以进一步分解如下(参考 PodTokenInstruction::TransferChecked ):
0c // instruction type, transferChecked 7b00000000000000 // instruction data, 转帐数量 123 的小端格式 05 // instruction data, decimals。指定精度 5,它必须和所转帐 Token(这里是 BERN)的精度一致,否则报错
注 1:明明 Tx 中是转帐 0.00123 BERN,为什么在区块浏览器中查看帐户的 Token Balance Change 时,会显示目标地址只收到了 0.00117 BERN 呢?这是因为 Solana 2022 支持 Hook,代币 BERN 实现了 Hook,会在每次转帐时销毁一部分代币。
注 2:帐户的 Associated Token Account 合约是什么时候部署呢?Account1 给 Account2 转帐 TokenA 时,如果 Account2 对应的 TokenA 的 Associated Token Account 还没有部署(这往往是 Account2 从未收到过 TokenA 时),那么 Account1 会帮 Account2 部署这个合约。比如交易 25NhZZgELi5hi9xCvv6d1utXR7QQQKkc7b3bwDsa28wMtYLhSuxNy7Hw4VNwoxBiv9RveYDKT78xv1E3P3vd56uJ 就是转帐 Token 时还帮别人部署 Associated Token Account 的例子。
5. 参考
Solana Transactions: https://solana.com/docs/core/transactions
bincode Serialization specification: https://github.com/bincode-org/bincode/blob/trunk/docs/spec.md
The Solana host and client SDK: https://docs.rs/solana-sdk/latest/solana_sdk/index.html