mookim

mookim

mookim.eth

Solana Durable Nonceの紹介とPythonの使用

Solana Durable Nonce とは何ですか?#

solana は取引を発行する際にrecent_blockhashに署名する必要があり、2 分を超えるとその取引は承認されなくなり、二重支払いを防ぎます。

しかし、この設計はマルチシグシナリオをサポートできません(複数の人が 2 分以内にすべての署名を完了し、ブロードキャストすることを保証できません)。Solana は Nonce Account を導入してオフライン署名をサポートします。

効果:

  • 1 つの取引は最近のブロックハッシュに依存せず、Nonce Account に保存された値を使用することで、任意の時間にオフライン署名を完了できます。
  • 取引の最初の命令は AdvanceNonce である必要があり、保存された Nonce 値が変更されます。
  • Nonce の変化を事前に予測することはできません(EVM の連続整数ではなく、ハッシュです)。
  • 1 つのアドレスが所有できる Nonce Account の数に制限はないため、オフライン署名が必要な複数の取引には複数の Nonce Account が必要です。

Python を使用して簡単な送金取引を送信する方法#

基礎知識:

  • lamport は SOL の最小単位で、1 lamport = 1e-9 SOL です。
  • テストネットでは request_airdrop を使用してテスト通貨を取得でき、IP 制限は 1 回 / 24 時間で、最大 5SOL をリクエストできます。
  • Solana アドレス、BlockHash は Base58 エンコードで表され、本質的には uint256 です。
  • solathon という python パッケージがありますが、機能サポートは非常に不完全で、solana-py も Nonce 関連のインターフェースを公開していないため、より低レベルの solders を使用する必要があります。
  • solana-py のネットワークリクエストは httpx を使用しており、https_proxy 環境変数を尊重せず、proxychains を使用してプロキシを通す必要があります。
  • solana-py と solders のドキュメントは非常に不完全です。参考としてsolana web3.js ドキュメントを参照し、コードリポジトリを直接検索することをお勧めします。
# pip install solana==0.32.0
import base58
from solana.rpc.api import Client
from solders.keypair import Keypair
from solders.system_program import transfer, TransferParams
from solana.transaction import Transaction
client = Client("https://api.devnet.solana.com")

sender = Keypair()
print("このプライベートキーを保存してください sender:", base58.b58encode(bytes(sender.to_bytes_array())).decode())
#sender = Keypair.from_base58_string(pk1)
print("テストネットでエアドロップをリクエスト:", client.request_airdrop(sender.pubkey(), 5*10**9))
from time import sleep
sleep(10) #エアドロップが完了するのを待つ

receiver = Keypair().pubkey() #送金先を変更

amt = 1238888 # client.get_minimum_balance_for_rent_exemption(0).valueより大きくする必要があります
instruction = transfer(TransferParams(
  from_pubkey=sender.pubkey(), 
  to_pubkey=receiver, 
  lamports=amt
))
instructions=[instruction]

bh = client.get_latest_blockhash().value.blockhash
transaction = Transaction(recent_blockhash=bh, instructions=instructions, fee_payer=sender.pubkey())
transaction.sign(sender)
stx = transaction.serialize()
result = client.send_raw_transaction(stx)
print("送金取引:", str(result.value))

取引例:https://explorer.solana.com/tx/3w3Fj2wYHNxr49umWf2p94RHxfEMWwk2JCQp1U1btniW9eLugVT5ZWiiJ2pf1a1gJdpZsBhombdHmnyQSbWUQfKn?cluster=devnet

Nonce Account を作成する方法#

from solana.rpc.api import Client
from solders.keypair import Keypair
from solders.system_program import create_nonce_account
from solana.transaction import Transaction
client = Client("https://api.devnet.solana.com")

pk1 = "..."
sender = Keypair.from_base58_string(pk1)
pk2 = "..."
nonceacc = Keypair.from_base58_string(pk2)

rent_value = client.get_minimum_balance_for_rent_exemption(80).value
instructions = create_nonce_account(
    from_pubkey=sender.pubkey(),
    nonce_pubkey=nonceacc.pubkey(),
    authority=sender.pubkey(),
    lamports=rent_value
)

