This commit is contained in:
2026-07-23 16:37:12 +02:00
parent 99c345f2f2
commit 0da75c1311
2159 changed files with 230833 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
# file: kb_materializer_lifecycle/Cargo.toml
# version: 5
[package]
name = "kb_materializer_lifecycle"
version.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
kb_core = { path = "../kb_core" }
kb_decoder_api = { path = "../kb_decoder_api" }
kb_materializer_api = { path = "../kb_materializer_api" }
kb_model = { path = "../kb_model" }
kb_program_ids = { path = "../kb_program_ids" }
serde_json.workspace = true
tracing.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,36 @@
<!-- file: kb_materializer_lifecycle/README.md -->
<!-- version: 9 -->
# kb_materializer_lifecycle
Ce crate matérialise des mutations lifecycle natives stables dans des projections métier génériques et versionnées. Il ne se limite pas à la famille décodée `Lifecycle` : certaines mutations exactes sont classées `Admin` ou `Audit` par le décodeur, puis promues en sortie `Lifecycle` uniquement lorsque leur effet durable est explicite et que la transaction est réussie et commitée.
## Projections actives
- comptes System génériques : `system_account:<operation>:0` pour create/allocate ;
- Address Lookup Table : `address_lookup_table:<operation>:0` ;
- loaders natifs : `program_loader:<surface>:<operation>:0` ;
- Feature Gate : `feature_gate:revoke_pending_activation:0` ;
- durable nonce System : `durable_nonce_account:<operation>:0` pour initialize, advance, authorize, upgrade et withdraw ;
- contexte ZK ElGamal : `zk_proof_context:<operation>:0` pour linitialisation demandée par une vérification réussie et pour la fermeture ;
- rapport Slashing : `slashing_violation_report:<operation>:0` pour linitialisation après preuve acceptée et la fermeture après rétention minimale.
Le retrait dun nonce account est projeté comme une opération de retrait avec fermeture conditionnelle. Le runtime détruit le compte seulement lorsque la totalité du solde est retirée ; le matérialiseur ne prétend pas reconstruire létat final du compte à partir de linstruction seule et ne duplique pas les deltas SOL déjà présents dans core.
Une vérification ZK ElGamal sans compte de contexte reste en decode/audit. Lorsquun contexte est demandé, la projection conserve le compte de contexte, son autorité et le type de preuve, mais ne matérialise jamais la preuve cryptographique. Le rapport Slashing est un enregistrement de violation et non une pénalité appliquée par le programme ; la projection conserve explicitement `penaltyAppliedByProgram = false`. Lancien ZK Token Proof Program reste exclu : le runtime Agave actuel est un stub sans effet et les layouts historiques ne disposent pas dun corpus mainnet fiable.
## Règles locales
- Les commentaires de code restent en anglais.
- La documentation Markdown reste en français.
- Les exports publics sont contrôlés depuis `lib.rs` lorsque le crate expose une bibliothèque.
- Toute projection mutable applique `SuccessfulCommittedOnly`.
- Lapplicabilité est filtrée au niveau exact de la surface, de lentrée et, pour ZK ElGamal, de `contextStateRequested`.
- Le lifecycle ATA appartient exclusivement à `kb_materializer_token_accounts` et reste explicitement ignoré ici afin d'éviter toute duplication.
- Les écritures/copies de bytecode et changements dautorité Loader sont pris en charge par les matérialiseurs compliance audit et admin ; Compute Budget, les précompiles de signature et les preuves ZK sans contexte restent dans decode.
Depuis `0.4.1-pre.005`, lapplicabilité est déclarée au niveau exact de lobservation. `pre.006` ajoute les mutations loader stables, `pre.008` la révocation Feature Gate et `pre.017` les durable nonce accounts System ainsi que les comptes de contexte ZK ElGamal, puis `pre.018` les rapports Slashing. Toute transaction échouée ou observation non commitée reste refusée.
## Comptes System génériques
`pre.020` ajoute les sorties `system_account:<operation>:0` pour `create_account`, `create_account_with_seed`, `create_account_allow_prefund`, `allocate` et `allocate_with_seed`. La projection conserve la cible, l'owner, l'espace, la seed et le montant demandé, mais ne recopie pas les deltas SOL déjà présents dans le graphe core.

View File

@@ -0,0 +1,7 @@
// file: kb_materializer_lifecycle/src/constants.rs
// version: 1
//! Local constants for the `kb_materializer_lifecycle` crate.
/// Canonical tracing target for this crate.
pub(crate) const TRACING_TARGET: &str = "kb_materializer_lifecycle";

View File

@@ -0,0 +1,16 @@
// file: kb_materializer_lifecycle/src/lib.rs
// version: 5
//! Materializer crate for `lifecycle`.
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
mod constants;
mod materializer;
/// Canonical tracing target for this crate.
pub(crate) use crate::constants::TRACING_TARGET;
/// Stable lifecycle materializer for supported native lifecycle projections.
pub use crate::materializer::LifecycleMaterializer;

View File

