科普 | Solana账户概念与命令行交易实现
Account账户理解
Solana链上的信息是记录在文件中,这个文件在Solana上表现为Account,所以用户所需要支付的就是一个文件存储所需要的花费,是以SOL计价的。如果想要关闭删除文件的话,那么只要把这个Account的SOL都转走,那么这个Account对应的地址,在链上就没有钱来买位置了,也就会被删除掉了。
- 私钥
私钥是私自保管不可示人的。私钥是一串乱码,不好记,与之对应有一串助记词。助记词可以通过算法推出私钥,所以实际上我们在使用钱包时,只要记住助记词。好比你使用支付宝的时候,不用输入支付密码,只需要伸出手指头或者露脸扫描以下。那么手指头就相当于是支付密码的助记物。
- 公钥
公钥是可以展示给别人看的,公钥也是合约的地址。在solana上智能合约一般称为“Onchain Program”,所以公钥也叫programId。
- 交易签名
当交易存在相应的数字签名时,表示该账户的私钥持有人已签名并因此“授权”了该交易。
Transaction介绍
- Transaction
Transaction是由客户端向solana节点发起请求的单元,一个Transaction可能包含有多个Instruction。Solana节点在收到一个客户端发起的Transaction后,会先解析里面的每个Instruction,然后根据Instruction里面的programId字段,来调用对应的智能合约,并将Instruction传递给该智能合约。
export
class Transaction{
signatures: Array;
signature?: Buffer;
//Transaction 包含多个 instruction
instructions: Array;
recentBlockhash?: Blockhash;
nonceInfo?: NonceInformation;
feePayer:Publickey | null;
constructor(opts?: TransactionCtorFields);
}
- Instuction
export
class TransactionInstruction{
keys: Array;
programId: PublicKey;
data: Buffer;
constructor(opts?: TransactionInstructionCtorFields);
}
指令Instruction是智能合约处理的基本单元:整体流程就是DApp客户端将自定义的指令数据序列化到data里面,然后将账号信息,programId和data发到链上,Solana节点为其找到要执行的程序,并将账号信息和数据data传递给合约程序,合约程序里面将这个data数据再反序列化,得到客户端传过来的具体参数。
Runtime介绍
Solana的Runtime是用来执行BPF字节码的。合约即On-Chain program是被编译成BPF的字节码的,Solana的节点的runtime会加载这个BPF字节码并执行它的逻辑。Solana选择BPF是因为它的执行效率更快。为了限制一个合约不至于占用所有资源,runtime对合约还做了一些限制,这些限制可以通过命令行也可以通过SDK查询。pub
struct
BpfComputeBudget {
/// Number of compute units that an instruction is allowed. Compute units
/// are consumed by program execution, resources they use, etc...
pub max_units: u64,
/// Number of compute units consumed by a log call
pub log_units: u64,
/// Number of compute units consumed by a log_u64 call
pub log_64_units: u64,
/// Number of compute units consumed by a create_program_address call
pub create_program_address_units: u64,
/// Number of compute units consumed by an invoke call (not including the cost incurred by
/// the called program)
pub invoke_units: u64,
/// Maximum cross-program invocation depth allowed including the original caller
pub max_invoke_depth: usize,
/// Base number of compute units consumed to call SHA256
pub sha256_base_cost: u64,
/// Incremental number of units consumed by SHA256 (based on bytes)
pub sha256_byte_cost: u64,
/// Maximum BPF to BPF call depth
pub max_call_depth: usize,
/// Size of a stack frame in bytes, must match the size specified in the LLVM BPF backend
pub stack_frame_size: usize,
/// Number of compute units consumed by logging a `Pubkey`
pub log_pubkey_units: u64,
}
当执行超过限制时,该条合约执行就会失败。
solana/web3.js介绍
1、创建Account账户
Account类定义export
class Account{
constructor(secretKey?: Buffer | Uint8Array | Array<number>);
publickey: PublicKey;
secret:Buffer;
}
创建一个accountimport {Account} from
"@solana/web3.js";
//secretKey 即私钥
const myAccount = new Account(secretKey);
2、发送交易sendTransaction
Transaction接口定义export
class Transaction{
signatures: Array;
signature?: Buffer;
//Transaction 包含多个 instruction
instructions: Array;
recentBlockhash?: Blockhash;
nonceInfo?: NonceInformation;
feePayer:Publickey | null;
constructor(opts?: TransactionCtorFields);
}
export
type SignaturePubkeyPair = {
signature?: Buffer;
publicKey: PublicKey;
}
sendTransaction方法/**
* Sign and send a transaction
*/
async sendTransaction(
transaction: Transaction,
signers: Array,
options?: SendOptions,
): Promise
/**
* Transaction signature as base-58 encoded string
*/
export
type TransactionSignature = string;
@solana/web3.js sendTransaction方法实现
async sendTransaction(
transaction: Transaction,
signers: Array,
options?: SendOptions,
): Promise {
if (transaction.nonceInfo) {
transaction.sign(...signers);
} else {
let disableCache = this._disableBlockhashCaching;
for (;;) {
transaction.recentBlockhash = await
this._recentBlockhash(disableCache);
transaction.sign(...signers);
if (!transaction.signature) {
throw
new
Error('!signature'); // should never happen
}
const signature = transaction.signature.toString('base64');
if (!this._blockhashInfo.transactionSignatures.includes(signature)) {
// The signature of this transaction has not been seen before with the
// current recentBlockhash, all done. Let's break
this._blockhashInfo.transactionSignatures.push(signature);
break;
} else {
// This transaction would be treated as duplicate (its derived signature
// matched to one of already recorded signatures).
// So, we must fetch a new blockhash for a different signature by disabling
// our cache not to wait for the cache expiration (BLOCKHASH_CACHE_TIMEOUT_MS).
disableCache = true;
}
}
}
3、确认交易confirmTransaction
交易发送完成后,我们拿到TransactionSignature结果,进行确认。60s(or 30s)没结果,我们可以再次确认,也可以手动在solana Explorer浏览器上确认。
/**
* Confirm the transaction identified by the specified signature.
*/
async confirmTransaction(
signature: TransactionSignature,
commitment?: Commitment,
): Promise> {
let decodedSignature;
try {
decodedSignature = bs58.decode(signature);
} catch (err) {
throw
new
Error('signature must be base58 encoded: ' + signature);
}
assert(decodedSignature.length === 64, 'signature has invalid length');
const start = Date.now();
const subscriptionCommitment = commitment || this.commitment;
let subscriptionId;
let response: RpcResponseAndContext | null = null;
const confirmPromise = new
Promise((resolve, reject) => {
try {
//订阅签名更新
// Register a callback to be invoked upon signature updates
subscriptionId = this.onSignature(
signature,
(result: SignatureResult, context: Context) => {
subscriptionId = undefined;
response = {
context,
value: result,
};
resolve(null);
},
subscriptionCommitment,
);
} catch (err) {
reject(err);
}
});
let timeoutMs = 60 * 1000;
switch (subscriptionCommitment) {
case
'processed':
case
'recent':
case
'single':
case
'confirmed':
case
'singleGossip': {
timeoutMs = 30 * 1000;
break;
}
// exhaust enums to ensure full coverage
case
'finalized':
case
'max':
case
'root':
}
try {
await promiseTimeout(confirmPromise, timeoutMs);
} finally {
if (subscriptionId) {
this.removeSignatureListener(subscriptionId);
}
}
if (response === null) {
const duration = (Date.now() - start) / 1000;
throw
new
Error(
`Transaction was not confirmed in ${duration.toFixed(
2,
)} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`,
);
}
return response;
}
Solana CLI交易实现
$ solana-keygen new --outfile my_solana_wallet.json # 创建我的第一个钱包,一个文件系统钱包
Generating a new keypair
For added security, enter a BIP39 passphrase
NOTE! This passphrase improves security of the recovery seed phrase NOT the
keypair file itself, which is stored as insecure plain text
BIP39 Passphrase (empty for none):
Wrote new keypair to my_solana_wallet.json
============================================================================
pubkey: 299PP8NZEecvobP6kjfbnEVPfJ82jeLhkr4MiqPe3nEL
# 这是我的第一个钱包地址
============================================================================
Save this seed phrase and your BIP39 passphrase to recover your new keypair:
desert off ladder brisk dice wall under veteran eternal chicken habit cancel
# 如果这是一个真的钱包,请不要把助记词分享出去!
============================================================================
$ solana airdrop 1 299PP8NZEecvobP6kjfbnEVPfJ82jeLhkr4MiqPe3nEL
Requesting airdrop of 1 SOL
# 空头 1 Sol到我的钱包地址
Signature: 3mBN5eucnE8teZ1GXuEyiehChzz9Gi2gSNFGYHKxoGLsvezuPXeZYRXXkPWc1C6XYz1R7NdoEDGEk2HaJMFfNfSe
1 SOL
$ solana balance 299PP8NZEecvobP6kjfbnEVPfJ82jeLhkr4MiqPe3nEL
1 SOL # 检查地址的余额
$ solana-keygen new --no-outfile # 创建第二个钱包,纸钱包
Generating a new keypair
For added security, enter a BIP39 passphrase
NOTE! This passphrase improves security of the recovery seed phrase NOT the
keypair file itself, which is stored as insecure plain text
BIP39 Passphrase (empty for none):
==================================================================================
pubkey: ExQyynsJZCxtukwqbjWoGNJ8XX2sz7zg472GxC4Xj2GU
# 这是第二个纸钱包的地址
==================================================================================
Save this seed phrase and your BIP39 passphrase to recover your new keypair:
pretty can question recycle critic embody permit daughter ghost rigid edit private
# 如果这是一个真的钱包,请不要把助记词分享出去!
==================================================================================
$ solana transfer --from my_solana_wallet.json ExQyynsJZCxtukwqbjWoGNJ8XX2sz7zg472GxC4Xj2GU 0.5 --allow-unfunded-recipient --url https://api.devnet.solana.com --fee-payer my_solana_wallet.json
# 转账代币到纸钱包的公钥地址
Signature: 2AjkxdQXQ6ZWaACFJATsTCAEdppGDRccEXLhKd6HEda2AtwyzKxvDi1jxJzyFBVL9PTPn2JKFLGtCwBNoKBMosfx
# 这是交易的签名
$ solana balance 299PP8NZEecvobP6kjfbnEVPfJ82jeLhkr4MiqPe3nEL
0.499995 SOL
# 发送账号剩下的代币少于0.5 SOL,因为减去了0.000005 SOL 的交易手续费
$ solana balance ExQyynsJZCxtukwqbjWoGNJ8XX2sz7zg472GxC4Xj2GU
0.5 SOL
# 接受账号余额为0.5 SOL,由发送账号转账