33 KiB
Inventaire et couverture des programmes Solana natifs
Périmètre
Le registre kb_program_ids::native_program_ids() contient dix-huit surfaces exécutables : programmes runtime natifs, loaders, précompiles et programmes historiques encore observables. Les sysvars et comptes natifs non exécutables restent suivis séparément dans registry/core_program_id_seed.toml.
Memo ne fait pas partie de ce périmètre. Les IDs officiels v1 Memo1U..., v3 MemoSq... et v4 Memo4c... sont réservés dans kb_decoder_spl_memo.
État du phasage
| Phase | Contenu | État |
|---|---|---|
| A | modèle d’événement, lecture bornée, validation des comptes et diagnostics | implémenté dans 0.4.1-pre.001 |
| B | System Program et Compute Budget | implémenté, replay mainnet validé dans pre.005 |
| C | Address Lookup Table et loaders | implémenté dans pre.003 puis pre.006 |
| D | Vote, Stake, Config et programmes historiques | implémenté : Config/Feature pre.008, Vote pre.010 validé dans pre.011, Stake pre.013 validé mainnet |
| E | précompiles et preuves ZK | précompiles signature pre.014, ZK ElGamal Proof pre.015, ancien ZK Token Proof pre.016 |
| F | matérialisations justifiées, diagnostics globaux et clôture | nonce System/contextes ZK pre.017; Slashing et invariants de couverture pre.018; corpus final à valider |
Le processor conserve l’identité solana_native_classifier@0.4.1 pendant les deltas préparatoires. Les nouveaux décodeurs nécessitent donc un force replay ciblé ou global ; la version ne sera relevée qu’avec un changement explicite du contrat de processor.
Sources normatives couvertes
| Surface | Source officielle | Sérialisation retenue | Couverture |
|---|---|---|---|
| System Program | solana-system-interface 3.2.0, enum SystemInstruction |
schéma wincode officiel compatible avec le format bincode historique, lecture exacte sans octets suffixes |
14 variantes |
| Compute Budget actuel | solana-compute-budget-interface 3.0.0 + runtime solana-compute-budget-instruction 4.1.1 |
tag u8 puis entier little-endian/Borsh ; désérialisation runtime unchecked, suffixe accepté et audité |
5 entrées, dont le tag réservé Unused |
| Compute Budget historique | ancien solana-sdk, RequestUnitsDeprecated |
tag 0, u32 units, u32 additional_fee |
1 variante historique |
| Address Lookup Table | solana-address-lookup-table-interface 3.1.0, enum ProgramInstruction |
schéma wincode officiel, lecture exacte |
5 variantes |
| BPF Loader immuable | solana-loader-v2-interface 3.0.0, enum LoaderInstruction |
tag u32 LE, offset u32, longueur de vector u64, lecture exacte |
write, finalize sur deux générations |
| BPF Loader upgradeable | solana-loader-v3-interface 8.0.1, enum UpgradeableLoaderInstruction |
wire format historique compatible bincode, champs booléens suffixes avec valeurs historiques documentées | 8 variantes |
| Loader v4 | solana-loader-v4-interface 3.1.0, enum LoaderV4Instruction |
tag et entiers little-endian, tailles exactes | 7 variantes |
| Config Program | solana-config-interface 2.0.0 et processor solana-config-program 2.2.20 |
ConfigKeys en solana_short_vec, clés (Pubkey, bool), puis payload typé opaque pour le décodeur générique |
1 écriture générique store |
| Feature Gate | solana-feature-gate-interface 4.0.0, FeatureGateInstruction |
tag u8 exact 0, sans suffixe |
1 variante |
| Vote Program | solana-vote-interface ^6.0, enum VoteInstruction |
schéma officiel wincode, discriminant historique u32 LE, lecture exacte sans octets suffixes |
20 variantes |
| Stake Program | solana-stake-interface ^4.3, enum StakeInstruction |
miroir wire privé wincode exact du layout historique ; types sémantiques officiels consommés |
18 variantes |
| ZK ElGamal Proof | solana-zk-elgamal-proof-interface ^0.1, ProofInstruction, types Pod, runtime Agave |
tag u8; preuve inline de taille officielle ou référence compte u32 LE sur instruction exacte de 5 octets |
13 variantes |
| ZK Token Proof historique | miroir wire local audité contre solana-zk-token-sdk 3.1.14, Agave v2.0.0/v4.1.1 |
tag u8; layout historique inline ou offset compte sur 5 octets ; fallback runtime actuel sans effet |
17 variantes + fallback no-op |
| Slashing stateless | Agave v4.1.1 + solana-program/slashing@fe8da3a |
tag u8; fermeture sur 1 octet ou preuve duplicate block sur 305 octets exacts |
2 variantes |
Les versions de crates sont utilisées comme références reproductibles. Un futur changement d’interface doit entraîner un changement de version du décodeur et un replay ledger/version/hash.
System Program
Program ID : 11111111111111111111111111111111.
Tag historique compatible bincode/wincode u32 LE |
Entry code | Paramètres structurés | Comptes principaux | État |
|---|---|---|---|---|
| 0 | create_account |
lamports, space, owner | funding, new account | décodé |
| 1 | assign |
owner | assigned account | décodé |
| 2 | transfer |
lamports | funding, recipient | décodé |
| 3 | create_account_with_seed |
base, seed, lamports, space, owner | funding, new account, base optionnelle | décodé |
| 4 | advance_nonce_account |
aucun | nonce, recent blockhashes, authority | décodé |
| 5 | withdraw_nonce_account |
lamports | nonce, recipient, sysvars, authority | décodé |
| 6 | initialize_nonce_account |
authority | nonce, recent blockhashes, rent | décodé |
| 7 | authorize_nonce_account |
authority | nonce, authority | décodé |
| 8 | allocate |
space | allocated account | décodé |
| 9 | allocate_with_seed |
base, seed, space, owner | derived account, base | décodé |
| 10 | assign_with_seed |
base, seed, owner | derived account, base | décodé |
| 11 | transfer_with_seed |
lamports, from seed, from owner | funding, base, recipient | décodé |
| 12 | upgrade_nonce_account |
aucun | nonce account | décodé |
| 13 | create_account_allow_prefund |
lamports, space, owner | new account, funding conditionnel | décodé |
Le décodeur utilise le schéma wincode dérivé directement par solana-system-interface 3.2.0. wincode::deserialize_exact conserve la compatibilité octet pour octet avec le format historique de cette enum et refuse les octets suffixes. Le crate n’utilise aucune dépendance directe à bincode.
pre.017 matérialise les cinq opérations durable nonce sous durable_nonce_account:<operation>:0. Initialize, advance, authorize et upgrade conservent leur transition explicite. Withdraw conserve le montant demandé et la règle officielle de fermeture conditionnelle : le compte n’est détruit que lorsque la totalité de son solde est retirée. La projection ne prétend pas reconstruire l’état final transactionnel du compte et ne duplique pas les deltas SOL du graphe core.
Les flags signer/writable sont conservés sous deux formes : privilèges attendus par le contrat officiel et privilèges réellement résolus depuis core. Ils ne sont pas confondus, car une CPI signée par PDA ne se reflète pas nécessairement comme signer statique de la transaction.
Compute Budget
Program ID : ComputeBudget111111111111111111111111111111.
Tag u8 |
Entry code | Taille décodée minimale | Paramètres | Statut |
|---|---|---|---|---|
| 0 | unused_reserved |
1 | aucun | ignored |
| 0 | request_units_deprecated |
9 | units, additional fee | decoded, historique |
| 1 | request_heap_frame |
5 | bytes u32 |
decoded |
| 2 | set_compute_unit_limit |
5 | compute unit limit u32 |
decoded |
| 3 | set_compute_unit_price |
9 | micro-lamports u64 |
decoded |
| 4 | set_loaded_accounts_data_size_limit |
5 | bytes u32 |
decoded |
Le runtime Agave traite les variantes modernes avec solana_borsh::v1::try_from_slice_unchecked. Les octets suffixes sont donc acceptés après les 5 ou 9 octets utiles. Le décodeur reproduit cette règle et conserve trailingDataByteLength, trailingDataSha256, trailingDataPrefixHex et trailingDataSemantics = ignored_by_runtime_borsh_unchecked. Les payloads trop courts restent failed. Le tag 0 demeure dispatché par taille afin de distinguer le tag réservé actuel de la variante historique à neuf octets ; toute autre taille pour ce tag reste failed. Un tag inconnu est unsupported avec diagnostic borné et hash du payload disponible pour un replay futur.
Address Lookup Table
Program ID : AddressLookupTab1e1111111111111111111111111.
Tag u32 LE |
Entry code | Paramètres | Comptes | État |
|---|---|---|---|---|
| 0 | create_lookup_table |
recent slot, bump seed, politique historique du signer authority | table, authority, payer, System Program | décodé |
| 1 | freeze_lookup_table |
aucun | table, authority | décodé |
| 2 | extend_lookup_table |
adresses ajoutées, count, présence du financement | table, authority, paire optionnelle payer/System | décodé |
| 3 | deactivate_lookup_table |
aucun | table, authority | décodé |
| 4 | close_lookup_table |
aucun | table, authority, recipient | décodé |
Le décodeur refuse une paire optionnelle d’extension incomplète et les octets suffixes. La création conserve expectedSigner = null pour l’autorité, car l’encodeur officiel actuel ne la marque plus signer tandis que les runtimes historiques antérieurs à v1.12 l’exigeaient.
La projection solana_native_lifecycle produit une sortie address_lookup_table:<operation>:0 uniquement si l’observation est exacte, réussie et commitée. Les intentions issues de transactions échouées restent dans decode et sont refusées avant matérialisation.
Programmes runtime natifs principaux
| Code canonique | Program ID | État au début de pre.018 |
|---|---|---|
vote |
Vote111111111111111111111111111111111111111 |
20 variantes décodées ; 500 tower_sync validés mainnet |
stake |
Stake11111111111111111111111111111111111111 |
18 variantes décodées ; 339 instructions mainnet sur neuf entry codes observés |
config |
Config1111111111111111111111111111111111111 |
écriture générique store décodée ; corpus ciblé à inventorier |
zk_elgamal_proof |
ZkE1Gama1Proof11111111111111111111111111111 |
13 variantes ; 151 instructions mainnet et 139 contextes matérialisés |
Loaders
| Surface | Program ID | Variantes | Statut pre.006 |
|---|---|---|---|
| Native Loader | NativeLoader1111111111111111111111111111111 |
invocation directe opaque | ignored, historique, aucun faux événement |
| BPF Loader deprecated | BPFLoader1111111111111111111111111111111111 |
write, finalize | décodé |
| BPF Loader v2 | BPFLoader2111111111111111111111111111111111 |
write, finalize | décodé |
| BPF Loader upgradeable | BPFLoaderUpgradeab1e11111111111111111111111 |
initialize buffer, write, deploy with max data length, upgrade, set authority, close, extend program, set authority checked | décodé |
| Loader v4 | LoaderV411111111111111111111111111111111111 |
write, copy, set program length, deploy, retract, transfer authority, finalize | décodé |
Les payloads write ne recopient pas le bytecode dans l’événement : ils conservent offset, longueur et SHA-256. Les tailles de vectors, booléens, octets suffixes et paires de comptes optionnelles sont validés avant toute lecture. Les anciennes formes upgradeable sans le booléen suffixe sont acceptées avec la valeur par défaut officielle et marquées comme telles.
La projection lifecycle produit program_loader:<surface>:<operation>:0 pour les mutations réussies et commitées : finalize immuable, initialize/deploy/upgrade/close/extend upgradeable, puis set length/deploy/retract/finalize v4. Les écritures, copies et changements d’autorité restent des événements decode de familles Audit ou Admin.
Slashing Program stateless
Program ID : S1ashing11111111111111111111111111111111111.
Agave v4.1.1 le déclare comme stateless builtin derrière enshrine_slashing_program, avec un hash de build vérifié correspondant à la release officielle solana-program/slashing@fe8da3a.
Tag u8 |
Entry code | Taille exacte | Effet runtime observé |
|---|---|---|---|
| 0 | close_violation_report |
1 | ferme le rapport après au moins trois epochs et transfère ses lamports |
| 1 | duplicate_block_proof |
305 | vérifie une preuve externe puis stocke un rapport de violation |
La preuve duplicate block est lue depuis un compte externe à l’offset u64 de l’instruction. Le replay transactionnel ne possède pas le contenu historique de ce compte. Le décodeur conserve l’offset, le slot, les clés, racines et signatures intégrées à l’instruction, résume l’instruction Ed25519 précédente et ne prétend ni relire le compte ni recalculer les signatures.
Une transaction réussie prouve seulement l’acceptation runtime, l’assignation/allocation du PDA préfinancé et l’écriture du rapport. Le programme ne brûle ni ne retire lui-même du stake : la projection slashing_violation_report conserve donc penaltyAppliedByProgram = false et décrit un signal destiné à une application de pénalité externe au programme.
Précompiles de signature pre.014
| Code canonique | Program ID | Format | État pre.014 |
|---|---|---|---|
ed25519 |
Ed25519SigVerify111111111111111111111111111 |
header 2 octets, offsets 14 octets, clé 32, signature 64 | décodé, zéro signature accepté seulement sur 2 octets |
secp256k1 |
KeccakSecp256k11111111111111111111111111111 |
header 1 octet, offsets 11 octets, adresse 20, signature 64 + recovery ID | décodé, index u8 explicites, zéro signature accepté seulement sur 1 octet |
secp256r1 |
Secp256r1SigVerify1111111111111111111111111 |
header 2 octets, offsets 14 octets, clé compressée 33, signature 64 | décodé, zéro signature refusé, maximum 8 |
Le contrat core 2 fournit les payloads de toutes les instructions outer. Ed25519 et secp256r1 interprètent u16::MAX comme la cible ; secp256k1 résout toujours un index explicite. Les références aux inner instructions ne sont pas autorisées. Les offsets et longueurs utilisent des additions vérifiées avant chaque slice.
Chaque observation conserve les index bruts et résolus, la provenance, les longueurs, SHA-256 et préfixes bornés des composants, ainsi que runtimeVerification. Aucun message complet n’est recopié, aucune signature n’est recalculée et aucune sortie métier n’est matérialisée. Une transaction échouée reste decoded si le layout est valide, mais porte not_asserted_transaction_failed et reste non commitée.
Surfaces historiques ou complémentaires
| Code canonique | Program ID | État après pre.008 |
|---|---|---|
feature |
Feature111111111111111111111111111111111111 |
revoke_pending_activation décodé et lifecycle matérialisable |
zk_token_proof |
ZkTokenProof1111111111111111111111111111111 |
17 layouts historiques décodés + fallback current_runtime_noop_invocation; validation synthétique |
Ancien ZK Token Proof Program
pre.016 couvre les discriminants 0..16 de l’interface historique : fermeture du contexte, zero balance, withdraw, égalités de ciphertext/commitment, transfer, transfer with fee, validité de pubkey, range proofs simples et batchés, validité de grouped ciphertext à deux ou trois handles et fee sigma. Les tailles de preuve et de contexte sont maintenant conservées dans une table locale auditée contre les types Pod de solana-zk-token-sdk 3.1.14, sans dépendre de cette crate dépréciée.
Le runtime historique Agave v2.0.0 distinguait une preuve inline d’une preuve stockée dans le premier compte lorsque l’instruction faisait exactement cinq octets. Il imposait des arités distinctes pour le contexte, refusait les vérifications comme inner instructions et plaçait certains chemins derrière enable_zk_proof_from_account ou enable_zk_transfer_with_fee. Le runtime Agave v4.1.1 est au contraire un stub qui retourne succès sans mutation. Le décodeur conserve ces deux références sans prétendre connaître la version runtime d’une transaction donnée.
Aucune signature mainnet n’a été trouvée pour ce program ID lors du jalon. La surface est donc validée par fixtures synthétiques et sources officielles, sans critère de backfill. Les preuves inline sont seulement mesurées, hashées et préfixées de façon bornée ; les preuves externes conservent l’offset et la taille attendue. Aucune preuve n’est recalculée et aucune sortie n’est matérialisée.
Stake Program
pre.013 couvre les dix-huit variantes de StakeInstruction, tags u32 LE 0..17 : initialize, authorize, delegate, split, withdraw, deactivate, set lockup, merge, autorisations avec seed, variantes checked, minimum delegation, deactivate delinquent, redelegate, move stake et move lamports. Redelegate reste déclaré historique : le processor actuel renvoie InvalidInstructionData avant tout traitement de comptes ; une occurrence réelle doit donc provenir d’une transaction échouée et reste non commitée.
L’enum officielle de solana-stake-interface ^4.3 conserve le contrat Serde/Bincode historique, mais n’implémente pas directement SchemaRead/SchemaWrite. Pour respecter l’interdiction workspace de dépendre de bincode, le décodeur utilise un miroir wire privé, ordonné exactement comme l’enum officielle, décodé par wincode::deserialize_exact. Les tests vérifient manuellement les tags, pubkeys, entiers, enums imbriquées et longueurs de chaînes du layout historique. Les types sémantiques et le program ID officiels restent consommés depuis l’interface.
Le processor BPF actuel supporte deux familles de comptes. Les layouts historiques sont détectés par la première sysvar héritée, généralement Clock ou Rent ; les layouts modernes retirent ces sysvars et placent directement les autorités aux positions attendues. Split, SetLockup et SetLockupChecked conservent volontairement les contraintes historiques plus souples du runtime. Le décodeur expose accountLayout, conserve les comptes additionnels et les privilèges attendus/observés, mais ne prétend pas rejouer les vérifications dépendant de l’état du stake account, du lockup, de la délégation ou des sysvars courantes. Aucune matérialisation Stake n’est ajoutée dans ce delta.
Compte Stake Config historique
StakeConfig11111111111111111111111111111111 n’est pas un programme exécutable. L’interface Stake officielle le déclare comme identifiant déprécié du compte de configuration historique du Stake Program. Il reste dans kb_program_ids comme STAKE_CONFIG_ACCOUNT_ID et dans le registre core avec identifier_kind = well_known_account, mais il est exclu des surfaces du décodeur et de l’exécuteur.
Config Program
Le Config Program ne possède pas un enum d’instructions discriminé comparable à System ou Stake. Chaque invocation stocke un préfixe ConfigKeys sérialisé avec solana_short_vec, suivi d’un payload spécifique au type de configuration. Le décodeur valide la longueur compacte, les clés, les booléens signataires et l’ordre des comptes signataires, puis conserve le reste par taille, préfixe borné et SHA-256 avec la sémantique explicite opaque_program_specific.
Vote Program
pre.010 utilise solana-vote-interface ^6.0 avec les features serde et wincode. Le discriminant wire historique reste un u32 little-endian compris entre 0 et 19, et wincode::deserialize_exact rejette les octets résiduels. VoteInitV2 contient les autorités, la clé BLS, sa preuve de possession et les deux commissions en points de base ; les collecteurs Inflation Rewards et Block Revenue sont les comptes d’instruction 2 et 3.
La couverture comprend : initialisations V1/V2, autorisations simples/checked/seed, vote et vote switch, mises à jour d’état normales/compactes, tower sync, retrait, identité, commissions historiques et en points de base, collecteurs différenciés et dépôt de récompenses délégateurs. Les tableaux BLS V2 sont conservés en hexadécimal exact. Les sysvars Rent, Clock et Slot Hashes sont vérifiées aux positions imposées par l’interface. Les privilèges attendus et observés restent distincts afin de ne pas confondre les signataires statiques et les signatures PDA de CPI.
Aucune projection matérialisée spécifique à Vote n’est ajoutée dans ce delta : la mutation réelle dépend de l’état du vote account et du runtime. Une transaction échouée produit uniquement une observation non commitée.
ZK ElGamal Proof pre.015
Program ID : ZkE1Gama1Proof11111111111111111111111111111.
Le décodeur consomme solana-zk-elgamal-proof-interface ^0.1 pour l’enum ProofInstruction et les tailles exactes des types Pod. Il couvre CloseContextState et les douze preuves : zero ciphertext, égalités ciphertext/ciphertext et ciphertext/commitment, validité de clé publique, percentage with cap, range proofs 64/128/256 bits et validités grouped/batched à deux ou trois handles.
Deux modes de transport sont distingués conformément au runtime :
- instruction de cinq octets : tag puis offset
u32 LEvers un compte contenant la preuve ; - autre taille exacte : preuve inline immédiatement après le tag, avec taille égale au type
ProofDataofficiel.
Pour une preuve inline, les blocs contexte et preuve sont séparés par leurs tailles Pod, puis conservés uniquement sous forme de longueur, SHA-256 et préfixe hexadécimal borné. Le décodeur ne recalcule aucune preuve cryptographique. Pour une transaction réussie, il indique seulement que l’instruction a été acceptée par le runtime. Pour une transaction échouée, aucune validité n’est affirmée et l’observation reste non commitée.
Le mode référencé par compte a une limite historique structurelle : le graphe core de transaction ne contient pas le contenu du compte à l’instant d’exécution. L’événement conserve donc le compte source, l’offset, le type et la taille attendue, mais marque les octets de preuve comme indisponibles. Il ne tente ni lecture RPC actuelle, qui pourrait être divergente, ni reconstruction.
Les comptes optionnels de contexte sont résolus selon le mode : positions 0/1 pour une preuve inline et 1/2 après le compte de preuve pour une preuve externe. La fermeture du contexte conserve le contexte, la destination des lamports et l’autorité signataire, accepte les octets suffixes comme le runtime et refuse que contexte et destination désignent le même compte.
pre.017 matérialise le lifecycle du compte de contexte sous zk_proof_context:<operation>:0. Une vérification n’est éligible que lorsque contextStateRequested = true, que la transaction a réussi et que l’observation est commitée. La sortie conserve le compte, l’autorité et le type de preuve, mais jamais la preuve elle-même. La fermeture réussie décrit la récupération des lamports, la remise à zéro des données et le retour de l’owner au System Program. Les preuves sans contexte restent en decode/audit.
Statuts et politique d’échec
decodedsignifie que le format complet a été validé et qu’une observation structurée a été produite.ignoredest réservé à une entrée comprise mais sans événement utile, comme le tag Compute BudgetUnused.unsupportedconserve une variante inconnue ou une surface non encore implémentée sans faux événement.failedindique un payload absent, invalide, tronqué, non canonique ou des comptes incohérents.unmatchedreste un défaut de dispatch et ne doit pas apparaître pour une sélection bornée aux surfaces déclarées.
Une transaction on-chain échouée reste décodable comme intention. L’observation porte transactionSucceeded = false et observation_committed = false. Le matérialiseur solana_native_lifecycle est actif pour ALT, les mutations loader listées, Feature Gate, les durable nonce accounts System et les comptes de contexte ZK ElGamal. Il accepte les familles Lifecycle, Admin et Audit uniquement au niveau exact surface/entrée/paramètres, puis applique SuccessfulCommittedOnly. Une observation non commitée est refusée et une preuve ZK sans contexte est exclue avant ledger et politique.
Validation mainnet pre.005
Les correctifs de pre.005 ont été validés par les tests ciblés, cargo clippy --all-targets et deux campagnes Tauri. Le force replay ciblé a décodé 3/3 inputs sans échec. Le replay global a sélectionné, démarré et terminé 476 inputs : 476 décodés, zéro failed, unsupported, unmatched et zéro faux materializationRefused. Aucun output n’était attendu, car le corpus ne contenait pas d’opération ALT éligible.
Validation pre.006, pre.008, Vote mainnet et anomalie Compute Budget
Les tests pre.006, Clippy et le replay Tauri global ont été validés : 476 inputs sélectionnés, 476 décodés, aucun échec, unsupported, unmatched ou refus de matérialisation. Les validations utilisateur de pre.008 couvrent 256 tests ciblés, PostgreSQL réel, Clippy et un démarrage/replay Tauri propre. Le corpus initial ne contenait que System et Compute Budget : loaders, Config et Feature restent en attente d’un corpus réel.
Le backfill Vote de 500 signatures a inséré 500 transactions et l’extraction core a terminé 500/500 sans échec. Le replay global suivant contient 500 instructions Vote réelles, toutes reconnues et décodées comme tower_sync, sans échec Vote. L’unique failed de la campagne provient d’un set_compute_unit_limit de 12 octets : le runtime l’accepte grâce à la désérialisation Borsh unchecked, alors que le décodeur strict exigeait encore 5 octets exacts. pre.011 corrige cet écart. Le processor reste solana_native_classifier@0.4.1 : un force replay est requis pour remplacer le résultat failed déjà enregistré.