@@ -0,0 +1,993 @@
// file: kb_materializer_lifecycle/src/materializer.rs
// version: 13
//! Stable lifecycle projections derived from exact decoded observations.
const ACCEPTED_FAMILIES: &[kb_model::EventFamily] = &[
kb_model::EventFamily::Lifecycle,
kb_model::EventFamily::Admin,
kb_model::EventFamily::Audit,
];
const ADDRESS_LOOKUP_TABLE_SURFACE: &str = "solana_native_address_lookup_table";
const ADDRESS_LOOKUP_TABLE_ENTRIES: &[&str] = &[
"create_lookup_table",
"freeze_lookup_table",
"extend_lookup_table",
"deactivate_lookup_table",
"close_lookup_table",
];
const BPF_LOADER_DEPRECATED_SURFACE: &str = "solana_native_bpf_loader_deprecated";
const BPF_LOADER_SURFACE: &str = "solana_native_bpf_loader";
const BPF_LOADER_UPGRADEABLE_SURFACE: &str = "solana_native_bpf_loader_upgradeable";
const LOADER_V4_SURFACE: &str = "solana_native_loader_v4";
const FEATURE_SURFACE: &str = "solana_native_feature";
const FEATURE_ENTRIES: &[&str] = &["revoke_pending_activation"];
const IMMUTABLE_LOADER_ENTRIES: &[&str] = &["finalize"];
const UPGRADEABLE_LOADER_ENTRIES: &[&str] = &[
"initialize_buffer",
"deploy_with_max_data_len",
"upgrade",
"close",
"extend_program",
];
const LOADER_V4_ENTRIES: &[&str] = &["set_program_length", "deploy", "retract", "finalize"];
const SLASHING_SURFACE: &str = "solana_native_slashing";
const SLASHING_ENTRIES: &[&str] = &["close_violation_report", "duplicate_block_proof"];
const SYSTEM_SURFACE: &str = "solana_native_system";
const SYSTEM_ACCOUNT_ENTRIES: &[&str] = &[
"create_account",
"create_account_with_seed",
"allocate",
"allocate_with_seed",
"create_account_allow_prefund",
];
const SYSTEM_NONCE_ENTRIES: &[&str] = &[
"advance_nonce_account",
"withdraw_nonce_account",
"initialize_nonce_account",
"authorize_nonce_account",
"upgrade_nonce_account",
];
const ZK_ELGAMAL_PROOF_SURFACE: &str = "solana_native_zk_elgamal_proof";
const ZK_ELGAMAL_CLOSE_ENTRIES: &[&str] = &["close_context_state"];
const ZK_ELGAMAL_VERIFY_ENTRIES: &[&str] = &[
"verify_zero_ciphertext",
"verify_ciphertext_ciphertext_equality",
"verify_ciphertext_commitment_equality",
"verify_pubkey_validity",
"verify_percentage_with_cap",
"verify_batched_range_proof_u64",
"verify_batched_range_proof_u128",
"verify_batched_range_proof_u256",
"verify_grouped_ciphertext_2_handles_validity",
"verify_batched_grouped_ciphertext_2_handles_validity",
"verify_grouped_ciphertext_3_handles_validity",
"verify_batched_grouped_ciphertext_3_handles_validity",
];
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ProjectionKind {
AddressLookupTable,
FeatureGate,
ProgramLoader(&'static str),
SlashingViolationReportInitialization,
SlashingViolationReportClosure,
SystemAccountLifecycle,
DurableNonceAccount,
ZkProofContextInitialization,
ZkProofContextClosure,
}
impl ProjectionKind {
fn domain(self) -> &'static str {
return match self {
Self::AddressLookupTable => "address_lookup_table",
Self::FeatureGate => "feature_gate",
Self::ProgramLoader(_) => "program_loader",
Self::SlashingViolationReportInitialization | Self::SlashingViolationReportClosure => {
"slashing_violation_report"
},
Self::SystemAccountLifecycle => "system_account",
Self::DurableNonceAccount => "durable_nonce_account",
Self::ZkProofContextInitialization | Self::ZkProofContextClosure => "zk_proof_context",
};
}
fn loader_surface(self) -> std::option::Option<&'static str> {
return match self {
Self::ProgramLoader(surface) => std::option::Option::Some(surface),
_ => std::option::Option::None,
};
}
fn output_key(self, surface_code: &str, operation: &str) -> std::string::String {
return match self {
Self::AddressLookupTable => format!("address_lookup_table:{operation}:0"),
Self::FeatureGate => format!("feature_gate:{operation}:0"),
Self::ProgramLoader(_) => {
format!("program_loader:{surface_code}:{operation}:0")
},
Self::SlashingViolationReportInitialization | Self::SlashingViolationReportClosure => {
format!("slashing_violation_report:{operation}:0")
},
Self::SystemAccountLifecycle => format!("system_account:{operation}:0"),
Self::DurableNonceAccount => format!("durable_nonce_account:{operation}:0"),
Self::ZkProofContextInitialization | Self::ZkProofContextClosure => {
format!("zk_proof_context:{operation}:0")
},
};
}
}
/// Stable lifecycle materializer for supported native lifecycle projections.
#[derive(Clone, Debug, Default)]
pub struct LifecycleMaterializer;
impl kb_materializer_api::Materializer for crate::LifecycleMaterializer {
fn materializer_name(&self) -> &'static str {
return "kb_materializer_lifecycle";
}
fn materializer_version(&self) -> &'static str {
return env!("CARGO_PKG_VERSION");
}
fn accepts_event(&self, event: &kb_model::DecodedProtocolEvent) -> bool {
return family_is_accepted(event.event_family)
&& static_projection(event.surface_code.0.as_str(), event.event_name.0.as_str())
.is_some();
}
fn materialize_event(
&self,
event: &kb_model::DecodedProtocolEvent,
) -> kb_core::Result<std::vec::Vec<kb_model::MaterializedEvent>> {
if !kb_materializer_api::Materializer::accepts_event(self, event) {
return std::result::Result::Ok(std::vec::Vec::new());
}
return std::result::Result::Ok(std::vec::Vec::new());
}
}
impl kb_materializer_api::EventMaterializer for crate::LifecycleMaterializer {
fn identity(&self) -> kb_materializer_api::MaterializerIdentity {
return kb_materializer_api::MaterializerIdentity {
name: "solana_native_lifecycle".to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
};
}
fn accepted_families(&self) -> &'static [kb_model::EventFamily] {
return ACCEPTED_FAMILIES;
}
fn accepts_observation(&self, observation: &kb_decoder_api::DecodedObservation) -> bool {
return family_is_accepted(observation.event.event_family)
&& observation_projection(observation).is_some();
}
fn transaction_policy(
&self,
_family: kb_model::EventFamily,
) -> kb_materializer_api::MaterializationTransactionPolicy {
return kb_materializer_api::MaterializationTransactionPolicy::SuccessfulCommittedOnly;
}
fn materialize(
&self,
observation: &kb_decoder_api::DecodedObservation,
) -> kb_materializer_api::MaterializerExecutionResult {
let projection = match observation_projection(observation) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
tracing::debug!(
target: crate::TRACING_TARGET,
action = "materialize_lifecycle",
surface_code = %observation.event.surface_code.0.as_str(),
entry_code = %observation.event.event_name.0.as_str(),
accepted = false,
"ignore observation outside supported native lifecycle projections"
);
return kb_materializer_api::MaterializerExecutionResult::ignored();
},
};
if observation.transaction_failed || !observation.observation_committed {
tracing::debug!(
target: crate::TRACING_TARGET,
action = "materialize_lifecycle",
surface_code = %observation.event.surface_code.0.as_str(),
entry_code = %observation.event.event_name.0.as_str(),
accepted = true,
committed = observation.observation_committed,
transaction_failed = observation.transaction_failed,
"refuse uncommitted native lifecycle observation"
);
return kb_materializer_api::MaterializerExecutionResult::refused(
"failed_transaction_lifecycle_refused",
"failed or uncommitted native lifecycle observations cannot create lifecycle outputs",
);
}
let accounts = observation
.payload_json
.get("accounts")
.cloned()
.unwrap_or(serde_json::Value::Null);
let parameters = observation
.payload_json
.get("parameters")
.cloned()
.unwrap_or(serde_json::Value::Null);
let operation = observation.event.event_name.0.clone();
let output_key =
projection.output_key(observation.event.surface_code.0.as_str(), operation.as_str());
let domain = projection.domain();
let loader_surface = projection.loader_surface();
let projection_details =
projection_details(projection, &accounts, &parameters, operation.as_str());
tracing::debug!(
target: crate::TRACING_TARGET,
action = "materialize_lifecycle",
surface_code = %observation.event.surface_code.0.as_str(),
entry_code = %observation.event.event_name.0.as_str(),
projection_domain = domain,
accepted = true,
committed = true,
output_count = 1_usize,
"materialize committed native lifecycle observation"
);
let output = kb_materializer_api::MaterializedOutput {
output_key,
family: kb_model::MaterializedEventFamily::Lifecycle,
payload_json: serde_json::json!({
"projectionVersion": 1,
"projectionSemantics": "committed_instruction_lifecycle_event",
"domain": domain,
"loaderSurface": loader_surface,
"operation": operation,
"programId": observation.event.program_id.0.clone(),
"signature": observation.event.signature.0.clone(),
"slot": observation.event.slot.0,
"instructionPath": observation.event.instruction_path.0.clone(),
"transactionSucceeded": true,
"accounts": accounts,
"parameters": parameters,
"projection": projection_details,
}),
};
return kb_materializer_api::MaterializerExecutionResult {
status: kb_materializer_api::MaterializerOutcomeStatus::Inserted,
outputs: std::vec![output],
diagnostics: std::vec::Vec::new(),
};
}
}
fn family_is_accepted(family: kb_model::EventFamily) -> bool {
return ACCEPTED_FAMILIES.iter().any(|accepted| return *accepted == family);
}
fn observation_projection(
observation: &kb_decoder_api::DecodedObservation,
) -> std::option::Option<ProjectionKind> {
let surface_code = observation.event.surface_code.0.as_str();
let entry_code = observation.event.event_name.0.as_str();
let static_projection = static_projection(surface_code, entry_code);
if static_projection.is_some() {
return static_projection;
}
if surface_code == ZK_ELGAMAL_PROOF_SURFACE
&& contains(ZK_ELGAMAL_VERIFY_ENTRIES, entry_code)
&& observation
.payload_json
.get("parameters")
.and_then(|parameters| return parameters.get("contextStateRequested"))
.and_then(serde_json::Value::as_bool)
== std::option::Option::Some(true)
{
return std::option::Option::Some(ProjectionKind::ZkProofContextInitialization);
}
return std::option::Option::None;
}
fn static_projection(surface_code: &str, entry_code: &str) -> std::option::Option<ProjectionKind> {
if surface_code == FEATURE_SURFACE && contains(FEATURE_ENTRIES, entry_code) {
return std::option::Option::Some(ProjectionKind::FeatureGate);
}
if surface_code == ADDRESS_LOOKUP_TABLE_SURFACE
&& contains(ADDRESS_LOOKUP_TABLE_ENTRIES, entry_code)
{
return std::option::Option::Some(ProjectionKind::AddressLookupTable);
}
if (surface_code == BPF_LOADER_DEPRECATED_SURFACE || surface_code == BPF_LOADER_SURFACE)
&& contains(IMMUTABLE_LOADER_ENTRIES, entry_code)
{
let loader_surface = if surface_code == BPF_LOADER_DEPRECATED_SURFACE {
BPF_LOADER_DEPRECATED_SURFACE
} else {
BPF_LOADER_SURFACE
};
return std::option::Option::Some(ProjectionKind::ProgramLoader(loader_surface));
}
if surface_code == BPF_LOADER_UPGRADEABLE_SURFACE
&& contains(UPGRADEABLE_LOADER_ENTRIES, entry_code)
{
return std::option::Option::Some(ProjectionKind::ProgramLoader(
BPF_LOADER_UPGRADEABLE_SURFACE,
));
}
if surface_code == LOADER_V4_SURFACE && contains(LOADER_V4_ENTRIES, entry_code) {
return std::option::Option::Some(ProjectionKind::ProgramLoader(LOADER_V4_SURFACE));
}
if surface_code == SLASHING_SURFACE && contains(SLASHING_ENTRIES, entry_code) {
return if entry_code == "duplicate_block_proof" {
std::option::Option::Some(ProjectionKind::SlashingViolationReportInitialization)
} else {
std::option::Option::Some(ProjectionKind::SlashingViolationReportClosure)
};
}
if surface_code == SYSTEM_SURFACE && contains(SYSTEM_ACCOUNT_ENTRIES, entry_code) {
return std::option::Option::Some(ProjectionKind::SystemAccountLifecycle);
}
if surface_code == SYSTEM_SURFACE && contains(SYSTEM_NONCE_ENTRIES, entry_code) {
return std::option::Option::Some(ProjectionKind::DurableNonceAccount);
}
if surface_code == ZK_ELGAMAL_PROOF_SURFACE && contains(ZK_ELGAMAL_CLOSE_ENTRIES, entry_code) {
return std::option::Option::Some(ProjectionKind::ZkProofContextClosure);
}
return std::option::Option::None;
}
fn projection_details(
projection: ProjectionKind,
accounts: &serde_json::Value,
parameters: &serde_json::Value,
operation: &str,
) -> serde_json::Value {
return match projection {
ProjectionKind::SlashingViolationReportInitialization => {
serde_json::json!({
"targetAccount": account_key_for_role(accounts, "violation_report"),
"proofAccount": account_key_for_role(accounts, "proof_account"),
"violationSlot": parameters.get("violationSlot").cloned().unwrap_or(serde_json::Value::Null),
"nodePubkey": parameters.get("nodePubkey").cloned().unwrap_or(serde_json::Value::Null),
"reporter": parameters.get("reporter").cloned().unwrap_or(serde_json::Value::Null),
"lamportDestination": parameters.get("destination").cloned().unwrap_or(serde_json::Value::Null),
"stateTransition": "pda_assigned_allocated_and_data_stored_after_successful_duplicate_block_proof_acceptance",
"proofMaterialized": false,
"penaltyAppliedByProgram": false,
"enforcementSemantics": "violation_report_only_external_consensus_enforcement",
})
},
ProjectionKind::SlashingViolationReportClosure => serde_json::json!({
"targetAccount": account_key_for_role(accounts, "violation_report"),
"lamportDestination": account_key_for_role(accounts, "destination"),
"stateTransition": "closed_after_minimum_retention_lamports_reclaimed_owner_reset_to_system_program",
"minimumRetentionEpochs": 3,
"transactionFinalAccountStateCaptured": false,
}),
ProjectionKind::SystemAccountLifecycle => serde_json::json!({
"targetAccount": first_account_key_for_roles(
accounts,
&["new_account", "allocated_account", "derived_account"],
),
"fundingAccount": account_key_for_role(accounts, "funding_account"),
"baseAccount": account_key_for_role(accounts, "base_account"),
"requestedLamports": parameters.get("lamports").cloned().unwrap_or(serde_json::Value::Null),
"requestedSpace": parameters.get("space").cloned().unwrap_or(serde_json::Value::Null),
"requestedOwner": parameters.get("owner").cloned().unwrap_or(serde_json::Value::Null),
"seed": parameters.get("seed").cloned().unwrap_or(serde_json::Value::Null),
"stateTransition": system_account_transition(operation),
"lamportTransferRepresentedByCoreBalanceChanges": operation == "create_account"
|| operation == "create_account_with_seed"
|| operation == "create_account_allow_prefund",
"transactionFinalAccountStateCaptured": false,
}),
ProjectionKind::DurableNonceAccount => serde_json::json!({
"targetAccount": account_key_for_role(accounts, "nonce_account"),
"authorityAccount": account_key_for_role(accounts, "nonce_authority"),
"lamportDestination": account_key_for_role(accounts, "recipient_account"),
"stateTransition": nonce_transition(operation),
"newAuthority": parameters.get("authority").cloned().unwrap_or(serde_json::Value::Null),
"withdrawLamports": parameters.get("lamports").cloned().unwrap_or(serde_json::Value::Null),
"closureSemantics": if operation == "withdraw_nonce_account" {
serde_json::Value::String("closes_when_entire_balance_is_withdrawn".to_string())
} else {
serde_json::Value::Null
},
"transactionFinalAccountStateCaptured": false,
}),
ProjectionKind::ZkProofContextInitialization => serde_json::json!({
"targetAccount": account_key_for_role(accounts, "proof_context_state"),
"contextStateAuthority": account_key_for_role(accounts, "context_state_authority"),
"proofType": parameters.get("proofType").cloned().unwrap_or(serde_json::Value::Null),
"stateTransition": "initialized_after_successful_runtime_verification",
"proofMaterialized": false,
}),
ProjectionKind::ZkProofContextClosure => serde_json::json!({
"targetAccount": account_key_for_role(accounts, "proof_context_state"),
"lamportDestination": account_key_for_role(accounts, "lamport_destination"),
"contextStateAuthority": account_key_for_role(accounts, "context_state_authority"),
"stateTransition": "closed_lamports_reclaimed_owner_reset_to_system_program",
"proofMaterialized": false,
}),
ProjectionKind::AddressLookupTable
| ProjectionKind::FeatureGate
| ProjectionKind::ProgramLoader(_) => serde_json::json!({
"stateTransition": operation,
}),
};
}
fn system_account_transition(operation: &str) -> &'static str {
return match operation {
"create_account" => "created_allocated_funded_and_assigned",
"create_account_with_seed" => "derived_account_created_allocated_funded_and_assigned",
"create_account_allow_prefund" => "prefunded_account_initialized_allocated_and_assigned",
"allocate" => "account_data_space_allocated",
"allocate_with_seed" => "derived_account_data_space_allocated",
_ => "unknown",
};
}
fn nonce_transition(operation: &str) -> &'static str {
return match operation {
"initialize_nonce_account" => "initialized",
"advance_nonce_account" => "nonce_advanced",
"authorize_nonce_account" => "authority_changed",
"upgrade_nonce_account" => "legacy_version_upgraded",
"withdraw_nonce_account" => "lamports_withdrawn_possible_closure",
_ => "unknown",
};
}
fn first_account_key_for_roles(accounts: &serde_json::Value, roles: &[&str]) -> serde_json::Value {
for role in roles {
let value = account_key_for_role(accounts, role);
if !value.is_null() {
return value;
}
}
return serde_json::Value::Null;
}
fn account_key_for_role(accounts: &serde_json::Value, role: &str) -> serde_json::Value {
let account_key = accounts.as_array().and_then(|values| {
return values.iter().find_map(|value| {
if value.get("role").and_then(serde_json::Value::as_str)
!= std::option::Option::Some(role)
{
return std::option::Option::None;
}
return value
.get("accountKey")
.and_then(serde_json::Value::as_str)
.map(|account_key| return account_key.to_string());
});
});
return match account_key {
std::option::Option::Some(value) => serde_json::Value::String(value),
std::option::Option::None => serde_json::Value::Null,
};
}
fn contains(entries: &[&str], entry_code: &str) -> bool {
return entries.iter().any(|entry| return *entry == entry_code);
}
#[cfg(test)]
mod tests {
fn observation_with_payload(
surface_code: &str,
entry_code: &str,
event_family: kb_model::EventFamily,
transaction_failed: bool,
accounts: serde_json::Value,
parameters: serde_json::Value,
) -> kb_decoder_api::DecodedObservation {
return kb_decoder_api::DecodedObservation {
event_key: format!("{entry_code}:0"),
event: kb_model::DecodedProtocolEvent {
signature: kb_model::Signature("signature".to_string()),
slot: kb_model::Slot(42),
instruction_path: kb_model::InstructionPath("0".to_string()),
program_id: kb_model::ProgramId("program-id".to_string()),
protocol_code: kb_model::ProtocolCode("solana_native".to_string()),
surface_code: kb_model::SurfaceCode(surface_code.to_string()),
event_code: kb_model::EventCode(format!("{surface_code}.{entry_code}")),
event_name: kb_model::EventName(entry_code.to_string()),
event_family,
source_kind: kb_model::EventSourceKind::Instruction,
confidence: kb_model::DecoderConfidence::ManualExact,
},
payload_json: serde_json::json!({
"accounts": accounts,
"parameters": parameters,
}),
transaction_failed,
transaction_error: if transaction_failed {
std::option::Option::Some(serde_json::json!({"InstructionError": [0, "Custom"]}))
} else {
std::option::Option::None
},
observation_committed: !transaction_failed,
proof: kb_decoder_api::DecoderProof {
kind: kb_decoder_api::DecoderProofKind::Manual,
confidence: kb_model::DecoderConfidence::ManualExact,
evidence: std::vec!["fixture".to_string()],
},
};
}
fn observation(
surface_code: &str,
entry_code: &str,
transaction_failed: bool,
) -> kb_decoder_api::DecodedObservation {
return observation_with_payload(
surface_code,
entry_code,
kb_model::EventFamily::Lifecycle,
transaction_failed,
serde_json::json!([{"role": "target", "accountKey": "account"}]),
serde_json::json!({"value": 41}),
);
}
fn nonce_observation(
entry_code: &str,
family: kb_model::EventFamily,
transaction_failed: bool,
) -> kb_decoder_api::DecodedObservation {
return observation_with_payload(
super::SYSTEM_SURFACE,
entry_code,
family,
transaction_failed,
serde_json::json!([
{"role": "nonce_account", "accountKey": "nonce111"},
{"role": "recipient_account", "accountKey": "recipient111"},
{"role": "nonce_authority", "accountKey": "authority111"}
]),
serde_json::json!({
"authority": "new-authority111",
"lamports": 55,
}),
);
}
fn slashing_observation(
entry_code: &str,
transaction_failed: bool,
) -> kb_decoder_api::DecodedObservation {
let (family, accounts) = if entry_code == "duplicate_block_proof" {
(
kb_model::EventFamily::Audit,
serde_json::json!([
{"role": "proof_account", "accountKey": "proof111"},
{"role": "violation_report", "accountKey": "report111"},
{"role": "instructions_sysvar", "accountKey": kb_program_ids::SYSVAR_INSTRUCTIONS_PROGRAM_ID},
{"role": "system_program", "accountKey": kb_program_ids::SYSTEM_PROGRAM_ID}
]),
)
} else {
(
kb_model::EventFamily::Lifecycle,
serde_json::json!([
{"role": "violation_report", "accountKey": "report111"},
{"role": "destination", "accountKey": "destination111"}
]),
)
};
return observation_with_payload(
super::SLASHING_SURFACE,
entry_code,
family,
transaction_failed,
accounts,
serde_json::json!({
"proofType": "duplicate_block",
"violationSlot": 42,
"nodePubkey": "node111",
"reporter": "reporter111",
"destination": "destination111",
"penaltyAppliedByProgram": false,
}),
);
}
fn zk_observation(
entry_code: &str,
context_state_requested: bool,
transaction_failed: bool,
) -> kb_decoder_api::DecodedObservation {
let accounts = if entry_code == "close_context_state" {
serde_json::json!([
{"role": "proof_context_state", "accountKey": "context111"},
{"role": "lamport_destination", "accountKey": "destination111"},
{"role": "context_state_authority", "accountKey": "authority111"}
])
} else if context_state_requested {
serde_json::json!([
{"role": "proof_context_state", "accountKey": "context111"},
{"role": "context_state_authority", "accountKey": "authority111"}
])
} else {
serde_json::json!([])
};
let family = if entry_code == "close_context_state" {
kb_model::EventFamily::Lifecycle
} else {
kb_model::EventFamily::Audit
};
return observation_with_payload(
super::ZK_ELGAMAL_PROOF_SURFACE,
entry_code,
family,
transaction_failed,
accounts,
serde_json::json!({
"proofType": entry_code,
"contextStateRequested": context_state_requested,
}),
);
}
#[test]
fn successful_alt_lifecycle_creates_one_stable_output() {
let materializer = crate::LifecycleMaterializer;
let result = kb_materializer_api::EventMaterializer::materialize(
&materializer,
&observation(super::ADDRESS_LOOKUP_TABLE_SURFACE, "create_lookup_table", false),
);
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs.len(), 1);
assert_eq!(result.outputs[0].output_key, "address_lookup_table:create_lookup_table:0");
assert_eq!(result.outputs[0].payload_json["domain"], "address_lookup_table");
assert_eq!(result.outputs[0].payload_json["loaderSurface"], serde_json::Value::Null);
}
#[test]
fn successful_loader_lifecycle_creates_program_projection() {
let materializer = crate::LifecycleMaterializer;
let result = kb_materializer_api::EventMaterializer::materialize(
&materializer,
&observation(super::BPF_LOADER_UPGRADEABLE_SURFACE, "deploy_with_max_data_len", false),
);
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Inserted);
assert_eq!(
result.outputs[0].output_key,
"program_loader:solana_native_bpf_loader_upgradeable:deploy_with_max_data_len:0"
);
assert_eq!(result.outputs[0].payload_json["domain"], "program_loader");
assert_eq!(
result.outputs[0].payload_json["loaderSurface"],
super::BPF_LOADER_UPGRADEABLE_SURFACE
);
}
#[test]
fn successful_feature_revoke_creates_feature_gate_projection() {
let materializer = crate::LifecycleMaterializer;
let result = kb_materializer_api::EventMaterializer::materialize(
&materializer,
&observation(super::FEATURE_SURFACE, "revoke_pending_activation", false),
);
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs[0].output_key, "feature_gate:revoke_pending_activation:0");
assert_eq!(result.outputs[0].payload_json["domain"], "feature_gate");
}
#[test]
fn every_committed_system_account_operation_creates_a_lifecycle_projection() {
let materializer = crate::LifecycleMaterializer;
for (operation, role, transition) in [
("create_account", "new_account", "created_allocated_funded_and_assigned"),
(
"create_account_with_seed",
"new_account",
"derived_account_created_allocated_funded_and_assigned",
),
("allocate", "allocated_account", "account_data_space_allocated"),
("allocate_with_seed", "derived_account", "derived_account_data_space_allocated"),
(
"create_account_allow_prefund",
"new_account",
"prefunded_account_initialized_allocated_and_assigned",
),
] {
let observation = observation_with_payload(
super::SYSTEM_SURFACE,
operation,
kb_model::EventFamily::Lifecycle,
false,
serde_json::json!([
{"role": role, "accountKey": "target111"},
{"role": "funding_account", "accountKey": "funding111"},
{"role": "base_account", "accountKey": "base111"},
]),
serde_json::json!({
"lamports": 55,
"space": 64,
"owner": "owner111",
"seed": "seed",
}),
);
let result =
kb_materializer_api::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs[0].output_key, format!("system_account:{operation}:0"));
assert_eq!(result.outputs[0].payload_json["projection"]["targetAccount"], "target111");
assert_eq!(result.outputs[0].payload_json["projection"]["stateTransition"], transition);
}
}
#[test]
fn system_account_creation_does_not_duplicate_balance_changes() {
let materializer = crate::LifecycleMaterializer;
let observation = observation_with_payload(
super::SYSTEM_SURFACE,
"create_account",
kb_model::EventFamily::Lifecycle,
false,
serde_json::json!([
{"role": "funding_account", "accountKey": "funding111"},
{"role": "new_account", "accountKey": "target111"},
]),
serde_json::json!({"lamports": 55, "space": 64, "owner": "owner111"}),
);
let result =
kb_materializer_api::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.outputs[0].payload_json["projection"]["requestedLamports"], 55);
assert_eq!(
result.outputs[0].payload_json["projection"]["lamportTransferRepresentedByCoreBalanceChanges"],
true
);
assert!(result.outputs[0].payload_json["projection"].get("balanceChanges").is_none());
}
#[test]
fn every_committed_system_nonce_operation_creates_a_lifecycle_projection() {
let materializer = crate::LifecycleMaterializer;
for (entry_code, family, transition) in [
("initialize_nonce_account", kb_model::EventFamily::Lifecycle, "initialized"),
("advance_nonce_account", kb_model::EventFamily::Lifecycle, "nonce_advanced"),
("authorize_nonce_account", kb_model::EventFamily::Admin, "authority_changed"),
(
"upgrade_nonce_account",
kb_model::EventFamily::Lifecycle,
"legacy_version_upgraded",
),
(
"withdraw_nonce_account",
kb_model::EventFamily::Lifecycle,
"lamports_withdrawn_possible_closure",
),
] {
let observation = nonce_observation(entry_code, family, false);
assert!(kb_materializer_api::EventMaterializer::accepts_observation(
&materializer,
&observation,
));
let result =
kb_materializer_api::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs[0].payload_json["domain"], "durable_nonce_account");
assert_eq!(result.outputs[0].payload_json["projection"]["targetAccount"], "nonce111");
assert_eq!(
result.outputs[0].payload_json["projection"]["authorityAccount"],
"authority111"
);
assert_eq!(result.outputs[0].payload_json["projection"]["stateTransition"], transition);
}
}
#[test]
fn nonce_withdrawal_preserves_conditional_closure_semantics() {
let materializer = crate::LifecycleMaterializer;
let observation =
nonce_observation("withdraw_nonce_account", kb_model::EventFamily::Lifecycle, false);
let result =
kb_materializer_api::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.outputs[0].payload_json["projection"]["withdrawLamports"], 55);
assert_eq!(
result.outputs[0].payload_json["projection"]["lamportDestination"],
"recipient111"
);
assert_eq!(
result.outputs[0].payload_json["projection"]["closureSemantics"],
"closes_when_entire_balance_is_withdrawn"
);
assert_eq!(
result.outputs[0].payload_json["projection"]["transactionFinalAccountStateCaptured"]
.as_bool(),
std::option::Option::Some(false)
);
}
#[test]
fn zk_context_is_materialized_only_when_requested() {
let materializer = crate::LifecycleMaterializer;
let without_context = zk_observation("verify_zero_ciphertext", false, false);
assert!(!kb_materializer_api::EventMaterializer::accepts_observation(
&materializer,
&without_context,
));
let with_context = zk_observation("verify_zero_ciphertext", true, false);
assert!(kb_materializer_api::EventMaterializer::accepts_observation(
&materializer,
&with_context,
));
let result =
kb_materializer_api::EventMaterializer::materialize(&materializer, &with_context);
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs[0].payload_json["domain"], "zk_proof_context");
assert_eq!(result.outputs[0].payload_json["projection"]["targetAccount"], "context111");
assert_eq!(
result.outputs[0].payload_json["projection"]["stateTransition"],
"initialized_after_successful_runtime_verification"
);
assert_eq!(
result.outputs[0].payload_json["projection"]["proofMaterialized"].as_bool(),
std::option::Option::Some(false)
);
}
#[test]
fn zk_context_close_creates_a_stable_lifecycle_projection() {
let materializer = crate::LifecycleMaterializer;
let observation = zk_observation("close_context_state", false, false);
let result =
kb_materializer_api::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs[0].output_key, "zk_proof_context:close_context_state:0");
assert_eq!(
result.outputs[0].payload_json["projection"]["lamportDestination"],
"destination111"
);
assert_eq!(
result.outputs[0].payload_json["projection"]["stateTransition"],
"closed_lamports_reclaimed_owner_reset_to_system_program"
);
}
#[test]
fn successful_slashing_proof_initializes_violation_report_projection() {
let materializer = crate::LifecycleMaterializer;
let observation = slashing_observation("duplicate_block_proof", false);
assert!(kb_materializer_api::EventMaterializer::accepts_observation(
&materializer,
&observation,
));
let result =
kb_materializer_api::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Inserted);
assert_eq!(
result.outputs[0].output_key,
"slashing_violation_report:duplicate_block_proof:0"
);
assert_eq!(result.outputs[0].payload_json["domain"], "slashing_violation_report");
assert_eq!(result.outputs[0].payload_json["projection"]["targetAccount"], "report111");
assert_eq!(result.outputs[0].payload_json["projection"]["proofMaterialized"], false);
assert_eq!(result.outputs[0].payload_json["projection"]["penaltyAppliedByProgram"], false);
}
#[test]
fn successful_slashing_close_creates_stable_closure_projection() {
let materializer = crate::LifecycleMaterializer;
let observation = slashing_observation("close_violation_report", false);
let result =
kb_materializer_api::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Inserted);
assert_eq!(
result.outputs[0].payload_json["projection"]["stateTransition"],
"closed_after_minimum_retention_lamports_reclaimed_owner_reset_to_system_program"
);
assert_eq!(result.outputs[0].payload_json["projection"]["minimumRetentionEpochs"], 3);
assert_eq!(
result.outputs[0].payload_json["projection"]["lamportDestination"],
"destination111"
);
}
#[test]
fn historical_zk_token_proof_is_not_materialized() {
let materializer = crate::LifecycleMaterializer;
let observation = observation_with_payload(
"solana_native_zk_token_proof",
"close_context_state",
kb_model::EventFamily::Audit,
false,
serde_json::json!([]),
serde_json::json!({}),
);
assert!(!kb_materializer_api::EventMaterializer::accepts_observation(
&materializer,
&observation,
));
let result =
kb_materializer_api::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Ignored);
}
#[test]
fn loader_write_and_admin_events_are_not_lifecycle_projections() {
let materializer = crate::LifecycleMaterializer;
for entry_code in ["write", "set_authority", "transfer_authority"] {
let observation = observation(super::BPF_LOADER_UPGRADEABLE_SURFACE, entry_code, false);
assert!(!kb_materializer_api::EventMaterializer::accepts_observation(
&materializer,
&observation,
));
}
}
#[test]
fn unrelated_lifecycle_is_ignored() {
let materializer = crate::LifecycleMaterializer;
let result = kb_materializer_api::EventMaterializer::materialize(
&materializer,
&observation(super::SYSTEM_SURFACE, "transfer", false),
);
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Ignored);
}
#[test]
fn ata_lifecycle_is_owned_by_token_accounts_materializer_only() {
let mut observation = observation("spl_associated_token_account", "create", false);
observation.event.program_id =
kb_model::ProgramId(kb_program_ids::ASSOCIATED_TOKEN_PROGRAM_ID.to_string());
assert!(!kb_materializer_api::EventMaterializer::accepts_observation(
&crate::LifecycleMaterializer,
&observation,
));
let result = kb_materializer_api::EventMaterializer::materialize(
&crate::LifecycleMaterializer,
&observation,
);
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Ignored);
assert!(result.outputs.is_empty());
}
#[test]
fn failed_native_lifecycle_is_refused() {
let materializer = crate::LifecycleMaterializer;
let observations = [
observation(super::ADDRESS_LOOKUP_TABLE_SURFACE, "close_lookup_table", true),
observation(super::LOADER_V4_SURFACE, "deploy", true),
nonce_observation("authorize_nonce_account", kb_model::EventFamily::Admin, true),
zk_observation("verify_zero_ciphertext", true, true),
slashing_observation("duplicate_block_proof", true),
];
for observation in observations {
let policy =
kb_materializer_api::validate_materialization_policy(&materializer, &observation);
assert!(policy.is_err());
let direct =
kb_materializer_api::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(direct.status, kb_materializer_api::MaterializerOutcomeStatus::Refused);
}
}
#[test]
fn output_serialization_is_deterministic() {
let materializer = crate::LifecycleMaterializer;
let observation = zk_observation("verify_batched_range_proof_u64", true, false);
let first =
kb_materializer_api::EventMaterializer::materialize(&materializer, &observation);
let second =
kb_materializer_api::EventMaterializer::materialize(&materializer, &observation);
let first_json = match serde_json::to_string(&first) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
panic!("first lifecycle serialization failed: {error}")
},
};
let second_json = match serde_json::to_string(&second) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
panic!("second lifecycle serialization failed: {error}")
},
};
assert_eq!(first_json, second_json);
}
}