0.5.1-pre.002
This commit is contained in:
@@ -0,0 +1,574 @@
|
||||
// file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/use_campaign.rs
|
||||
// version: 2
|
||||
|
||||
//! Bounded Devnet availability probe for the current Metaplex `Use` surface.
|
||||
|
||||
/// Outcome of one bounded Devnet `Use` availability probe.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum DevnetMetaplexUseProbeStatus {
|
||||
/// The current `Use` instruction was confirmed with complete pipeline evidence.
|
||||
Confirmed,
|
||||
/// The exact current `Use` instruction was rejected by Devnet simulation.
|
||||
RuntimeUnavailable,
|
||||
}
|
||||
|
||||
/// Complete evidence produced by one current Metaplex `Use` availability probe.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct DevnetMetaplexUseProbeSummary {
|
||||
/// Fresh classic NFT prepared with two bounded `Multiple` uses.
|
||||
pub fixture: crate::DevnetMetaplexCreateMintCampaignSummary,
|
||||
/// Exact simulation evidence for the current `Use` instruction.
|
||||
pub simulation: crate::DevnetMetaplexTokenMetadataExecutionSummary,
|
||||
/// Confirmed execution evidence when the runtime accepts the current instruction.
|
||||
pub execution: std::option::Option<crate::DevnetMetaplexTokenMetadataExecutionSummary>,
|
||||
/// Runtime availability classification produced by this probe.
|
||||
pub status: crate::DevnetMetaplexUseProbeStatus,
|
||||
/// Remaining uses observed immediately before the `Use` probe.
|
||||
pub uses_before: u64,
|
||||
/// Remaining uses observed after confirmed execution, when available.
|
||||
pub uses_after: std::option::Option<u64>,
|
||||
/// Exact bounded simulation failure diagnostic when runtime-unavailable.
|
||||
pub unavailable_reason: std::option::Option<std::string::String>,
|
||||
}
|
||||
|
||||
/// Returns the exact ordered operations exercised by the bounded `Use` probe.
|
||||
pub fn metaplex_use_probe_operation_names() -> &'static [&'static str; 3] {
|
||||
return &["create", "mint", "use"];
|
||||
}
|
||||
|
||||
/// Executes one fresh classic NFT `Use` availability probe on Devnet.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn execute_devnet_metaplex_use_probe<S, O>(
|
||||
http_pool: &ks_onchain_transport::HttpEndpointPool,
|
||||
store: &S,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
workspace_root: &std::path::Path,
|
||||
options: &crate::MetaplexCreateFixturePreparationOptions,
|
||||
observer: &O,
|
||||
) -> ks_core::Result<crate::DevnetMetaplexUseProbeSummary>
|
||||
where
|
||||
S: ks_store::RawTransactionStore
|
||||
+ ks_store::CoreExtractionStore
|
||||
+ ks_store::DecodePipelineStore
|
||||
+ Sync,
|
||||
O: crate::SolanaExecutionObserver,
|
||||
{
|
||||
if options.asset_family != crate::MetaplexTokenMetadataAssetFamily::Nft {
|
||||
return std::result::Result::Err(ks_core::Error::config(
|
||||
"Metaplex Use probe requires the classic NFT fixture family",
|
||||
));
|
||||
}
|
||||
let fixture_options = options.clone().with_multiple_uses(2);
|
||||
let fixture = match crate::execute_devnet_metaplex_create_mint_campaign(
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&fixture_options,
|
||||
observer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let uses_before = match metadata_remaining_multiple_uses(
|
||||
fixture.mint.after.as_slice(),
|
||||
fixture.fixture.metadata.as_str(),
|
||||
2,
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if uses_before != 2 {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"metaplex_use_probe_initial_uses_invalid",
|
||||
format!("Metaplex Use probe requires remaining=2 before execution; got {uses_before}"),
|
||||
));
|
||||
}
|
||||
let operation = use_operation(&fixture.fixture);
|
||||
let mut simulation_request = crate::DevnetMetaplexTokenMetadataExecutionRequest::new(
|
||||
format!("metaplex-use-probe-simulation-{}", uuid::Uuid::new_v4()),
|
||||
operation.clone(),
|
||||
);
|
||||
simulation_request.query_role = fixture_options.query_role.clone();
|
||||
simulation_request.transaction_role = fixture_options.transaction_role.clone();
|
||||
let simulation = match crate::simulate_devnet_metaplex_token_metadata(
|
||||
http_pool,
|
||||
profile,
|
||||
workspace_root,
|
||||
&simulation_request,
|
||||
observer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if !simulation.simulation.success {
|
||||
return std::result::Result::Ok(crate::DevnetMetaplexUseProbeSummary {
|
||||
fixture,
|
||||
unavailable_reason: std::option::Option::Some(crate::simulation_failure_message(
|
||||
&simulation.simulation,
|
||||
)),
|
||||
simulation,
|
||||
execution: std::option::Option::None,
|
||||
status: crate::DevnetMetaplexUseProbeStatus::RuntimeUnavailable,
|
||||
uses_before,
|
||||
uses_after: std::option::Option::None,
|
||||
});
|
||||
}
|
||||
let decoders: std::vec::Vec<std::sync::Arc<dyn ks_lib::DcApiInstructionDecoder>> =
|
||||
std::vec![std::sync::Arc::new(ks_lib::DcMetadataMetaplexTokenMetadataDecoder)];
|
||||
let materializers: std::vec::Vec<std::sync::Arc<dyn ks_lib::MtApiEventMaterializer>> = std::vec![
|
||||
std::sync::Arc::new(ks_lib::MtAdminMaterializer),
|
||||
std::sync::Arc::new(ks_lib::MtComplianceAuditMaterializer),
|
||||
std::sync::Arc::new(ks_lib::MtLifecycleMaterializer),
|
||||
std::sync::Arc::new(ks_lib::MtMetadataMetaplexTokenMetadataMaterializer),
|
||||
std::sync::Arc::new(ks_lib::MtRiskMaterializer),
|
||||
];
|
||||
let mut execution_request = crate::DevnetMetaplexTokenMetadataExecutionRequest::new(
|
||||
format!("metaplex-use-probe-submit-{}", uuid::Uuid::new_v4()),
|
||||
operation,
|
||||
);
|
||||
execution_request.query_role = fixture_options.query_role.clone();
|
||||
execution_request.transaction_role = fixture_options.transaction_role.clone();
|
||||
execution_request.submit = true;
|
||||
execution_request.operator_confirmed = true;
|
||||
execution_request.materialize_after_confirmation = true;
|
||||
execution_request.post_validation_max_retries = 20;
|
||||
execution_request.postcondition_reads =
|
||||
metadata_read(fixture.fixture.metadata.as_str(), fixture_options.query_role.as_str());
|
||||
let execution = match crate::execute_devnet_metaplex_token_metadata(
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
observer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let confirmation_slot = match validate_confirmed_use_execution(&execution) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let uses_after = match metadata_remaining_multiple_uses(
|
||||
execution.after.as_slice(),
|
||||
fixture.fixture.metadata.as_str(),
|
||||
2,
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if uses_after != 1 {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"metaplex_use_probe_remaining_uses_invalid",
|
||||
format!(
|
||||
"confirmed Metaplex Use must decrement remaining uses from 2 to 1; got {uses_after}"
|
||||
),
|
||||
));
|
||||
}
|
||||
let token_after = match crate::read_token_account_at_or_after(
|
||||
http_pool,
|
||||
fixture_options.query_role.as_str(),
|
||||
fixture.fixture.token_account.as_str(),
|
||||
std::option::Option::Some(confirmation_slot),
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(std::option::Option::Some(value)) => value,
|
||||
std::result::Result::Ok(std::option::Option::None) => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"metaplex_use_probe_token_missing",
|
||||
"classic NFT token account disappeared after confirmed Use",
|
||||
));
|
||||
},
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if let std::result::Result::Err(error) = crate::validate_classic_fixture_token_account_state(
|
||||
&token_after,
|
||||
fixture.fixture.mint.as_str(),
|
||||
fixture.fixture.authority.as_str(),
|
||||
1,
|
||||
spl_token_interface::state::AccountState::Initialized,
|
||||
) {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"metaplex_use_probe_token_state_invalid",
|
||||
format!("confirmed Metaplex Use left invalid NFT token state: {error}"),
|
||||
));
|
||||
}
|
||||
return std::result::Result::Ok(crate::DevnetMetaplexUseProbeSummary {
|
||||
fixture,
|
||||
simulation,
|
||||
execution: std::option::Option::Some(execution),
|
||||
status: crate::DevnetMetaplexUseProbeStatus::Confirmed,
|
||||
uses_before,
|
||||
uses_after: std::option::Option::Some(uses_after),
|
||||
unavailable_reason: std::option::Option::None,
|
||||
});
|
||||
}
|
||||
|
||||
fn use_operation(
|
||||
fixture: &crate::MetaplexCreateFixturePreparationSummary,
|
||||
) -> ks_lib::ExMetaplexTokenMetadataOperation {
|
||||
return ks_lib::ExMetaplexTokenMetadataOperation::Use {
|
||||
authority: ks_lib::MdPubkey(fixture.authority.clone()),
|
||||
delegate_record: std::option::Option::None,
|
||||
token: std::option::Option::Some(ks_lib::MdPubkey(fixture.token_account.clone())),
|
||||
mint: ks_lib::MdPubkey(fixture.mint.clone()),
|
||||
metadata: ks_lib::MdPubkey(fixture.metadata.clone()),
|
||||
edition: std::option::Option::Some(ks_lib::MdPubkey(fixture.master_edition.clone())),
|
||||
payer: ks_lib::MdPubkey(fixture.authority.clone()),
|
||||
system_program: ks_lib::MdPubkey(ks_program_ids::SYSTEM_PROGRAM_ID.to_string()),
|
||||
sysvar_instructions: ks_lib::MdPubkey(
|
||||
ks_program_ids::SYSVAR_INSTRUCTIONS_PROGRAM_ID.to_string(),
|
||||
),
|
||||
spl_token_program: std::option::Option::Some(ks_lib::MdPubkey(
|
||||
ks_program_ids::SPL_TOKEN_PROGRAM_ID.to_string(),
|
||||
)),
|
||||
authorization_rules_program: std::option::Option::None,
|
||||
authorization_rules: std::option::Option::None,
|
||||
args: mpl_token_metadata::types::UseArgs::V1 {
|
||||
authorization_data: std::option::Option::None,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn metadata_read(
|
||||
metadata: &str,
|
||||
query_role: &str,
|
||||
) -> std::vec::Vec<ks_pipeline::MetaplexTokenMetadataStatefulReadRequest> {
|
||||
return std::vec![ks_pipeline::MetaplexTokenMetadataStatefulReadRequest {
|
||||
query_role: query_role.to_string(),
|
||||
account: ks_lib::MdPubkey(metadata.to_string()),
|
||||
kind: ks_pipeline::MetaplexTokenMetadataAccountKind::Metadata,
|
||||
min_context_slot: std::option::Option::None,
|
||||
max_data_bytes: ks_pipeline::MAX_METAPLEX_TOKEN_METADATA_ACCOUNT_BYTES,
|
||||
}];
|
||||
}
|
||||
|
||||
fn metadata_remaining_multiple_uses(
|
||||
snapshots: &[ks_pipeline::MetaplexTokenMetadataStatefulReadResult],
|
||||
metadata: &str,
|
||||
expected_total: u64,
|
||||
) -> ks_core::Result<u64> {
|
||||
let snapshot = match snapshots.iter().find(|value| {
|
||||
return value.snapshot.account.0.as_str() == metadata
|
||||
&& value.snapshot.account_kind == "metadata";
|
||||
}) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"metaplex_use_probe_metadata_snapshot_missing",
|
||||
format!("metadata snapshot {metadata} is missing from the Use probe"),
|
||||
));
|
||||
},
|
||||
};
|
||||
let uses_value = match snapshot.snapshot.payload_json.get("uses") {
|
||||
std::option::Option::Some(value) if !value.is_null() => value.clone(),
|
||||
_ => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"metaplex_use_probe_uses_missing",
|
||||
"metadata snapshot has no Uses state",
|
||||
));
|
||||
},
|
||||
};
|
||||
let uses = match serde_json::from_value::<mpl_token_metadata::types::Uses>(uses_value) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"metaplex_use_probe_uses_decode_failed",
|
||||
format!("metadata Uses projection is invalid: {error}"),
|
||||
));
|
||||
},
|
||||
};
|
||||
if uses.use_method != mpl_token_metadata::types::UseMethod::Multiple
|
||||
|| uses.total != expected_total
|
||||
|| uses.remaining > uses.total
|
||||
{
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"metaplex_use_probe_uses_state_invalid",
|
||||
format!(
|
||||
"metadata Uses state must remain Multiple with total={expected_total}; got method={:?}, remaining={}, total={}",
|
||||
uses.use_method, uses.remaining, uses.total
|
||||
),
|
||||
));
|
||||
}
|
||||
return std::result::Result::Ok(uses.remaining);
|
||||
}
|
||||
|
||||
fn validate_confirmed_use_execution(
|
||||
execution: &crate::DevnetMetaplexTokenMetadataExecutionSummary,
|
||||
) -> ks_core::Result<u64> {
|
||||
let confirmation = match execution.confirmation.as_ref() {
|
||||
std::option::Option::Some(value)
|
||||
if matches!(
|
||||
value.status,
|
||||
ks_lib::ExApiExecutionConfirmationStatus::Confirmed
|
||||
| ks_lib::ExApiExecutionConfirmationStatus::Finalized
|
||||
) =>
|
||||
{
|
||||
value
|
||||
},
|
||||
std::option::Option::Some(value) => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"metaplex_use_probe_confirmation_incomplete",
|
||||
format!("Metaplex Use stopped at {:?}", value.status),
|
||||
));
|
||||
},
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"metaplex_use_probe_confirmation_missing",
|
||||
"Metaplex Use has no confirmation evidence",
|
||||
));
|
||||
},
|
||||
};
|
||||
let diagnostic = match execution.post_execution.as_ref() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"metaplex_use_probe_post_execution_missing",
|
||||
"Metaplex Use has no post-execution diagnostic",
|
||||
));
|
||||
},
|
||||
};
|
||||
if !diagnostic.canonical_inserted
|
||||
|| !diagnostic.core_extracted
|
||||
|| !diagnostic.decode_replayed
|
||||
|| !diagnostic.materialized
|
||||
|| execution.materializations.is_empty()
|
||||
|| execution.materialized_snapshots.is_empty()
|
||||
{
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"metaplex_use_probe_post_execution_incomplete",
|
||||
format!(
|
||||
"Metaplex Use post-execution incomplete: signature={}, slot={:?}, canonical_inserted={}, core_extracted={}, decode_replayed={}, materialized={}, materialization_rows={}, materialized_snapshots={}, diagnostics={:?}",
|
||||
confirmation.signature.0,
|
||||
confirmation.slot,
|
||||
diagnostic.canonical_inserted,
|
||||
diagnostic.core_extracted,
|
||||
diagnostic.decode_replayed,
|
||||
diagnostic.materialized,
|
||||
execution.materializations.len(),
|
||||
execution.materialized_snapshots.len(),
|
||||
diagnostic.diagnostics
|
||||
),
|
||||
));
|
||||
}
|
||||
let idempotence = match execution.idempotence_replay.as_ref() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"metaplex_use_probe_idempotence_missing",
|
||||
"Metaplex Use has no idempotence replay evidence",
|
||||
));
|
||||
},
|
||||
};
|
||||
if idempotence.failed_inputs != 0
|
||||
|| idempotence.processing_error_inputs != 0
|
||||
|| idempotence.processors.iter().any(|processor| {
|
||||
return processor.failed != 0
|
||||
|| processor.processing_errors != 0
|
||||
|| processor.materialized_outputs != 0
|
||||
|| processor.materialization_refused != 0;
|
||||
})
|
||||
{
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"metaplex_use_probe_idempotence_failed",
|
||||
"Metaplex Use second replay is not idempotent",
|
||||
));
|
||||
}
|
||||
return match confirmation.slot {
|
||||
std::option::Option::Some(value) => std::result::Result::Ok(value),
|
||||
std::option::Option::None => std::result::Result::Err(ks_core::Error::new(
|
||||
"metaplex_use_probe_confirmation_slot_missing",
|
||||
"Metaplex Use confirmation has no slot",
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn use_probe_contract_is_exactly_create_mint_then_use() {
|
||||
assert_eq!(crate::metaplex_use_probe_operation_names(), &["create", "mint", "use"]);
|
||||
assert_ne!(
|
||||
crate::DevnetMetaplexUseProbeStatus::Confirmed,
|
||||
crate::DevnetMetaplexUseProbeStatus::RuntimeUnavailable,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn optional_devnet_metaplex_use_probe_from_env() {
|
||||
if std::env::var("KB_DEVNET_METAPLEX_USE_PROBE_TEST").ok().as_deref()
|
||||
!= std::option::Option::Some("1")
|
||||
{
|
||||
return;
|
||||
}
|
||||
assert_eq!(
|
||||
std::env::var("KB_DEVNET_METAPLEX_OPERATOR_CONFIRMED").ok().as_deref(),
|
||||
std::option::Option::Some("1"),
|
||||
"set KB_DEVNET_METAPLEX_OPERATOR_CONFIRMED=1 before submitting an accepted Use probe"
|
||||
);
|
||||
let workspace_root = match std::path::Path::new(env!("CARGO_MANIFEST_DIR")).parent() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("workspace root cannot be resolved"),
|
||||
};
|
||||
let config_path = match std::env::var("KB_DEVNET_CONFIG_PATH") {
|
||||
std::result::Result::Ok(value) => {
|
||||
let path = std::path::PathBuf::from(value);
|
||||
if path.is_absolute() { path } else { workspace_root.join(path) }
|
||||
},
|
||||
std::result::Result::Err(_) => workspace_root.join("config/example.config.json"),
|
||||
};
|
||||
let config = ks_config::read_config_json_file_with_environment(
|
||||
config_path.as_path(),
|
||||
workspace_root,
|
||||
)
|
||||
.unwrap_or_else(|error| panic!("Devnet configuration loading failed: {error}"));
|
||||
let profile_name = std::env::var("KB_DEVNET_PROFILE").ok();
|
||||
let mut profile = crate::resolve_demo_devnet_profile(&config, profile_name.as_deref())
|
||||
.unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}"));
|
||||
let database_url = std::env::var("KB_POSTGRES_TEST_URL")
|
||||
.unwrap_or_else(|error| panic!("KB_POSTGRES_TEST_URL is required: {error}"));
|
||||
profile.database.backend = "postgres".to_string();
|
||||
profile.database.postgres.url = database_url;
|
||||
let http_pool = ks_onchain_transport::HttpEndpointPool::from_profile(&profile)
|
||||
.unwrap_or_else(|error| panic!("HTTP pool initialization failed: {error}"));
|
||||
let store_options = ks_store::PostgresStoreOptions::new(
|
||||
profile.database.postgres.url.clone(),
|
||||
profile.database.postgres.max_connections,
|
||||
profile.database.postgres.connect_timeout_ms,
|
||||
false,
|
||||
)
|
||||
.unwrap_or_else(|error| panic!("PostgreSQL options failed: {error}"));
|
||||
let store = ks_store::PostgresStore::connect(store_options)
|
||||
.await
|
||||
.unwrap_or_else(|error| panic!("PostgreSQL connection failed: {error}"));
|
||||
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
|
||||
panic!("PostgreSQL schema initialization failed: {error}");
|
||||
}
|
||||
let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
|
||||
let wallet_dir = if configured_wallet_dir.is_absolute() {
|
||||
configured_wallet_dir
|
||||
} else {
|
||||
workspace_root.join(configured_wallet_dir)
|
||||
};
|
||||
let options = crate::MetaplexCreateFixturePreparationOptions::new(wallet_dir)
|
||||
.with_asset_family(crate::MetaplexTokenMetadataAssetFamily::Nft);
|
||||
let summary = crate::execute_devnet_metaplex_use_probe(
|
||||
&http_pool,
|
||||
&store,
|
||||
&profile,
|
||||
workspace_root,
|
||||
&options,
|
||||
&crate::NoopSolanaExecutionObserver,
|
||||
)
|
||||
.await
|
||||
.unwrap_or_else(|error| panic!("Metaplex Use Devnet probe failed: {error}"));
|
||||
println!(
|
||||
"METAPLEX_USE_PROBE_FIXTURE mint={} metadata={} master_edition={} token={} uses_before={}",
|
||||
summary.fixture.fixture.mint,
|
||||
summary.fixture.fixture.metadata,
|
||||
summary.fixture.fixture.master_edition,
|
||||
summary.fixture.fixture.token_account,
|
||||
summary.uses_before,
|
||||
);
|
||||
println!(
|
||||
"METAPLEX_USE_PROBE_SIMULATION success={} context_slot={} logs={} error={}",
|
||||
summary.simulation.simulation.success,
|
||||
summary.simulation.simulation_context_slot,
|
||||
summary.simulation.simulation.logs.len(),
|
||||
serde_json::to_string(&summary.simulation.simulation.error).unwrap_or_else(
|
||||
|error| panic!("Use simulation error serialization failed: {error}")
|
||||
),
|
||||
);
|
||||
match summary.status {
|
||||
crate::DevnetMetaplexUseProbeStatus::RuntimeUnavailable => {
|
||||
println!(
|
||||
"METAPLEX_USE_PROBE_STATUS status=runtime_unavailable uses_before={} uses_after=null reason={}",
|
||||
summary.uses_before,
|
||||
serde_json::to_string(&summary.unavailable_reason).unwrap_or_else(
|
||||
|error| panic!("Use unavailable reason serialization failed: {error}")
|
||||
),
|
||||
);
|
||||
println!(
|
||||
"METAPLEX_USE_PROBE_LOGS {}",
|
||||
serde_json::to_string(&summary.simulation.simulation.logs).unwrap_or_else(
|
||||
|error| panic!("Use simulation log serialization failed: {error}")
|
||||
),
|
||||
);
|
||||
},
|
||||
crate::DevnetMetaplexUseProbeStatus::Confirmed => {
|
||||
let execution = summary
|
||||
.execution
|
||||
.as_ref()
|
||||
.unwrap_or_else(|| panic!("confirmed Use probe execution is missing"));
|
||||
let confirmation = execution
|
||||
.confirmation
|
||||
.as_ref()
|
||||
.unwrap_or_else(|| panic!("confirmed Use probe confirmation is missing"));
|
||||
println!(
|
||||
"METAPLEX_USE_PROBE_STATUS status=confirmed uses_before={} uses_after={}",
|
||||
summary.uses_before,
|
||||
summary
|
||||
.uses_after
|
||||
.unwrap_or_else(|| panic!("confirmed Use uses_after is missing")),
|
||||
);
|
||||
println!(
|
||||
"METAPLEX_USE_PROBE_STEP operation={} signature={} slot={:?} materializations={}",
|
||||
execution.plan.operation_code,
|
||||
confirmation.signature.0,
|
||||
confirmation.slot,
|
||||
execution.materializations.len(),
|
||||
);
|
||||
println!("METAPLEX_USE_PROBE_EVIDENCE {}", execution_evidence_json(execution),);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn execution_evidence_json(
|
||||
execution: &crate::DevnetMetaplexTokenMetadataExecutionSummary,
|
||||
) -> serde_json::Value {
|
||||
let confirmation = execution.confirmation.as_ref();
|
||||
let post_execution = execution.post_execution.as_ref();
|
||||
let idempotence_clean = execution.idempotence_replay.as_ref().is_some_and(|replay| {
|
||||
return replay.failed_inputs == 0
|
||||
&& replay.processing_error_inputs == 0
|
||||
&& replay.processors.iter().all(|processor| {
|
||||
return processor.failed == 0
|
||||
&& processor.processing_errors == 0
|
||||
&& processor.materialized_outputs == 0
|
||||
&& processor.materialization_refused == 0;
|
||||
});
|
||||
});
|
||||
return serde_json::json!({
|
||||
"operation": execution.plan.operation_code.as_str(),
|
||||
"cluster": execution.cluster,
|
||||
"genesisHash": execution.genesis_hash.as_str(),
|
||||
"simulationContextSlot": execution.simulation_context_slot,
|
||||
"simulationSuccess": execution.simulation.success,
|
||||
"simulationLogCount": execution.simulation.logs.len(),
|
||||
"messageHash": execution.readiness.message_hash.as_str(),
|
||||
"feeLamports": execution.fee.fee_lamports,
|
||||
"signature": confirmation.map(|value| return value.signature.0.clone()),
|
||||
"confirmationStatus": confirmation.map(|value| return format!("{:?}", value.status)),
|
||||
"confirmationSlot": confirmation.and_then(|value| return value.slot),
|
||||
"beforeSnapshots": execution.before.len(),
|
||||
"afterSnapshots": execution.after.len(),
|
||||
"canonicalHydration": post_execution.is_some_and(|value| return value.canonical_inserted),
|
||||
"coreExtraction": post_execution.is_some_and(|value| return value.core_extracted),
|
||||
"decodeReplay": post_execution.is_some_and(|value| return value.decode_replayed),
|
||||
"materialized": post_execution.is_some_and(|value| return value.materialized),
|
||||
"instructionMaterializations": execution.materializations.len(),
|
||||
"materializedSnapshots": execution.materialized_snapshots.len(),
|
||||
"idempotenceReplayClean": idempotence_clean,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user