152 lines
8.4 KiB
Markdown
152 lines
8.4 KiB
Markdown
<!-- file: kb_execution_solana/README.md -->
|
||
<!-- version: 4 -->
|
||
|
||
# kb_execution_solana
|
||
|
||
`kb_execution_solana` transforme un `ExApiPreparedExecutionPlan` commun en message et transaction Solana, puis résout les signataires via l’interface Solana `Signer` et signe uniquement après une simulation autorisée par `kb_execution_safety`.
|
||
|
||
La crate est indépendante des exécuteurs concrets et des transports RPC. Elle assemble aussi bien les plans à recent blockhash que les transactions consommant réellement un durable nonce.
|
||
|
||
## API publique
|
||
|
||
| Export | Usage |
|
||
|----------------------------------------|-------------------------------------------------------------------------------------------------------|
|
||
| `build_legacy_transaction(...)` | Compile un plan `Latest` avec un recent blockhash explicite. |
|
||
| `build_durable_nonce_transaction(...)` | Compile un plan `DurableNonce` avec un état nonce validé et injecte l’avance en première instruction. |
|
||
| `durable_nonce_account_data_length()` | Fournit la longueur exacte à demander au RPC pour un compte nonce. |
|
||
| `parse_durable_nonce_account(...)` | Valide et décode un snapshot complet de compte nonce System. |
|
||
| `DurableNonceAccountState` | État validé exposant compte, autorité, nonce/blockhash et coût par signature. |
|
||
| `UnsignedSolanaTransaction` | Message compilé non signé, destiné aux frais, à la simulation exacte et à la signature contrôlée. |
|
||
| `SolanaSimulationEvidence` | Simulation liée au hash et au blockhash/nonce exacts du message. |
|
||
| `SignedSolanaTransaction` | Transaction signée, vérifiable localement et sérialisable pour `sendTransaction`. |
|
||
|
||
### Méthodes des types
|
||
|
||
| Type | Méthodes publiques |
|
||
|-----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||
| `DurableNonceAccountState` | `account()`, `authority()`, `blockhash()`, `lamports_per_signature()`. |
|
||
| `UnsignedSolanaTransaction` | `plan()`, `message_hash()`, `blockhash()`, `required_signer_pubkeys()`, `message_base64()`, `transaction_base64()`, `bind_simulation(...)`, `sign_after_simulation(...)`. |
|
||
| `SolanaSimulationEvidence` | `message_hash()`, `blockhash()`, `simulation()`. |
|
||
| `SignedSolanaTransaction` | `message_hash()`, `primary_signature()`, `signer_pubkeys()`, `wire_length()`, `transaction_base64()`, `verify_signatures()`. |
|
||
|
||
## Exemple de flux
|
||
|
||
```rust
|
||
let unsigned = kb_execution_solana::build_legacy_transaction(&plan, recent_blockhash)?;
|
||
let simulation = rpc_simulation.to_execution_result(
|
||
unsigned.message_hash(),
|
||
unsigned.blockhash(),
|
||
observed_cluster,
|
||
)?;
|
||
let evidence = unsigned.bind_simulation(simulation)?;
|
||
let signed = unsigned.sign_after_simulation(&evidence, signers.as_slice())?;
|
||
signed.verify_signatures()?;
|
||
```
|
||
|
||
Le transport RPC reste extérieur : cette crate ne récupère pas le blockhash, ne charge pas le compte nonce et n’envoie pas le payload signé.
|
||
|
||
## Assemblage à recent blockhash
|
||
|
||
### `build_legacy_transaction(plan, recent_blockhash)`
|
||
|
||
Construit une transaction legacy non signée à partir d’un blockhash base58 obtenu par `getLatestBlockhash`.
|
||
|
||
La fonction vérifie :
|
||
|
||
- la politique `ExecutionBlockhashKind::Latest` ;
|
||
- les public keys et le blockhash ;
|
||
- la présence d’au moins une instruction ;
|
||
- l’égalité exacte entre signataires déclarés et signataires compilés ;
|
||
- la limite wire Solana de 1 232 octets.
|
||
|
||
Un plan `DurableNonce` est refusé et doit utiliser le chemin dédié.
|
||
|
||
## Lecture et validation d’un compte nonce
|
||
|
||
### `durable_nonce_account_data_length()`
|
||
|
||
Retourne la taille wire officielle d’un compte nonce, actuellement 80 octets. Cette valeur doit être utilisée comme limite exacte d’un appel `getAccountInfo` avec données complètes.
|
||
|
||
### `parse_durable_nonce_account(account, owner, executable, space, data)`
|
||
|
||
Décode l’état officiel `solana_nonce::versions::Versions` avec `wincode::deserialize_exact` et produit un `DurableNonceAccountState` uniquement lorsque toutes les conditions suivantes sont satisfaites :
|
||
|
||
- adresse du compte valide ;
|
||
- owner égal au System Program ;
|
||
- compte non exécutable ;
|
||
- `space` et longueur décodée égaux à la taille officielle ;
|
||
- version `Current` ;
|
||
- état `Initialized` ;
|
||
- autorité et valeur nonce extraites sans approximation.
|
||
|
||
Les états `Legacy`, `Uninitialized`, tronqués, suffixés ou possédés par un autre programme sont refusés. `DurableNonceAccountState` expose uniquement l’adresse, l’autorité, la valeur nonce utilisée comme blockhash et le coût par signature stocké dans l’état.
|
||
|
||
Le chargement réseau reste extérieur à cette crate. Le contrat RPC prévu est :
|
||
|
||
```rust
|
||
let config = kb_rpc::GetAccountInfoConfig::confirmed_with_data(
|
||
kb_execution_solana::durable_nonce_account_data_length(),
|
||
)?;
|
||
```
|
||
|
||
## Assemblage durable nonce
|
||
|
||
### `build_durable_nonce_transaction(plan, nonce_state)`
|
||
|
||
Construit une transaction non signée qui consomme l’état nonce validé.
|
||
|
||
La fonction exige :
|
||
|
||
- une politique `ExecutionBlockhashKind::DurableNonce` sans âge de blockhash récent ;
|
||
- le même compte nonce dans la politique et dans l’état validé ;
|
||
- la même autorité dans la politique et dans l’état validé ;
|
||
- l’autorité nonce dans `required_signers` ;
|
||
- au moins une instruction métier à exécuter après l’avance nonce.
|
||
|
||
L’assemblage utilise `Message::new_with_nonce`, place l’instruction officielle `AdvanceNonceAccount` en position zéro, puis remplace le champ `recent_blockhash` par la valeur nonce extraite de l’état. Le plan d’origine n’est pas muté : `UnsignedSolanaTransaction::plan()` expose un plan effectif cloné contenant l’instruction injectée.
|
||
|
||
Après compilation, la crate vérifie :
|
||
|
||
- la reconnaissance de la transaction par `solana_transaction::uses_durable_nonce` ;
|
||
- l’égalité exacte entre signataires déclarés et signataires compilés ;
|
||
- la taille wire ;
|
||
- le hash du message et la valeur nonce effectivement compilée.
|
||
|
||
Les opérations de gestion du compte nonce elles-mêmes, telles que `initialize`, `authorize`, `withdraw` ou `upgrade`, restent des transactions ordinaires à recent blockhash. Elles ne passent pas par ce chemin.
|
||
|
||
## Simulation et signature
|
||
|
||
### `UnsignedSolanaTransaction`
|
||
|
||
Expose :
|
||
|
||
- le plan effectif ;
|
||
- le hash exact du message ;
|
||
- le recent blockhash ou la valeur nonce compilée ;
|
||
- l’ordre exact des signataires ;
|
||
- le message base64 pour `getFeeForMessage` ;
|
||
- la transaction non signée base64 pour `simulateTransaction` avec `sigVerify = false` et `replaceRecentBlockhash = false`.
|
||
|
||
### `bind_simulation(result)`
|
||
|
||
Lie le résultat typé de simulation au hash du message **et** au blockhash/nonce compilé. Une simulation utilisant un autre message, une autre source de blockhash ou un blockhash de remplacement ne peut pas autoriser la signature.
|
||
|
||
### `sign_after_simulation(evidence, signers)`
|
||
|
||
Réévalue `kb_execution_safety`, exige une décision `Allow`, résout exactement tous les signataires via `&dyn solana_signer::Signer`, signe puis vérifie que la transaction est complète.
|
||
|
||
Les signataires manquants, supplémentaires ou dupliqués sont refusés. La fonction ne contacte aucun endpoint RPC et n’envoie rien.
|
||
|
||
### `SignedSolanaTransaction`
|
||
|
||
Expose uniquement des données non secrètes :
|
||
|
||
- hash du message ;
|
||
- signature primaire ;
|
||
- public keys des signataires ;
|
||
- taille wire ;
|
||
- payload base64 signé ;
|
||
- vérification locale des signatures.
|
||
|
||
Le payload signé reste côté backend. Il ne doit pas être transféré automatiquement à Tauri. `kb_rpc::HttpClient::send_transaction` et sa variante pool imposent le preflight et refusent une signature RPC différente de la signature primaire locale.
|