Solanaでトランザクション送信すると「custom program error: 0x1b」エラー

SolanaとMetaplexを使って、トランザクションのパフォーマンスチューニングをしていたときに発生した現象。

現象

connection.sendRawTransaction すると以下のエラー。

     Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 2: custom program error: 0x1b

コードの中身は、Metaplexでinstructionsを出力したあとに、無理やりtokenOwnerを変更しても動くのか、といった検証したもの。
その際に、Messageをシリアライズなどの処理をしている。挙動は以下のとおり。

Solana Cookbook オフライントランザクション

原因

Message内のaccountKeys に今回指定したtokenOwnerがないため。
たとえば、トランザクション送信直前のinstructionsは以下のようになるが、accountKeysに存在していないのが原因。

Transaction {
  signatures: [],
  feePayer: PublicKey [PublicKey(HXtBm8XZbxaTt41uqaKhwUAa6Z1aPyvJdsZVENiWsetg)] {
    _bn: <BN: f5a44a6f36839611711f04149f51dd406dd4bc52cb86f20dd2b11608a62c7ee9>
  },
  instructions: [
    TransactionInstruction {
      keys: [Array],
      programId: [PublicKey [PublicKey(11111111111111111111111111111111)]],
      data: <Buffer 04 00 00 00>
    },
    TransactionInstruction {
      keys: [Array],
      programId: [PublicKey [PublicKey(metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s)]],
      data: <Buffer 2a 00 06 00 00 00 4d 79 20 4e 46 54 00 00 00 00 3f 00 00 00 68 74 74 70 73 3a 2f 2f 61 72 77 65 61 76 65 2e 6e 65 74 2f 72 5a 79 78 4e 43 6c 47 58 39 ... 93 more bytes>
    },
    TransactionInstruction {
      keys: [Array],
      programId: [PublicKey [PublicKey(metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s)]],
      data: <Buffer 2b 00 01 00 00 00 00 00 00 00 00>
    }
  ],
  recentBlockhash: '6Ndz45p8aiLu1nGe7PDmX45nBeYHmZ6iMM5MkGm5V2Bm',
  lastValidBlockHeight: undefined,
  nonceInfo: undefined,
  minNonceContextSlot: undefined,
  _message: Message {
    header: {
      numRequiredSignatures: 3,
      numReadonlySignedAccounts: 1,
      numReadonlyUnsignedAccounts: 7
    },
    accountKeys: [
      [PublicKey [PublicKey(HXtBm8XZbxaTt41uqaKhwUAa6Z1aPyvJdsZVENiWsetg)]],
      [PublicKey [PublicKey(8hwwwTNZt9th6yhyGF82htckmx39Lo7A5XS67kHWXgtb)]],
      [PublicKey [PublicKey(8ahaDBb5BwpViRvYKrggmCE2J4QPevnbVXFN5b7L6tN8)]],
      [PublicKey [PublicKey(22CGnTzzmnHtkNuRFuAeRMhs7fpTncPzkCHHiQtGrrXi)]],
      [PublicKey [PublicKey(6mjFGc63HMKrJoNEXaSoGkPsBESaanAQT2m2J4Vf6z1p)]],
      [PublicKey [PublicKey(AnKqMoEeHz5hEhKHoGfHG4ox9AR7ikNoj2WQSU5wVCZq)]],
      [PublicKey [PublicKey(FKW1BEigmcrWNusNJB4uNYANkgBHRgNyApBxj3Mnfc9S)]],
      [PublicKey [PublicKey(StTr2F4yaffe7HDZGNJ35a8GVoTy1GnJ7Dm8kcA3s2z)]],
      [PublicKey [PublicKey(11111111111111111111111111111111)]],
      [PublicKey [PublicKey(ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL)]],
      [PublicKey [PublicKey(auth9SigNpDKz4sJJ1DfCTuZrZNSAgh9sFD3rboVmgg)]],
      [PublicKey [PublicKey(metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s)]],
      [PublicKey [PublicKey(Sysvar1nstructions1111111111111111111111111)]],
      [PublicKey [PublicKey(SysvarRecentB1ockHashes11111111111111111111)]],
      [PublicKey [PublicKey(TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA)]]
    ],
    recentBlockhash: '6Ndz45p8aiLu1nGe7PDmX45nBeYHmZ6iMM5MkGm5V2Bm',
    instructions: [ [Object], [Object], [Object] ],
    indexToProgramIds: Map(2) {
      8 => [PublicKey [PublicKey(11111111111111111111111111111111)]],
      11 => [PublicKey [PublicKey(metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s)]]
    }
  },
  _json: {
    recentBlockhash: '6Ndz45p8aiLu1nGe7PDmX45nBeYHmZ6iMM5MkGm5V2Bm',
    feePayer: 'HXtBm8XZbxaTt41uqaKhwUAa6Z1aPyvJdsZVENiWsetg',
    nonceInfo: null,
    instructions: [ [Object], [Object], [Object] ],
    signers: []
  }
}

対応

Message.fromでシリアライズしたトランザクションを指定すると、Messageの中身が取得できるため、accountKeysに対象のアドレスを追加する。

    const m = Message.from(serializedTx);

おそらく、本エラーは一般的な実装だと、まず発生しないと思われる。
自分のケースのように、パフォーマンスチューニングをしたくてinsturctionsを強引に変更して検証する、といったことがない限りは発生しない。
もし、普通の実装でも本件エラーが出た場合は、前提や根本から見直すのがよさそう(なにか普通と違うことをやろうとしている可能性が高い)。