Solana中的跨合约调用 及 Program Derived Addresses
Posted mutourend
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Solana中的跨合约调用 及 Program Derived Addresses相关的知识,希望对你有一定的参考价值。
1. 引言
Solana runtime可通过cross-program invocation机制来支持合约间的相互调用。
invoking合约A(Caller) 可触发调用 invoked合约B(Callee) 的instruction。invoking合约A 将halted直到 invoked合约B处理完成该instruction。
借助Program derived address,合约可 issue instructions that contain signed accounts that were not signed in the original transaction。
2. Cross-Program Invocation机制
如客户端可创建a transaction that modifies two accounts, each owned by separate on-chain programs:
let message = Message::new(vec![
token_instruction::pay(&alice_pubkey),
acme_instruction::launch_missiles(&bob_pubkey),
]);
client.send_and_confirm_message(&[&alice_keypair, &bob_keypair], &message);
替代方案可为:客户端可允许acme
合约来代替其触发token
instruction:
let message = Message::new(vec![
acme_instruction::pay_and_launch_missiles(&alice_pubkey, &bob_pubkey),
]);
client.send_and_confirm_message(&[&alice_keypair, &bob_keypair], &message);
存在2个链上合约:
- token合约:实现了
pay()
instruction。 - acme合约:实现了
launch_missiles()
instruction。
可借助cross-program invocation,在acme合约中,实现对token合约的调用:
mod acme
use token_instruction;
fn launch_missiles(accounts: &[AccountInfo]) -> Result<()>
...
fn pay_and_launch_missiles(accounts: &[AccountInfo]) -> Result<()>
let alice_pubkey = accounts[1].key;
let instruction = token_instruction::pay(&alice_pubkey);
invoke(&instruction, accounts)?; //
launch_missiles(accounts)?;
其中的invoke()
函数集成在Solana runtime中,负责routing the given instruction to the token
program via the instruction’s program_id
field。
注意,invoke
时需要传入所有accounts:
- the executable account (the ones that matches the instruction’s program id)
- the accounts passed to the instruction processor
在调用pay()
之前,runtime需确保acme
未修改token
所拥有的任何account。
pay()
执行完成后,runtime需确保token
未修改acme
所拥有的任何account。
2.1 instructions that require privileges
runtime使用the privileges granted to the caller program to determine what privileges can be extended to the callee。
在本上下文中,privileges是指:
- signers
- writable accounts
即若caller处理的instruction中包含了signer和(或)writable account,则该caller可触发同样包含signer和(或)writable account的instruction。
这种privileges extension依赖于合约是不会更改的,排除了合约升级这种特殊场景。
在本例中,acme
合约中,runtime可安全地将交易签名 当成是 token
instruction的签名。当runtime看到token
instruction中引用了alice_pubkey
时,其会在acme
instruction中查找该key是否对应a signed account。此时,即可授权token
合约修改Alice的account。
2.2 Program signed accounts
借助Program derived address,合约可 issue instructions that contain signed accounts that were not signed in the original transaction。
合约可调用invoke_signed()
来sign an account with program derived addresses:
invoke_signed(
&instruction,
accounts,
&[&["First addresses seed"],
&["Second addresses first seed", "Second addresses second seed"]],
)?;
2.3 Call Depth
通过Cross-program invocations,支持合约A 直接调用 合约B,但是depth当前限制为4。
2.4 Reentrancy
Reentrancy当前限制为direct self recursion capped at a fixed depth。该限制可防止a program might invoke another from an intermediary state without the knowledge that it might later be called back into。而direct self recursion可使合约能完全控制其state when it gets called back。
3. Program Derived Addresses(PDAs)
当跨合约调用时,Program derived addresses支持在合约内部生成签名。
Using a program derived address, a program may be given the authority over an account and later transfer that authority to another. This is possible because the program can act as the signer in the transaction that gives authority.
program derived addresses具有如下优点:
- 1)支持合约控制program addresses,使得外部用户无法为这些address生成有效的签名。
- 2)支持合约通过代码来为 跨合约调用的instructions内的program addresses 进行签名。
基于以上2个优点,用户可安全地 transfer or assign the authority of on-chain assets to program addresses,然后该合约可assign the authority elsewhere at its discretion。
从而可将program derived address用于以下场景:
- Decentralized Exchanges that transfer assets between matching bid and ask orders.
- Auctions that transfer assets to the winner.
- Games or prediction markets that collect and redistribute prizes to the winners.
3.1 program address 没有私钥
program address不在ed25519 curve上,因此,没有有效的私钥与其关联,从而使得无法为其生成签名。
由于其没有私钥合约可issue an instruction,将该program address作为a signer。
3.2 基于hash生成的program address
使用a 256-bit pre-image resistant hash function,根据a collection of seeds 以及 a program id,来确定性地生成program address。
program address必须不在ed25519 curve上,以确保没有关联的私钥。若发现生成的address在ed25519 curve上,则直接放回错误(对于特定的seeds和program id,这种情况发生的概率为50/50,若发生,可更换seed直到生成有效的program address)。
pub fn create_with_seed(
base: &Pubkey,
seed: &str,
program_id: &Pubkey,
) -> Result<Pubkey, SystemError>
if seed.len() > MAX_ADDRESS_SEED_LEN
return Err(SystemError::MaxSeedLengthExceeded);
Ok(Pubkey::new(
hashv(&[base.as_ref(), seed.as_ref(), program_id.as_ref()]).as_ref(),
))
create_program_address()
并无法保证可生成off ed25519 curve的有效program address,可采用find_program_address()
(可能会多次调用create_program_address()
)来确保生成的program address不在ed25519 curve上。
【根据 create_program_address()
和 find_program_address()
生成的地址无法与其它公钥进行区分。runtime区分某address是否属于某合约 的唯一方法为:由合约提供生成该address的seed。然后runtime内部会调用create_program_address()
,比对二者是否一样。】
/// Generate a derived program address
/// * seeds, symbolic keywords used to derive the key
/// * program_id, program that the address is derived for
pub fn create_program_address(
seeds: &[&[u8]],
program_id: &Pubkey,
) -> Result<Pubkey, PubkeyError>
/// Find a valid off-curve derived program address and its bump seed
/// * seeds, symbolic keywords used to derive the key
/// * program_id, program that the address is derived for
pub fn find_program_address(
seeds: &[&[u8]],
program_id: &Pubkey,
) -> Option<(Pubkey, u8)>
let mut bump_seed = [std::u8::MAX];
for _ in 0..std::u8::MAX
let mut seeds_with_bump = seeds.to_vec();
seeds_with_bump.push(&bump_seed);
if let Ok(address) = create_program_address(&seeds_with_bump, program_id)
return Some((address, bump_seed[0]));
bump_seed[0] -= 1;
None
3.3 program address应用举例
客户端使用find_program_address
来生成a destination address:
// find the escrow key and valid bump seed
let (escrow_pubkey2, escrow_bump_seed) = find_program_address(&[&["escrow2"]], &escrow_program_id);
// construct a transfer message using that key
let message = Message::new(vec![
token_instruction::transfer(&alice_pubkey, &escrow_pubkey2, 1),
]);
// process the message which transfer one 1 token to the escrow
client.send_and_confirm_message(&[&alice_keypair], &message);
合约内采用相同的规则来生成相同的destination program address:
fn transfer_one_token_from_escrow2(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult
// User supplies the destination
let alice_pubkey = keyed_accounts[1].unsigned_key();
// Iteratively derive the escrow pubkey
let (escrow_pubkey2, bump_seed) = find_program_address(&[&["escrow2"]], program_id);
// Create the transfer instruction
let instruction = token_instruction::transfer(&escrow_pubkey2, &alice_pubkey, 1);
// Include the generated bump seed to the list of all seeds
invoke_signed(&instruction, accounts, &[&["escrow2", &[bump_seed]]])
参考资料
以上是关于Solana中的跨合约调用 及 Program Derived Addresses的主要内容,如果未能解决你的问题,请参考以下文章