목차
- Account 생성하기
- 프로그램 구조
- Native 프로그래밍
- Anchor 프로그래밍
Account 생성하기
이전 포스팅인 [Solana - Native vs Anchor] Account 생성하기에서 Account 하나를 생성하기 위해 Client에서 System Program으로 곧바로 생성을 요청했습니다. 이번 글 "Account 생성하기"에서는 Client가 Server Program으로 Account 생성을 요청하고, Server Program에서 System Program의 create_account
Instruction을 호출해 Account를 생성하는 과정을 살펴보겠습니다.
이런 방법은 Solana에서 CPI(Cross-Program Invocation)라고 불립니다. CPI는 하나의 Program이 다른 Program의 Instruction을 호출하는 것을 말합니다. 이러면 Client 입장에서 하나의 Instruction으로 여러 작업을 처리를 할 수 있게 됩니다.
Account 생성하기는 우리 Server Program이 System Program을 호출하는 CPI의 한 예를 설명합니다.
프로그램 구조
Client Program은 하나의 Instruction을 갖는 Transaction을 보냅니다. 이 Instruction이 갖는 keys
에는 payer
와 System Program
과 함께 새롭게 생성할 Account인 newKey
가 포함되어 있습니다.
Server Program은 Client로부터 3개의 Account를 받습니다. newKey
Account를 생성하기 위해 System Program을 호출할 때 이 3개의 Account 정보를 이용합니다.
Native 프로그래밍
Native 프로그래밍을 먼저 살펴봅시다.
Server Program
entrypoint!(process_instruction);
pub fn process_instruction(
_program_id: &Pubkey,
accounts: &[AccountInfo],
_instruction_data: &[u8],
) -> ProgramResult {
let account_iter = &mut accounts.iter();
let payer = next_account_info(account_iter)?;
let new_account = next_account_info(account_iter)?;
let system_program = next_account_info(account_iter)?;
msg!("Program 호출됨. System Account 만드는 중...");
msg!("새로운 Public Key : {}", &new_account.key.to_string());
invoke(
&system_instruction::create_account(
&payer.key,
&new_account.key,
1 * LAMPORTS_PER_SOL,
0,
&system_program::ID,
),
&[
payer.clone(), new_account.clone(), system_program.clone()
],
)?;
msg!("Account created successfully.");
Ok(())
}
Solana에서 Account의 생성은 System Program이 담당하고, 일반적인 Account는 모두 System Program이 소유합니다. 따라서, Server Program은 새로운 Account를 생성하기 위해 invoke
함수를 통해 System Program의 Instruction을 호출합니다.
invoke
: 다른 Program의 Instruction을 호출합니다.system_instruction::create_account()
: Account를 생성하기 위해 System Program으로 보낼 Instruction을 만듭니다.account_infos
: Instruction을 받을 프로그램이 처리를 위해 필요로 하는 모든 Account들입니다.
Client Program
describe("Create a system account", async () => {
const connection = new Connection(`http://127.0.0.1:8899`, 'confirmed');
const payer = createKeypairFromFile(require('os').homedir() + '/.config/solana/id.json');
const PROGRAM_ID: PublicKey = new PublicKey("6Kv8wFQtRT8XeQd4sR4jQQefwL1oJC6xE36r6U1oRJ7Z");
it("Create the account", async () => {
const newKeypair = Keypair.generate();
let ix = new TransactionInstruction({
keys: [
{pubkey: payer.publicKey, isSigner: true, isWritable: true},
{pubkey: newKeypair.publicKey, isSigner: true, isWritable: true},
{pubkey: SystemProgram.programId, isSigner: false, isWritable: false}
],
programId: PROGRAM_ID,
data: Buffer.alloc(0),
});
await sendAndConfirmTransaction(
connection,
new Transaction().add(ix),
[payer, newKeypair]
);
});
});
Client Program은 이전 포스팅들에서 설명했던 내용과 크게 다르지 않습니다. Server Program으로 보낼 Instruction을 생성할 때 payer
, newKeypair
, SystemProgram
세 개의 pubkey를 포함합니다. 이렇게 만들어진 하나의 Instruction을 Transaction에 담아 보냅니다.
Test
테스트 결과 성공적으로 Account가 생성된 것을 볼 수 있습니다.
우리가 만든 Account의 Pubkey는 AGxcvfNVekDSParpMZiPihjJyazs2vm8Y1ZB52gZ6RSS
입니다.
생성된 결과를 아래와 같이 Solana Explorer를 통해서도 확인할 수 있습니다.
Anchor 프로그래밍
다음은 Anchor 프로그래밍을 살펴봅시다.
Server Program
declare_id!("6KULEpud8tcYLZxrw2KUb1APZTeLCLdKtp2fhvVKQfqQ");
const LAMPORTS_PER_SOL: u64 = 1000000000;
#[program]
pub mod create_account {
use super::*;
pub fn create_system_account(ctx: Context<CreateSystemAccount>) -> Result<()> {
msg!("Program이 호출되었습니다. System Account 생성중...");
msg!(" 새로운 Public Key : {}", &ctx.accounts.new_account.key().to_string());
system_program::create_account(
CpiContext::new(
ctx.accounts.system_program.to_account_info(),
system_program::CreateAccount {
from: ctx.accounts.payer.to_account_info(), // From Pubkey
to: ctx.accounts.new_account.to_account_info(), // To Pubkey
},
),
1 * LAMPORTS_PER_SOL, // Lamports (1 SOL)
0, // Space
&ctx.accounts.system_program.key(), // Owner
)?;
msg!("Account가 생성되었습니다.");
Ok(())
}
}
#[derive(Accounts)]
pub struct CreateSystemAccount<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(mut)]
pub new_account: Signer<'info>,
pub system_program: Program<'info, System>,
}
- CreateSystemAccount
:payer
,new_account
,system_program
총 3개의 Account를 갖고 있는 Struct. - system_program::create_account()
: 이 함수는 Native 프로그래밍에서 사용한 것과는 다른 solana_program을 감싸고 있는 anchor_lang이 갖고 있는 함수입니다. 내부에서 Native 프로그래밍에서 사용했던 것과 같은 solana_program::system_instruction을 호출하고 있습니다. - CpiContext
: 호출할 Program과 처리에 필요한 account들을 담고 있는 CpiContext 정보입니다. Anchor에서는 CPI 호출을 위해 CpiContext를 사용합니다.
Client Program
describe("create-account", () => {
// Configure the client to use the local cluster.
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const wallet = provider.wallet as anchor.Wallet;
const program = anchor.workspace.CreateAccount as anchor.Program<CreateAccount>;
it("Account 생성하기", async () => {
const newKeypair = anchor.web3.Keypair.generate();
await program.methods.createSystemAccount()
.accounts({
payer: wallet.publicKey,
newAccount: newKeypair.publicKey,
systemProgram: anchor.web3.SystemProgram.programId
})
.signers([wallet.payer, newKeypair])
.rpc();
});
});
Anchor 프로그램은 역시 자동으로 생성된 ts 파일을 통해 편하게 Server Program과 통신할 수 있게 도와줍니다.
- methods.createSystemAccount()
: Server Program에서 정의한create_system_account()
함수를 호출합니다. - accounts()
: payer와 systemProgram의 pubkey를 입력해주고, 새롭게 생성할 Account의 pubkey를 추가해줍니다. - signers()
: 위 Account 목록 중 SystemProgram을 제외한 payer와 newAccount를 서명자로 넣어줍니다.
Test
References
'Dev > Blockchain' 카테고리의 다른 글
[Metaplex Candy Machine] #2. Candy Machine 관리하기 (0) | 2023.04.13 |
---|---|
[Metaplex Candy Machine] #1. Candy Machine 설정 이해하기 (0) | 2023.04.12 |
[Solana - Native vs Anchor] #2. Account 확인하기 (0) | 2022.10.20 |
[Solana - Native vs Anchor] #1. Hello Solana (0) | 2022.10.14 |
[Solana 이해하기] Program (1) | 2022.10.03 |