목차
- Hello Solana
- 프로그램 구조
- Native 프로그래밍
- Anchor 프로그래밍
Hello Solana
[Solana - Native vs Anchor] 시리즈에서는 솔라나 프로그래밍을 하는 두 가지 방법에 대해 알아봅니다.
하나는 솔라나에서 지원하는 Rust Crate인 solana-program을 이용한 Native 프로그래밍 방식이고,
다른 하나는 솔라나 프로그래밍을 좀 더 편하게 해주는 Anchor 프레임워크를 이용한 방식입니다.
이 글은 Hello Solana 예제를 다루는 [Solana - Native vs Anchor] 시리즈의 첫 번째 글입니다.
향후 포스팅되는 다른 글들에서는 다양한 예제를 코드와 로직에 집중해 살펴보겠지만,
이 글에서는 프로그램을 빌드하고 솔라나 로컬넷에 배포하는 것까지도 살펴보겠습니다.
Account에 대한 개념은 여기, Program에 대한 개념은 여기에서 보실 수 있습니다.
전체 코드나 설정 파일과 같은 상세한 내용들은 References를 참고해주세요.
구조
아래 그림은 프로그램 흐름의 큰 이해를 돕기 위해 직접 만든 것으로 절차나 구성 요소들을 정확히 표현하는 것은 아닙니다. 실제로는 더 상세한 내용들이 있는데, 예를 들어 Solana Runtime 구성 요소는 함축적인 의미로 표현됐습니다. 이 그림에는 RPC 엔드포인트가 존재하지 않으며, Runtime이 Instruction의 처리 결과를 검증하는 절차와 같은 것들이 표현되지 않았습니다. 실제 통신과 처리를 위한 요소들 대부분을 생략한 내용이니 자세한 사항은 솔라나 공식문서를 참고해주세요.
위 그림은 Native 코드를 기준으로 표현했으며, 초록색으로 표시한 것들이 실제 우리가 개발할 내용들입니다.
Client Program
- hello instruction
- Keys : 전송할 Account들을 의미합니다. Hello Solana 에제에는 어떠한 Account도 필요하지 않아 Payer만 설정합니다.
- Payer : 이 Instruction에 대한 비용을 지불하는 Account입니다.
- ProgramId : 이 Instruction을 실행할 Program의 Id(Pubkey)입니다.
- Transaction A : Transaction은 하나 이상의 Instruction으로 구성할 수 있지만, 현재 hello instruction 하나만 담고 있습니다.
- sendAndConfirmTransaction : sendAndConfirmTransaction은 Transaction 처리 결과를 기다립니다.
- conntection : Solana의 JSON RPC endpoint와 연결하는 객체입니다. 여기선 localnet에서 테스트합니다.
- Transaction A : 앞서 설명한 내용
- Signers : Transaction A에 있는 Instruction에 서명하는 모든 KeyPair 객체들이 들어갑니다.
먼저, Server Program을 만들어 Solana 블록체인 네트워크에 배포합니다. 우리가 배포한 Program은 실행가능한 Account로 온체인에 저장됩니다. 그리고 클라이언트에서 ProgramId를 기준으로 Instruction들을 만듭니다. 하나 이상의 Instruction이 모여 Transaction을 구성하고, 우리는 Solana 블록체인 네트워크의 RPC 엔드포인트로 Transaction을 보냅니다. 그러면, Solana Native Program 중 하나인 BPF Loader가 우리 Program을 실행시킵니다.
Server Program
- msg!("Hello Solana") : *"Hello Solana"*를 출력합니다.
Server Program은 Solana 온체인에 배포되고 Solana Runtime에 의해 실행됩니다.
Client가 RPC 엔드포인트를 통해 요청을 보내면 Solana Runtime은 ProgramId를 기준으로 Instruction을 Program에 보냅니다.
먼저, Native 프로그램의 코드를 살펴봅시다.
Native 프로그래밍
Server Program
Server Program인 Rust 프로그램에 solana-program
의존성을 추가해야 합니다.
use solana_program::{
account_info::AccountInfo,
entrypoint,
entrypoint::ProgramResult,
msg,
pubkey::Pubkey,
};
entrypoint!(process_instruction);
fn process_instruction(
program_id: &Pubkey,
_accounts: &[AccountInfo],
_instruction_data: &[u8],
) -> ProgramResult {
msg!("Hello Solana");
msg!("현재 프로그램의 Program ID: {}", &program_id);
Ok(())
}
entrypoint!
매크로는 이 Program의 entrypoint로 process_instruction
함수를 지정합니다. entrypoint로 지정된 process_instruction
함수에서 "Hello Solana"와 현재 프로그램의 ProgramID를 출력합니다.
Client Program
Client Program에는 @solana/web3.js
의존성을 추가해줍니다.
import {
Connection,
Keypair,
sendAndConfirmTransaction,
Transaction,
TransactionInstruction,
} from '@solana/web3.js';
function createKeypairFromFile(path: string): Keypair {
return Keypair.fromSecretKey(
Buffer.from(JSON.parse(require('fs').readFileSync(path, "utf-8")))
)
};
describe("hello-solana", () => {
const connection = new Connection(`http://127.0.0.1:8899`, 'confirmed');
const payer = createKeypairFromFile(require('os').homedir() + '/.config/solana/id.json');
const program = createKeypairFromFile('./program/target/so/program-keypair.json');
it("Say hello!", async () => {
let ix = new TransactionInstruction({
keys: [
{
pubkey: payer.publicKey,
isSigner: true,
isWritable: true
}
],
programId: program.publicKey,
data: Buffer.alloc(0), // No data
});
await sendAndConfirmTransaction(
connection,
new Transaction().add(ix), // Add our instruction (you can add more than one)
[payer]
);
});
});
- createKeypairFromFile 함수는 로컬 지갑과 Rust Program의 json key 파일을 KeyPair 객체로 만들어 줍니다.
- connection 변수는 local rpc endpoint 주소로 설정하여 생성합니다.
- payer는 이 Transaction에 비용을 지불할 KeyPair 객체입니다.
- program은 Instruction을 보낼 Program의 KeyPair 객체입니다.
이제 Client 로직을 구현합시다.
먼저 하나의 Instruction을 의미하는 TransactionInstruction 객체를 만들기 위해서는 keys, programId, data 3개로 구성된 객체가 필요합니다. keys에는 이 Instruction에 필요한 모든 Account들을 넣습니다. 여기서는 payer Account 하나입니다. programId는 Instruction을 처리할 Program의 ID(Pubkey)입니다. data는 Instruction에 보낼 Account가 아닌 데이터를 의미합니다.
마지막으로 만든 데이터를 보내기 위해 sendAndConfirmTransaction 함수에 connection, transaction, Signers(KeyPair들을 담고 있는 배열)을 인자로 넘깁니다.
Deploy
이제 프로그램이 정상적으로 동작하는지 테스트합시다.
테스트를 위해 Rust로 만든 Server Program을 온체인에 배포해야하는데, 우리는 localnet을 사용할 것입니다.
1. 먼저, Solana CLI에서 RPC 설정이 localhost인지 확인합니다.
solana config get
만약 local로 설정되어 있지 않다면 local로 설정해 줍니다.
solana config set --url http://127.0.0.1:8899
2. 터미널에서 solana-test-validator
를 실행합니다.
배포하기 전 solana localnet을 구동해야 합니다.
solana-test-validator
는 테스트를 위해 local환경에 솔라나 체인을 구동시켜줍니다.
solana-test-validator
3. 이제, Server Program을 배포하기 위해 빌드합니다.
cargo build-bpf --manifest-path=./program/Cargo.toml --bpf-out-dir=./program/target/so
빌드가 성공했다면 --bpf-out-dir
에 설정된 경로에 so 파일이 생성됩니다.
4. 프로그램 배포
이제 솔라나 프로그램을 local 온체인에 배포합니다.
solana program deploy ./program/target/so/program.so
배포에 성공하면 Program Id가 출력되는 것을 볼 수 있습니다.
5. 온체인에서 배포 결과 확인하기
solana program show <Program Id>
Test
이제 테스트만 남았습니다. Server Program이 잘 동작하는지 확인하면서, 테스트 코드를 실행하겠습니다.
새로운 터미널에서 입력하세요.
solana logs
solana logs를 통해 transaction log들을 확인할 수 있습니다.
이제 Client 코드를 실행해 테스트를 진행합시다.
npm test
Clinet Test 결과
Server Program Log (Solana Transaction logs)
로그 메시지 내용을 보면 "Hello Solana" 문자열과 우리가 배포한 프로그램 ID를 확인할 수 있습니다.
지금까지 Native 프로그래밍을 위한 Client와 Server코드, 빌드와 배포, 테스트에 대한 내용을 살펴봤습니다.
이제, 같은 내용을 Anchor 프레임워크를 이용해 만들어 보고 어떤 차이가 있는지 알아봅시다.
Anchor 프로그래밍
Anchor를 설치하고 프로젝트를 생성하면 기본적인 의존성들이 자동으로 추가되어 있습니다. Anchor 프로젝트 안에 있는 program 폴더
에서는 Server 프로그램을, tests 폴더
에서는 Client Program을 만들 수 있습니다. Rust 프로그램을 완성하고 빌드하게 되면 모든 Instruction들이 명시된 idl 파일이 자동으로 생깁니다. Anchor는 이를 이용해서 Client가 Server Program과 통신할 때 사용할 ts 파일을 자동으로 생성해줍니다.
Anchor 프레임워크에 대해 더 자세한 내용은 공식 홈페이지를 참고해주세요.
Server Program
앞서 설명했듯이, Anchor 프로젝트를 생성하고 나면, 프로젝트 최상위 경로에 programs
폴더가 생깁니다. 이 programs
폴더 하위에 Rust 프로젝트가 위치하게 됩니다. Anchor 프로젝트는 여러 개의 Program을 가질 수 있습니다.
use anchor_lang::prelude::*;
declare_id!("GU1JMpSwbsXHANv8Voas6JCjQwWgRDhL72XRoJu4c3oC");
#[program]
pub mod hello_solana {
use super::*;
pub fn hello(_ctx: Context<Hello>) -> Result<()> {
msg!("Hello, Solana!");
msg!("현재 프로그램의 Program ID: {}", &id());
Ok(())
}
}
#[derive(Accounts)]
pub struct Hello {}
- declare_id!("") : 현재 프로그램의 ID(Pubkey)를 지정합니다.
- #[program] : 이 Program 모듈에 메서드 별로 Instruction 비지니스 로직을 구현합니다.
- #[derive(Accounts)] : Instruction 모듈에서 사용할 Account들과 데이터를 정의합니다.
Program을 빌드하면 Anchor가 프로그램의 keypair를 자동으로 생성해줍니다. 이때, 생성된 id(pubkey)를 declare_id!()
에 설정할 것입니다. Program 모듈에 있는 메서드들은 첫 번째 인자로 Context를 받고, Context의 제네릭 타입으로 Hello struct를 받습니다.
이 예제에서 Hello 구조체는 현재 아무런 Account와 데이터를 갖고 있지 않습니다. hello 메서드는 인자로 받은 Hello 타입에 대한 처리를 하진 않지만, "Hello, Solana!"와 "현재 프로그램의 Program ID" 두 개의 메시지를 출력합니다.
Client Program
Anchor 프로젝트에서 Client 코드는 ts 파일로 tests
폴더 하위에 작성합니다. 프로젝트를 생성하면 tests
폴더에 기본적으로 ts 파일이 하나 생성되지만, 처음엔 import 할 HelloSolana를 찾을 수 없다고 나올 것입니다. 위에서 작성한 program을 빌드하면 hello_solana.ts
파일을 Anchor가 자동으로 생성해주며, import가 가능해집니다.
import * as anchor from "@project-serum/anchor";
import { Program } from "@project-serum/anchor";
import { HelloSolana } from "../target/types/hello_solana";
describe("hello-solana", () => {
// Configure the client to use the local cluster.
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.HelloSolana as Program<HelloSolana>;
it("Say Hello!", async () => {
// Add your test here.
const tx = await program.methods
.hello()
.accounts({})
.rpc();
});
});
program 변수는 위에서 만든 program에 대한 pubkey를 갖고 있고 식별자 역할을 해줍니다. hello Instruction은 현재 인자로 요구하는 Account가 없습니다.
native 프로그래밍에서는 payer와 program_id에 대한 직접적인 명시가 필요했습니다. Anchor 프로젝트에서는 anchor.toml
과 앞서 생성한 프로그램 전용 ts파일을 통해 내부적으로 처리해주기 때문에 직접 명시할 필요가 없습니다.
Test
이제 Anchor 프로젝트로 작성한 코드들을 테스트해봅시다. 앞서 설명한 대로 먼저 프로그램을 빌드합니다.
프로젝트 최상위 경로에서 실행하세요.
anchor build
빌드가 성공하면 ./target/deploy
경로에 프로그램의 keypair 파일이 생성됩니다.
아래 명령어를 통해 id(pubkey) 값을 확인할 수 있습니다.
anchor keys list
생성된 program id를 declare_id!()
에 설정해주고, 다시 한번 빌드해줍니다.
우리는 이제 테스트할 준비를 마쳤습니다.
Anchor 프로젝트에서는 아래 명령어를 통해 간단하게 테스트를 진행할 수 있습니다.
anchor test
이때, Anchor 프레임워크는 자동으로 local에 test-validator 실행하기 때문에 solana-test-validator
를 실행할 필요가 없습니다. 만약, solana-test-validator
가 실행 중이라면 종료해주세요.
Anchor 프레임워크를 이용한 코드는 native 프로그램의 코드와 비교해 상당히 간결해진 것을 볼 수 있습니다. 테스트하기 위한 환경이 잘 구성돼있어 개발 편의성을 많이 올려줍니다.
References
'Dev > Blockchain' 카테고리의 다른 글
[Metaplex Candy Machine] #1. Candy Machine 설정 이해하기 (0) | 2023.04.12 |
---|---|
[Solana - Native vs Anchor] #3. Account 생성하기 (0) | 2022.10.21 |
[Solana - Native vs Anchor] #2. Account 확인하기 (0) | 2022.10.20 |
[Solana 이해하기] Program (1) | 2022.10.03 |
[Solana 이해하기] Account (0) | 2022.10.01 |