科普 | 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;
}

创建一个account
import {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,由发送账号转账


写评论,请先登录