[Solana - Native vs Anchor] #1. Hello Solana
[Solana - Native vs Anchor] #1. Hello Solana 이 글은 Solana - Native vs Anchor 시리즈의 1번째 글입니다. [Solana - Native vs Anchor] 시리즈에서는 솔라나 프로그래밍을 하는 두 가지 방법에 대해 알아봅니다. 하나는 솔라나에서 지원하는 Rust Crate인 solana-program을 이용한 Native 프로그래밍 방식이고, 다른 하나는 솔라나 프로그래밍을 좀 더 편하게 해주는 Anchor 프레임워크를 이용한 방식입니다. 이 글은 Hello Solana 예제를 다루는 [Solana - Native vs Anchor] 시리즈의 첫 번째 글입니다. 향후 포스팅되는 다른 글들에서는 다양한 예제를 코드와 로직에 집중해 살펴보겠지만, 이 글에서는 프로그램을 빌드하고 솔라나 로컬넷에 배포하는 것까지도 살펴보겠습니다. 아래 그림은 프로그램 흐름의 큰 이해를 돕기 위해 직접 만든 것으로 절차나 구성 요소들을 정확히 표현하는 것은 아닙니다. 실제로는 더 상세한 내용들이 있는데, 예를 들어 Solana Runtime 구성 요소는 함축적인 의미로 표현됐습니다. 이 그림에는 RPC 엔드포인트가 존재하지 않으며, Runtime이 Instruction의 처리 결과를 검증하는 절차와 같은 것들이 표현되지 않았습니다. 실제 통신과 처리를 위한 요소들 대부분을 생략한 내용이니 자세한 사항은 솔라나 공식문서를 참고해주세요. 위 그림은 Native 코드를 기준으로 표현했으며, 초록색으로 표시한 것들이 실제 우리가 개발할 내용들입니다. 먼저, Server Program을 만들어 Solana 블록체인 네트워크에 배포합니다. 우리가 배포한 Program은 실행가능한 Account로 온체인에 저장됩니다. 그리고 클라이언트에서 ProgramId를 기준으로 Instruction들을 만듭니다. 하나 이상의 Instruction이 모여 Transaction을 구성하고, 우리는 Solana 블록체인 네트워크의 RPC 엔드포인트로 Transaction을 보냅니다. 그러면, Solana Native Program 중 하나인 BPF Loader가 우리 Program을 실행시킵니다. Server Program은 Solana 온체인에 배포되고 Solana Runtime에 의해 실행됩니다. Client가 RPC 엔드포인트를 통해 요청을 보내면 Solana Runtime은 ProgramId를 기준으로 Instruction을 Program에 보냅니다. 먼저, Native 프로그램의 코드를 살펴봅시다. Server Program인 Rust 프로그램에 solana-program 의존성을 추가해야 합니다. entrypoint! 매크로는 이 Program의 entrypoint로 process_instruction 함수를 지정합니다. entrypoint로 지정된 process_instruction 함수에서 "Hello Solana"와 현재 프로그램의 ProgramID를 출력합니다. Client Program에는 @solana/web3.js 의존성을 추가해줍니다. 이제 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들을 담고 있는 배열)을 인자로 넘깁니다. 이제 프로그램이 정상적으로 동작하는지 테스트합시다. 테스트를 위해 Rust로 만든 Server Program을 온체인에 배포해야하는데, 우리는 localnet을 사용할 것입니다. 만약 local로 설정되어 있지 않다면 local로 설정해 줍니다. 배포하기 전 solana localnet을 구동해야 합니다. solana-test-validator는 테스트를 위해 local환경에 솔라나 체인을 구동시켜줍니다. 이제 솔라나 프로그램을 local 온체인에 배포합니다. 이제 테스트만 남았습니다. Server Program이 잘 동작하는지 확인하면서, 테스트 코드를 실행하겠습니다. 새로운 터미널에서 입력하세요. solana logs를 통해 transaction log들을 확인할 수 있습니다. 이제 Client 코드를 실행해 테스트를 진행합시다. Clinet Test 결과 Server Program Log (Solana Transaction logs) 지금까지 Native 프로그래밍을 위한 Client와 Server코드, 빌드와 배포, 테스트에 대한 내용을 살펴봤습니다. 이제, 같은 내용을 Anchor 프레임워크를 이용해 만들어 보고 어떤 차이가 있는지 알아봅시다. Anchor를 설치하고 프로젝트를 생성하면 기본적인 의존성들이 자동으로 추가되어 있습니다. Anchor 프로젝트 안에 있는 program 폴더에서는 Server 프로그램을, tests 폴더에서는 Client Program을 만들 수 있습니다. Rust 프로그램을 완성하고 빌드하게 되면 모든 Instruction들이 명시된 idl 파일이 자동으로 생깁니다. Anchor는 이를 이용해서 Client가 Server Program과 통신할 때 사용할 ts 파일을 자동으로 생성해줍니다. 앞서 설명했듯이, Anchor 프로젝트를 생성하고 나면, 프로젝트 최상위 경로에 programs 폴더가 생깁니다. 이 programs 폴더 하위에 Rust 프로젝트가 위치하게 됩니다. Anchor 프로젝트는 여러 개의 Program을 가질 수 있습니다. Program을 빌드하면 Anchor가 프로그램의 keypair를 자동으로 생성해줍니다. 이때, 생성된 id(pubkey)를 declare_id!()에 설정할 것입니다. Program 모듈에 있는 메서드들은 첫 번째 인자로 Context를 받고, Context의 제네릭 타입으로 Hello struct를 받습니다. 이 예제에서 Hello 구조체는 현재 아무런 Account와 데이터를 갖고 있지 않습니다. hello 메서드는 인자로 받은 Hello 타입에 대한 처리를 하진 않지만, "Hello, Solana!"와 "현재 프로그램의 Program ID" 두 개의 메시지를 출력합니다. Anchor 프로젝트에서 Client 코드는 ts 파일로 tests 폴더 하위에 작성합니다. 프로젝트를 생성하면 tests 폴더에 기본적으로 ts 파일이 하나 생성되지만, 처음엔 import 할 HelloSolana를 찾을 수 없다고 나올 것입니다. 위에서 작성한 program을 빌드하면 hello_solana.ts 파일을 Anchor가 자동으로 생성해주며, import가 가능해집니다. program 변수는 위에서 만든 program에 대한 pubkey를 갖고 있고 식별자 역할을 해줍니다. hello Instruction은 현재 인자로 요구하는 Account가 없습니다. native 프로그래밍에서는 payer와 program_id에 대한 직접적인 명시가 필요했습니다. Anchor 프로젝트에서는 anchor.toml과 앞서 생성한 프로그램 전용 ts파일을 통해 내부적으로 처리해주기 때문에 직접 명시할 필요가 없습니다. 이제 Anchor 프로젝트로 작성한 코드들을 테스트해봅시다. 앞서 설명한 대로 먼저 프로그램을 빌드합니다. 빌드가 성공하면 ./target/deploy 경로에 프로그램의 keypair 파일이 생성됩니다. 아래 명령어를 통해 id(pubkey) 값을 확인할 수 있습니다. 생성된 program id를 declare_id!() 에 설정해주고, 다시 한번 빌드해줍니다. 우리는 이제 테스트할 준비를 마쳤습니다. 이때, Anchor 프레임워크는 자동으로 local에 test-validator 실행하기 때문에 solana-test-validator를 실행할 필요가 없습니다. 만약, solana-test-validator가 실행 중이라면 종료해주세요. Anchor 프레임워크를 이용한 코드는 native 프로그램의 코드와 비교해 상당히 간결해진 것을 볼 수 있습니다. 테스트하기 위한 환경이 잘 구성돼있어 개발 편의성을 많이 올려줍니다. 전체 목차:Hello Solana
구조

Client Program
Server Program
Native 프로그래밍
Server 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(())
}Client Program
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]
);
});
});Deploy
1. 먼저, Solana CLI에서 RPC 설정이 localhost인지 확인합니다.
solana config get
solana config set --url http://127.0.0.1:88992. 터미널에서 solana-test-validator를 실행합니다.
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. 프로그램 배포
solana program deploy ./program/target/so/program.so
배포에 성공하면 Program Id가 출력되는 것을 볼 수 있습니다. 5. 온체인에서 배포 결과 확인하기
solana program show <Program Id>
Test
solana logsnpm test
로그 메시지 내용을 보면 "Hello Solana" 문자열과 우리가 배포한 프로그램 ID를 확인할 수 있습니다.Anchor 프로그래밍
Server 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 {}Client Program
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();
});
});Test
anchor buildanchor keys list
Anchor 프로젝트에서는 아래 명령어를 통해 간단하게 테스트를 진행할 수 있습니다.anchor test
References