bh = client.get_latest_blockhash().value.blockhash
transaction = Transaction(recent_blockhash=bh, instructions=instructions, fee_payer=sender.pubkey())
transaction.sign(sender, nonceacc)
stx = transaction.serialize()
result = client.send_raw_transaction(stx)
print("Nonceアカウント作成取引:", str(result.value))

取引例:https://explorer.solana.com/tx/4GqHiidSi1s5bzNijKyn5yoweEAu97W26524je62iY5nrAVEFDF62sW869mJbFBHBXsLWkFHsZoPne1xbmGCQboi?cluster=devnet

Solana で新しいアドレスを作成するには、rent exemption の要件を満たす必要があり、nonce account は 80 バイトを占有します。必要な数量を確認するには get_minimum_balance_for_rent_exemption を使用します。

Durable Nonce を使用して送金取引を送信する方法#

from solana.rpc.api import Client
from solders.hash import Hash
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solders.system_program import transfer, TransferParams, create_nonce_account, AdvanceNonceAccountParams, advance_nonce_account
from solana.transaction import Transaction

pk1 = "..."
sender = Keypair.from_base58_string(pk1)
pk2 = "..."
nonceacc = Keypair.from_base58_string(pk2)
receiver = "..."
receiver = Pubkey.from_string(receiver)

advance = advance_nonce_account(AdvanceNonceAccountParams(
  nonce_pubkey=nonceacc.pubkey(), 
  authorized_pubkey=sender.pubkey()
))
amt = 1238888
instruction = transfer(TransferParams(
  from_pubkey=sender.pubkey(), 
  to_pubkey=receiver, lamports=amt
))
instructions=[advance, instruction]

nonceinfo = client.get_account_info(nonceacc.pubkey())
bh = Hash.from_bytes(nonceinfo.value.data[40:72])
transaction = Transaction(recent_blockhash=bh, instructions=instructions, fee_payer=sender.pubkey())
transaction.sign(sender)
stx = transaction.serialize()
result = client.send_raw_transaction(stx)
print("Nonceを使用した送金取引:", str(result.value))

取引例:https://explorer.solana.com/tx/5nHUFP6K8px6L7N7k7qsCcJiyigGj11KuzPW7AZT5YZ3j2SZqcnQFdvjK1gJa4kyM48Ev4YxuTbWczNj5kyz22Bk?cluster=devnet

なぜ nonce 値はアカウント情報の[40:72]なのですか?#

https://github.com/solana-labs/solana-web3.js/blob/b7d1c26/packages/library-legacy/src/nonce-account.ts#L11 によると、Nonce アカウントが保存するデータは次のとおりです:

  • I uint32 version=1
  • I uint32 state=1
  • 32s publicKey authorizedPubkey = 送信者のアドレス
  • 32s publicKey nonce = 現在の nonce 値、つまり[40:72]
  • Q uint64 feeCalculator=5000 各署名に必要な lamports の数

Nonce Account Data をデコードするには、次のようにします:struct.unpack("II32s32sQ", nonceinfo.value.data)

AdvanceNonce 後、nonce はどのように変化するのですか?#

AdvanceNonce 命令はここで処理されます。

https://github.com/solana-labs/solana/blob/cdb0d15283bbad0724a98d58458684468921c66c/programs/system/src/system_instruction.rs#L45

let next_durable_nonce = DurableNonce::from_blockhash(&invoke_context.blockhash);

https://github.com/solana-labs/solana/blob/cdb0d15283bbad0724a98d58458684468921c66c/sdk/program/src/nonce/state/current.rs#L56

DurableNonce::from_blockhash は次のようになります。

const DURABLE_NONCE_HASH_PREFIX: &[u8] = "DURABLE_NONCE".as_bytes();

pub fn from_blockhash(blockhash: &Hash) -> Self {
    Self(hashv(&[DURABLE_NONCE_HASH_PREFIX, blockhash.as_ref()]))
}

ここでの hashv は sha256 です。したがって、新しい nonce は sha256 ("DURABLE_NONCE" + parentBlockHash) になります。これは、現在の取引が存在するブロックの前のブロックハッシュにDURABLE_NONCEというプレフィックスを付けてハッシュを計算したものです。

import base58
h=hashlib.sha256()
h.update(b"DURABLE_NONCE" + base58.b58decode(parentBlockHash))
print(h.digest())
読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。