ソラナ:命令データを通してカスタム命令を送る方法


この記事では、我々はチェーンプログラム上のソラナにカスタム命令を送信するプロセスを歩いていきます.ソラナを変更しますexample helloworld 2つの命令を取る.SayHello and SayGoodbye .
最後の結果を見てみましょう.

始めましょう.ここから拾うhelloworld チュートリアル私たちを残します.

プログラムのAPIを定義する
// instruction.rs
#[derive(Debug)]
pub enum HelloInstruction {
    SayHello,
    SayBye,
}
私たちはenum それは我々のプログラムが実行できる操作を含みます.instruction.rs デコードの原因instruction_data .

デコードinstruction_data
// instruction.rs
+use solana_program::{program_error::ProgramError};
+
#[derive(Debug)]
pub enum HelloInstruction {
    SayHello,
    SayBye,
}

+impl HelloInstruction {
+    pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
+        let (&tag, rest) = input
+            .split_first()
+            .ok_or(ProgramError::InvalidInstructionData)?;
+
+        Ok(match tag {
+            0 => HelloInstruction::SayHello,
+            1 => HelloInstruction::SayBye,
+            _ => return Err(ProgramError::InvalidInstructionData),
+        })
+    }
+}
関連関数を作成しますunpackinstruction_data を返します.HelloInstruction またはProgramError .
// --snip--
let (&tag, rest) = input
    .split_first()
    .ok_or(ProgramError::InvalidInstructionData)?;
// --snip--
私たちはinstruction_data を含むタプルをスライスして受け取る&tag これが最初の要素でありrest 残りのバイトはどれですか.&tag (数値/U 8範囲;0〜255)は1:1と演算される.これはクライアント上の命令を作成するときにより明確になります.rest は、操作が必要となる追加情報を含む残りのバイト数を持つ.
我々はOption with .ok_or(ProgramError::InvalidInstructionData)?;Result , if the OptionNone variantを返しますInvalidInstructionData エラーです.もっと見るok_or メソッド.
Ok(match tag {
    0 => HelloInstruction::SayHello,
    1 => HelloInstruction::SayBye,
    _ => return Err(ProgramError::InvalidInstructionData),
})
私たちは今ではtag どのような操作を知っているプログラムを実行する必要があります.既知の命令が一致しない場合、エラーを返します.

更新process_instruction
// lib.rs
// --snip--
+pub mod instruction;
+use crate::instruction::HelloInstruction;
// --snip--

