Solana简介

Solana利用了 工作历史证明 PoH、基站拜占庭容错(Tower BFT)、涡轮机(区块传播协议)、海湾流(无内存交易转发协议)、海平面(并行智能合约)、管道(验证交易)、云散(水平扩展账户数据库)以及 档案(分布式账本存储)

等一系列牛逼哄哄的技术构建出一个超高性能的区块链公链,目前TPS实测已经达到了65,000TPS。高TPS带来的一个问题是数据暴胀,相对来讲对节点要提供可以回溯久远的历史查询服务应该会带来更大的挑战!

模型

一笔交易中包含一个或多个指令,可执行Program会根据接受到的指令、指令参数和账号签名,运行相关逻辑,载入相关的账户数据

根据提供的签名,可以读取或修改一些账户的状态数据等,如果任何一个指令无效,则所有的账户的状态更新都会被重置等等,详见 Overview

账户

每个账户都有自己的data 大小上限为10MB,Solana的合约状态都是存储在不同账户上的data里实现的,和EOS一样需要租金来获得一定量的存储空间,具体计算详见Accounts

1
2
Rent: 2,439 = 19.055441478439427 (rent rate) * 128 bytes (minimum account size) * 1 (epoch)
Account Balance: 7,561 = 10,000 (transfered lamports) - 2,439 (this account's rent fee for an epoch)

合约

在Solana体系中叫program,Solana使用LLVM编译器基础结构,因此可以使用可以针对LLVM的BPF后端的任何编程语言编写程序。

Solana目前支持用Rust和C / C ++编写程序(官方合约都是rust写的,最近刚学的rust可以用上场了hh)详见Runtime

Program学习

官方有一些列的Program叫SPL,具体在:https://spl.solana.com/

  • Token - 同质和非同质化代币的实现
  • Token Swap - Uniswap-like 的AMM交易所
  • Token-Lending - 类似Aave的借贷协议

等等…

简单看看SPL Token指令的实现

create-token

创建Token
token/cli/src/main.src:2194

1
2
3
4
5
6
7
8
9
10
11
("create-token", Some(arg_matches)) => {
...
command_create_token(
&config,
decimals,
token,
mint_authority,
arg_matches.is_present("enable_freeze"),
memo,
)
}

command_create_token

  • system_instruction::create_account
  • initialize_mint

创建Token账户,并对Token账户执行了initialize_mint指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
let mut instructions = vec![
system_instruction::create_account(
&config.fee_payer,
&token,
minimum_balance_for_rent_exemption,
Mint::LEN as u64,
&spl_token::id(),
),
initialize_mint(
&spl_token::id(),
&token,
&authority,
freeze_authority_pubkey.as_ref(),
decimals,
)?,
];


/// token/program/src/instruction.rs:630
pub fn initialize_mint(
token_program_id: &Pubkey,
mint_pubkey: &Pubkey,
mint_authority_pubkey: &Pubkey,
freeze_authority_pubkey: Option<&Pubkey>,
decimals: u8,
) -> Result<Instruction, ProgramError> {
...
// 指令的accounts构造
// 后面指令处理阶段拿到的accounts也是这个
let accounts = vec![
AccountMeta::new(*mint_pubkey, false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
];

Ok(Instruction {
program_id: *token_program_id,
accounts,
data,
})
}

process_initialize_mint 指令的具体处理逻辑

Mint 实现了Pack Trait

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pub struct Mint {
/// Optional authority used to mint new tokens. The mint authority may only be provided during
/// mint creation. If no mint authority is present then the mint has a fixed supply and no
/// further tokens may be minted.
pub mint_authority: COption<Pubkey>,
/// Total supply of tokens.
pub supply: u64,
/// Number of base 10 digits to the right of the decimal place.
pub decimals: u8,
/// Is `true` if this structure has been initialized
pub is_initialized: bool,
/// Optional authority to freeze token accounts.
pub freeze_authority: COption<Pubkey>,
}

impl Pack for Mint {
fn unpack_from_slice(src: &[u8]) {
...
},
fn pack_into_slice(&self, dst: &mut [u8]){
...
}
}

具体的指令实现

/// token/program/src/processor.rs:28

1
2
3
4
5
6
7

pub fn process_initialize_mint(
accounts: &[AccountInfo],
decimals: u8,
mint_authority: Pubkey,
freeze_authority: COption<Pubkey>,
) -> ProgramResult {

获取第一个AccountInfo

1
2
let account_info_iter = &mut accounts.iter();
let mint_info = next_account_info(account_info_iter)?;

从data中反序列化Mint

1
let mut mint = Mint::unpack_unchecked(&mint_info.data.borrow())?;

修改一些属性

1
2
3
4
5
// 铸造权限的pubKey
mint.mint_authority = COption::Some(mint_authority);
mint.decimals = decimals;
mint.is_initialized = true;
mint.freeze_authority = freeze_authority;

序列化更新mint_info.data

1
2
3
Mint::pack(mint, &mut mint_info.data.borrow_mut())?;
Ok(())
}

create-account

创建token账户和create-token一样创建了个新账号,并执行了初始化

Account

Token Acount 主要用来存储Token的amount
owner 指向了所属账户
mint 指向了token账户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
pub struct Account {
/// The mint associated with this account
pub mint: Pubkey,
/// The owner of this account.
pub owner: Pubkey,
/// The amount of tokens this account holds.
pub amount: u64,
/// If `delegate` is `Some` then `delegated_amount` represents
/// the amount authorized by the delegate
pub delegate: COption<Pubkey>,
/// The account's state
pub state: AccountState,
/// If is_some, this is a native token, and the value logs the rent-exempt reserve. An Account
/// is required to be rent-exempt, so the value is used by the Processor to ensure that wrapped
/// SOL accounts do not drop below this threshold.
pub is_native: COption<u64>,
/// The amount delegated
pub delegated_amount: u64,
/// Optional authority to close the account.
pub close_authority: COption<Pubkey>,
}

impl Pack for Account {
...
}
1
2
3
4
5
6
7
8

/// token/program/src/processor.rs:55
fn _process_initialize_account(
accounts: &[AccountInfo],
owner: Option<&Pubkey>,
) -> ProgramResult {

let account_info_iter = &mut accounts.iter();

mint账户 和要初始化的new_account_info

1
2
3
4
5
6
7
8
9

let new_account_info = next_account_info(account_info_iter)?;
// mint账户
let mint_info = next_account_info(account_info_iter)?;
let owner = if let Some(owner) = owner {
owner
} else {
next_account_info(account_info_iter)?.key
};

从new_account_info.data中反序列化Account

1
2
3
4
let mut account = Account::unpack_unchecked(&new_account_info.data.borrow())?;
if account.is_initialized() {
return Err(TokenError::AlreadyInUse.into());
}

初始化Token Account的信息

1
2
3
4
5
6
7
8
9
10
11
if *mint_info.key != crate::native_mint::id() {
let _ = Mint::unpack(&mint_info.data.borrow_mut())
.map_err(|_| Into::<ProgramError>::into(TokenError::InvalidMint))?;
}

account.mint = *mint_info.key;
account.owner = *owner;
account.delegate = COption::None;
account.delegated_amount = 0;
account.amount = 0;
account.state = AccountState::Initialized;

写入状态

1
2
3
Account::pack(account, &mut new_account_info.data.borrow_mut())?;
Ok(())
}

transfer,mint

像其他的指令都是对不同账户的状态(Mint、Account)进行增删改查

浏览器

用区块链浏览器看看不同账户的数据 https://explorer.solana.com/,以[USD Coin](https://explorer.solana.com/address/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v “USD Coin”)为例:

USD Coin

通过getTokenLargestAccounts接口可以直接查询到持有量比较大的账户

常规program

  • executeable 是否可执行
  • last_deployed_slot 上一次部署的slot
  • upgrade_authority 具有更新权限的账户
  • executable_data 指向了存program代码的账户

Program Executable Data Account

用来存储编译后代码的账户,代码占用903506Bytes

Token Account

owner 指向了所属的Account账户

Account

根据getTokenAccountsByOwner接口可查询某个账户到相关的TokenAccount持有币种的数量