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))
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))
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))
なぜ 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 命令はここで処理されます。
let next_durable_nonce = DurableNonce::from_blockhash(&invoke_context.blockhash);
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())