pub fn process_instruction(
    program_id: &Pubkey, // Public key of the account the hello world program was loaded into
    accounts: &[AccountInfo], // The account to say hello to
    instruction_data: &[u8],
) -> ProgramResult {
    msg!("Hello World Rust program entrypoint");
+
+   let instruction = HelloInstruction::unpack(instruction_data)?;
+   msg!("Instruction: {:?}", instruction);

    // Iterating accounts is safer then indexing
    let accounts_iter = &mut accounts.iter();

    // Get the account to say hello to
    let account = next_account_info(accounts_iter)?;

    // The account must be owned by the program in order to modify its data
    if account.owner != program_id {
        msg!("Greeted account does not have the correct program id");
        return Err(ProgramError::IncorrectProgramId);
    }

    // Increment and store the number of times the account has been greeted
    let mut greeting_account = GreetingAccount::try_from_slice(&account.data.borrow())?;
    greeting_account.counter += 1;
    greeting_account.serialize(&mut &mut account.data.borrow_mut()[..])?;

    msg!("Greeted {} time(s)!", greeting_account.counter);

    Ok(())
}
我々は、我々HelloInstructionuse crate::instruction::HelloInstruction; と命令をデコードしようとするlet instruction = HelloInstruction::unpack(instruction_data)?;今のところ、我々はちょうど命令を記録します.後でこの命令を使用するためにコードを変更します.
我々がこれまでにしたことの速い要約:
  • 我々のプログラムAPIを定義しましたHelloInstruction enum
  • 未パックのinstruction_data クライアントからの操作を取得するバイト配列
  • デコードされた命令を記録した
  • ましょうbuild and re-deploy プログラム.あなたがこれをする方法がわからないならば、見てくださいexample helloworld docs . 我々は現在、クライアントを実行することができますし、ログメッセージが表示されます参照してください.

    エラーが発生しました:Program log: Instruction Err(InvalidInstructionData) 我々のクライアントが有効な指示を通過していないので.それを直しましょう.

    更新sayHello
    // hello_world.ts
    // --snip --
    
    /**
     * Say hello
     */
    export async function sayHello(): Promise<void> {
      console.log('Saying hello to', greetedPubkey.toBase58());
      const instruction = new TransactionInstruction({
        keys: [{pubkey: greetedPubkey, isSigner: false, isWritable: true}],
        programId,
    -   data: Buffer.alloc(0),
    +   data: createSayHelloInstructionData(),
      });
      await sendAndConfirmTransaction(
        connection,
        new Transaction().add(instruction),
        [payer],
      );
    }
    
    私たちは呼び出しをBuffer.alloc(0) 新しい機能でcreateSayHelloInstructionData() を返します.Buffer .

    クリエイトcreateSayHelloInstructionDataこのために二つのライブラリをインストールする必要があります.
    npm i @solana/buffer-layout buffer
    
    // hello_world.ts
    
    import * as BufferLayout from '@solana/buffer-layout';
    import { Buffer } from 'buffer';
    
    // --snip--
    
    function createSayHelloInstructionData(): Buffer {
      const dataLayout = BufferLayout.struct([
        BufferLayout.u8('instruction')
      ]);
    
      const data = Buffer.alloc(dataLayout.span);
      dataLayout.encode({
        instruction: 0
      }, data);
    
      return data;
    }
    
    つのフィールドを持つバッファレイアウト構造を作成しますinstruction . これは、我々が我々のプログラムで実行する操作をエンコードする場所ですu8 .
    const data = Buffer.alloc(dataLayout.span);
    
    次に、以前に作成したバッファレイアウトからサイズを使用して新しいバッファーを割り当てます.
    dataLayout.encode({
      instruction: 0
    }, data);
    
    最後に、命令をエンコードします.instruction: 0 ) を返します.
    Th instruction: 0 に対応するtag の最初の要素で取得する変数instruction_data スライス.
    // --snip--
    let (&tag, rest) = input
        .split_first()
        .ok_or(ProgramError::InvalidInstructionData)?;
    
        Ok(match tag {
            0 => HelloInstruction::SayHello,
            1 => HelloInstruction::SayBye,
    
    // --snip--
    
    今私たちのクライアントを実行すると、我々の指示が正しくデコードされていることがわかります.

    正しい操作を行います.Program log: Instruction Ok(SayHello) .
    すごい!もう少しで済んだ.を作成するcreateSayByeInstructionData 関数.これはcreateSayHelloInstructionData 関数は、我々が送信する必要がある命令を除いて.
    今、私たちがプログラムを変更しましょうSayBye 操作.

    更新process_instruction
    pub fn process_instruction(
        program_id: &Pubkey, // Public key of the account the hello world program was loaded into
        accounts: &[AccountInfo], // The account to say hello to
        instruction_data: &[u8], // Ignored, all helloworld instructions are hellos
    ) -> ProgramResult {
        msg!("Hello World Rust program entrypoint");
        let instruction = HelloInstruction::unpack(instruction_data)?;
        msg!("Instruction {:?}", instruction);
    
        // Iterating accounts is safer then indexing
        let accounts_iter = &mut accounts.iter();
    
        // Get the account to say hello to
        let account = next_account_info(accounts_iter)?;
    
        // The account must be owned by the program in order to modify its data
        if account.owner != program_id {
            msg!("Greeted account does not have the correct program id");
            return Err(ProgramError::IncorrectProgramId);
        }
    
        // Increment and store the number of times the account has been greeted
        let mut greeting_account = GreetingAccount::try_from_slice(&account.data.borrow())?;
    +
    +   match instruction {
    +       HelloInstruction::SayHello => {
                greeting_account.counter += 1;
    +       },
    +       HelloInstruction::SayBye => {
    +           greeting_account.counter -= 1;
    +       },
    +       _ => {}
    +   }
    +
        greeting_account.serialize(&mut &mut account.data.borrow_mut()[..])?;
    
        msg!("Greeted {} time(s)!", greeting_account.counter);
    
        Ok(())
    }
    
    今、我々は我々のプログラムを変えました.我々は再び構築し、それを展開する必要があります.
    お客様を経営しましょう.

    クライアントの複数回を実行すると、グリーティングカウントがインクリメントされます.
    Aを送りましょうSayBye 操作.
    // --snip--
    export async function sayHello(): Promise<void> {
      console.log('Saying hello to', greetedPubkey.toBase58());
      const instruction = new TransactionInstruction({
        keys: [{pubkey: greetedPubkey, isSigner: false, isWritable: true}],
        programId,
    -   data: createSayHelloInstructionData(),
    +   data: createSayByeInstructionData(),
      });
      await sendAndConfirmTransaction(
        connection,
        new Transaction().add(instruction),
        [payer],
      );
    }
    
    function createSayByeInstructionData(): Buffer {
      const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]);
    
      const data = Buffer.alloc(dataLayout.span);
      dataLayout.encode(
        {
          instruction: 1,
        },
        data,
      );
    
      return data;
    }
    
    我々は再びクライアントを実行します.

    我々のプログラムはSayBye 操作と我々の挨拶数を減らす.
    おめでとう!我々は、プログラムがどのような操作を実行すべきかを指定するために、我々のプログラムを変更することができました.
    ソラナの開発についてもっと読むために私に従ってください.