commit 8c7399b3a322fb837c218d7c89952f0933b427b6
parent d3f18e4000ce1cd733321bde1a06063a4004a2fe
Author: triesap <tyson@radroots.org>
Date: Tue, 14 Jul 2026 11:12:43 +0000
cli: align with sealed event runtime APIs
- replace direct event envelope construction with typed constructors
- read sealed draft and event data through accessors
- update proxy publish tests for raw signed event JSON requests
- preserve u64 event timestamps in CLI output views
Diffstat:
10 files changed, 126 insertions(+), 90 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -3671,6 +3671,7 @@ version = "0.1.0-alpha.2"
dependencies = [
"hex",
"radroots_core",
+ "secp256k1",
"serde",
"serde_json",
"sha2",
diff --git a/src/ops/exec/basket.rs b/src/ops/exec/basket.rs
@@ -1315,9 +1315,9 @@ fn invalid_input(operation_id: &str, message: String) -> OperationAdapterError {
mod tests {
use std::path::{Path, PathBuf};
- use radroots_event::RadrootsEventEnvelope;
use radroots_event::ids::RadrootsListingAddress;
use radroots_event::kinds::{KIND_FARM, KIND_LISTING};
+ use radroots_event::{RadrootsEventEnvelope, RadrootsEventEnvelopeParts};
use radroots_replica_sync::{RadrootsReplicaIngestOutcome, radroots_replica_ingest_event};
use radroots_secret_vault::RadrootsSecretBackend;
use radroots_sql_core::{SqlExecutor, SqliteExecutor};
@@ -1770,7 +1770,7 @@ mod tests {
fn seed_current_listing(config: &RuntimeConfig) {
crate::runtime::store::init(config).expect("store init");
let (seller_pubkey, listing_id) = listing_addr_parts(LISTING_ADDR);
- let event = RadrootsEventEnvelope {
+ let event = RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts {
id: "2".repeat(64),
author: seller_pubkey.clone(),
created_at: 1,
@@ -1818,7 +1818,8 @@ mod tests {
],
content: "# Market Eggs".to_owned(),
sig: "f".repeat(128),
- };
+ })
+ .expect("seed listing event");
let executor =
SqliteExecutor::open(&config.local.replica_store_path).expect("open replica");
assert_eq!(
diff --git a/src/runtime/farm.rs b/src/runtime/farm.rs
@@ -1180,10 +1180,10 @@ fn sdk_enqueued_publish_view(
fn sdk_plan_event_view(plan: &FarmPublishPlan) -> FarmPublishEventView {
FarmPublishEventView {
- kind: plan.frozen_draft.kind,
- author: plan.frozen_draft.expected_pubkey.clone(),
- content: plan.frozen_draft.content.clone(),
- tags: plan.frozen_draft.tags.clone(),
+ kind: plan.frozen_draft.kind_u32(),
+ author: plan.frozen_draft.expected_pubkey_str().to_owned(),
+ content: plan.frozen_draft.content().to_owned(),
+ tags: plan.frozen_draft.tags_as_vec(),
event_id: Some(plan.expected_event_id.as_str().to_owned()),
event_addr: Some(plan.farm_addr.as_str().to_owned()),
}
diff --git a/src/runtime/listing.rs b/src/runtime/listing.rs
@@ -10,7 +10,6 @@ use radroots_core::{
RadrootsCoreDiscountThreshold, RadrootsCoreDiscountValue, RadrootsCoreMoney,
RadrootsCorePercent, RadrootsCoreQuantity, RadrootsCoreQuantityPrice, RadrootsCoreUnit,
};
-use radroots_event::RadrootsEventEnvelope;
use radroots_event::contract::RadrootsActorRole;
use radroots_event::farm::RadrootsFarmRef;
use radroots_event::ids::{RadrootsDTag, RadrootsInventoryBinId};
@@ -21,6 +20,7 @@ use radroots_event::listing::{
RadrootsListingStatus,
};
use radroots_event::trade_validation::RadrootsTradeValidationListingError;
+use radroots_event::{RadrootsEventEnvelope, RadrootsEventEnvelopeParts};
use radroots_event_codec::d_tag::is_d_tag_base64url;
use radroots_event_codec::listing::encode::to_wire_parts_with_kind;
use radroots_replica_store::ReplicaSql;
@@ -588,14 +588,20 @@ pub fn validate(
issue,
));
}
- let event = RadrootsEventEnvelope {
- id: String::new(),
- author: canonical.seller_pubkey.clone(),
- created_at: 0,
- kind: KIND_LISTING_DRAFT,
- tags: parts.tags,
- content: parts.content,
- sig: String::new(),
+ let event = match listing_validation_event(&canonical, parts) {
+ Ok(event) => event,
+ Err(error) => {
+ return Ok(invalid_validation_view(
+ args.file.as_path(),
+ &parsed,
+ &context,
+ ListingValidationIssueView {
+ field: "listing".to_owned(),
+ message: format!("invalid listing event envelope: {error}"),
+ line: None,
+ },
+ ));
+ }
};
match validate_listing_event(&event) {
Ok(_) => Ok(ListingValidateView {
@@ -1157,14 +1163,15 @@ fn listing_ready_issues(
}];
}
};
- let event = RadrootsEventEnvelope {
- id: String::new(),
- author: canonical.seller_pubkey.clone(),
- created_at: 0,
- kind: KIND_LISTING_DRAFT,
- tags: parts.tags,
- content: parts.content,
- sig: String::new(),
+ let event = match listing_validation_event(canonical, parts) {
+ Ok(event) => event,
+ Err(error) => {
+ return vec![ListingValidationIssueView {
+ field: "listing".to_owned(),
+ message: format!("invalid listing event envelope: {error}"),
+ line: None,
+ }];
+ }
};
match validate_listing_event(&event) {
Ok(_) => Vec::new(),
@@ -1947,17 +1954,33 @@ fn sdk_enqueued_publish_view(
fn sdk_plan_event_view(plan: &ListingPublishPlan) -> ListingMutationEventView {
ListingMutationEventView {
- kind: plan.frozen_draft.kind,
- author: plan.frozen_draft.expected_pubkey.clone(),
- created_at: Some(plan.frozen_draft.created_at),
- content: plan.frozen_draft.content.clone(),
- tags: plan.frozen_draft.tags.clone(),
+ kind: plan.frozen_draft.kind_u32(),
+ author: plan.frozen_draft.expected_pubkey_str().to_owned(),
+ created_at: Some(plan.frozen_draft.created_at_u64()),
+ content: plan.frozen_draft.content().to_owned(),
+ tags: plan.frozen_draft.tags_as_vec(),
event_id: Some(plan.expected_event_id.as_str().to_owned()),
signature: None,
event_addr: plan.public_listing_addr.as_str().to_owned(),
}
}
+fn listing_validation_event(
+ canonical: &CanonicalListingDraft,
+ parts: radroots_event::wire::RadrootsNip01EventWireParts,
+) -> Result<RadrootsEventEnvelope, String> {
+ RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts {
+ id: "0".repeat(64),
+ author: canonical.seller_pubkey.clone(),
+ created_at: 0,
+ kind: KIND_LISTING_DRAFT,
+ tags: parts.tags,
+ content: parts.content,
+ sig: "0".repeat(128),
+ })
+ .map_err(|error| error.to_string())
+}
+
fn sdk_push_event_for_listing<'a>(
enqueue: &ListingEnqueueReceipt,
push: &'a PushOutboxReceipt,
diff --git a/src/runtime/order.rs b/src/runtime/order.rs
@@ -1734,8 +1734,9 @@ fn order_event_list_entry_from_event(
let event = radroots_event_from_nostr(event);
let envelope = order_request_from_event(&event)
.map_err(|error| RuntimeError::Config(format!("decode order request event: {error}")))?;
+ let event_tags = event.tags_as_vec();
let context =
- order_event_context_from_tags(RadrootsOrderEventType::OrderRequested, &event.tags)
+ order_event_context_from_tags(RadrootsOrderEventType::OrderRequested, &event_tags)
.map_err(|error| RuntimeError::Config(format!("decode order request tags: {error}")))?;
if context.counterparty_pubkey != seller_pubkey
@@ -1747,13 +1748,13 @@ fn order_event_list_entry_from_event(
}
let listing_event_id = context.listing_event.as_ref().map(|event| event.id.clone());
- let created_at_unix = u64::from(event.created_at);
+ let created_at_unix = event.created_at_u64();
Ok(OrderEventListEntryView {
id: envelope.order_id.clone(),
state: "requested".to_owned(),
- event_id: Some(event.id),
- event_kind: Some(event.kind),
+ event_id: Some(event.id_str().to_owned()),
+ event_kind: Some(event.kind_u32()),
listing_lookup: None,
listing_addr: Some(envelope.listing_addr),
listing_event_id,
@@ -4718,8 +4719,9 @@ fn order_submit_request_from_event(
let event = radroots_event_from_nostr(event);
let envelope = order_request_from_event(&event)
.map_err(|error| RuntimeError::Config(format!("decode order request event: {error}")))?;
+ let event_tags = event.tags_as_vec();
let context =
- order_event_context_from_tags(RadrootsOrderEventType::OrderRequested, &event.tags)
+ order_event_context_from_tags(RadrootsOrderEventType::OrderRequested, &event_tags)
.map_err(|error| RuntimeError::Config(format!("decode order request tags: {error}")))?;
if envelope.order_id != loaded.document.order.order_id
@@ -4743,12 +4745,12 @@ fn order_submit_request_from_event(
"order request listing address is outside seller authority".to_owned(),
));
}
- let payload = canonicalize_order_request_for_signer(envelope.payload, event.author.as_str())
+ let payload = canonicalize_order_request_for_signer(envelope.payload, event.author_str())
.map_err(|error| RuntimeError::Config(format!("canonicalize order request: {error}")))?;
let listing_event_id = context.listing_event.as_ref().map(|event| event.id.clone());
Ok(ResolvedOrderSubmitRequest {
- request_event_id: event.id,
+ request_event_id: event.id_str().to_owned(),
listing_event_id,
payload,
})
diff --git a/src/runtime/sync.rs b/src/runtime/sync.rs
@@ -1783,11 +1783,11 @@ mod tests {
use radroots_event::list_set::RadrootsListSet;
use radroots_event::plot::RadrootsPlot;
use radroots_event::profile::{RadrootsProfile, RadrootsProfileType};
+ use radroots_event::wire::RadrootsNip01EventWireParts;
use radroots_event_codec::farm::encode as farm_encode;
use radroots_event_codec::list_set::encode as list_set_encode;
use radroots_event_codec::plot::encode as plot_encode;
use radroots_event_codec::profile::encode as profile_encode;
- use radroots_event_codec::wire::WireEventParts;
use radroots_identity::RadrootsIdentity;
use radroots_nostr::prelude::{
RadrootsNostrEvent, RadrootsNostrFilter, RadrootsNostrTimestamp, radroots_nostr_build_event,
@@ -2929,7 +2929,7 @@ mod tests {
let events = vec![
signed_event(
&seller,
- WireEventParts {
+ RadrootsNip01EventWireParts {
kind: KIND_POST,
content: "hello".to_owned(),
tags: Vec::new(),
@@ -2937,7 +2937,7 @@ mod tests {
),
signed_event(
&seller,
- WireEventParts {
+ RadrootsNip01EventWireParts {
kind: KIND_LISTING,
content: "not a listing".to_owned(),
tags: Vec::new(),
@@ -3152,7 +3152,10 @@ mod tests {
.expect("signed event")
}
- fn signed_event(identity: &RadrootsIdentity, parts: WireEventParts) -> RadrootsNostrEvent {
+ fn signed_event(
+ identity: &RadrootsIdentity,
+ parts: RadrootsNip01EventWireParts,
+ ) -> RadrootsNostrEvent {
radroots_nostr_build_event(parts.kind, parts.content, parts.tags)
.expect("event builder")
.sign_with_keys(identity.keys())
diff --git a/src/runtime/validation_receipt.rs b/src/runtime/validation_receipt.rs
@@ -107,7 +107,7 @@ pub struct ValidationReceiptResourceView {
pub struct ValidationReceiptEventView {
pub id: String,
pub author: String,
- pub created_at: u32,
+ pub created_at: u64,
pub kind: u32,
pub sig: String,
pub tags: Vec<Vec<String>>,
@@ -179,7 +179,7 @@ pub struct ValidationReceiptSummaryView {
pub receipt_event_id: String,
pub order_id: String,
pub author: String,
- pub created_at: u32,
+ pub created_at: u64,
pub receipt_type: String,
pub result: String,
pub proof_system: String,
@@ -394,7 +394,7 @@ fn inspected_event_view(
failed_transport_targets: Vec<TransportTargetFailureView>,
relay_reason_code: Option<String>,
) -> ValidationReceiptInspectionView {
- let event_id = sdk_receipt.event.id.clone();
+ let event_id = sdk_receipt.event.id_str().to_owned();
let order_id = sdk_receipt.tags.order_id.clone();
let proof_verification = proof_verification_view_for_receipt(
&sdk_receipt.receipt,
@@ -457,8 +457,8 @@ fn invalid_inspected_event_view(
) -> ValidationReceiptInspectionView {
ValidationReceiptInspectionView {
state: "invalid".to_owned(),
- resource: Some(validation_receipt_resource(&invalid.event.id)),
- receipt_event_id: Some(invalid.event.id.clone()),
+ resource: Some(validation_receipt_resource(invalid.event.id_str())),
+ receipt_event_id: Some(invalid.event.id_str().to_owned()),
order_id: None,
validation_state: "invalid".to_owned(),
proof_verification: None,
@@ -496,8 +496,8 @@ fn list_from_sdk_receipt(
);
if proof_state_is_invalid(proof_verification.state.as_str()) {
invalid_receipts.push(ValidationReceiptInvalidCandidateView {
- receipt_event_id: sdk_event.event.id,
- kind: sdk_event.event.kind,
+ receipt_event_id: sdk_event.event.id_str().to_owned(),
+ kind: sdk_event.event.kind_u32(),
reason_code: proof_verification
.reason_code
.clone()
@@ -718,13 +718,13 @@ fn validation_receipt_resource(id: &str) -> ValidationReceiptResourceView {
fn event_view(event: radroots_event::RadrootsEventEnvelope) -> ValidationReceiptEventView {
ValidationReceiptEventView {
- id: event.id,
- author: event.author,
- created_at: event.created_at,
- kind: event.kind,
- sig: event.sig,
- tags: event.tags,
- content: event.content,
+ id: event.id_str().to_owned(),
+ author: event.author_str().to_owned(),
+ created_at: event.created_at_u64(),
+ kind: event.kind_u32(),
+ sig: event.sig_str().to_owned(),
+ tags: event.tags_as_vec(),
+ content: event.content().to_owned(),
}
}
@@ -1189,8 +1189,8 @@ fn invalid_candidate_view(
candidate: TradeValidationReceiptInvalidCandidate,
) -> ValidationReceiptInvalidCandidateView {
ValidationReceiptInvalidCandidateView {
- receipt_event_id: candidate.event.id,
- kind: candidate.event.kind,
+ receipt_event_id: candidate.event.id_str().to_owned(),
+ kind: candidate.event.kind_u32(),
reason_code: candidate.reason_code,
reason: candidate.reason,
proof_verification: None,
@@ -1269,11 +1269,11 @@ fn summary_view(
proof_verification: &ValidationReceiptProofVerificationView,
) -> ValidationReceiptSummaryView {
ValidationReceiptSummaryView {
- resource: validation_receipt_resource(&event.id),
- receipt_event_id: event.id.clone(),
+ resource: validation_receipt_resource(event.id_str()),
+ receipt_event_id: event.id_str().to_owned(),
order_id: tags.order_id.clone(),
- author: event.author.clone(),
- created_at: event.created_at,
+ author: event.author_str().to_owned(),
+ created_at: event.created_at_u64(),
receipt_type: receipt_type_label(receipt.receipt_type).to_owned(),
result: receipt_result_label(receipt.result).to_owned(),
proof_system: receipt.proof.system.as_str().to_owned(),
diff --git a/src/view/runtime.rs b/src/view/runtime.rs
@@ -3036,7 +3036,7 @@ pub struct ListingMutationEventView {
pub kind: u32,
pub author: String,
#[serde(skip_serializing_if = "Option::is_none")]
- pub created_at: Option<u32>,
+ pub created_at: Option<u64>,
pub content: String,
pub tags: Vec<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
diff --git a/tests/support/mod.rs b/tests/support/mod.rs
@@ -4,9 +4,9 @@ use std::process::{Command, Output};
use std::sync::Mutex;
use assert_cmd::prelude::*;
-use radroots_event::RadrootsEventEnvelope;
use radroots_event::ids::RadrootsListingAddress;
use radroots_event::kinds::{KIND_FARM, KIND_LISTING};
+use radroots_event::{RadrootsEventEnvelope, RadrootsEventEnvelopeParts};
use radroots_identity::{RadrootsIdentity, RadrootsIdentityPublic};
use radroots_protected_store::RadrootsProtectedFileSecretVault;
use radroots_replica_sync::{RadrootsReplicaIngestOutcome, radroots_replica_ingest_event};
@@ -274,7 +274,7 @@ pub fn seed_orderable_listing(sandbox: &RadrootsCliSandbox, listing_addr: &str)
.expect("replica db path from store init");
let (seller_pubkey, listing_id) = listing_addr_parts(listing_addr);
let event_id = "2".repeat(64);
- let event = RadrootsEventEnvelope {
+ let event = RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts {
id: event_id.clone(),
author: seller_pubkey.clone(),
created_at: 1,
@@ -322,7 +322,8 @@ pub fn seed_orderable_listing(sandbox: &RadrootsCliSandbox, listing_addr: &str)
],
content: "# Market Eggs".to_owned(),
sig: "f".repeat(128),
- };
+ })
+ .expect("seed listing event");
let executor = SqliteExecutor::open(Path::new(db_path)).expect("open replica db");
assert_eq!(
radroots_replica_ingest_event(&executor, &event).expect("ingest listing"),
@@ -437,24 +438,26 @@ fn seed_orderable_listing_signed_event(
store.migrate_up().expect("migrate runtime store");
store
.append_record(&RuntimeStoreRecordInput {
- record_id: format!("test:signed_listing:{}", event.id),
+ record_id: format!("test:signed_listing:{}", event.id_str()),
family: RuntimeStoreRecordFamily::SignedEvent,
status: RuntimeStoreRecordStatus::Published,
source_runtime: SourceRuntime::Cli,
created_at_ms: 1_779_000_001_000,
inserted_at_ms: 1_779_000_001_000,
owner_account_id: None,
- owner_pubkey: Some(event.author.clone()),
+ owner_pubkey: Some(event.author_str().to_owned()),
farm_id: None,
listing_addr: Some(listing_addr.to_owned()),
local_work_json: None,
- event_id: Some(event.id.clone()),
- event_kind: Some(i64::from(event.kind)),
- event_pubkey: Some(event.author.clone()),
- event_created_at: Some(i64::try_from(event.created_at).expect("event created_at")),
- event_tags_json: Some(json!(event.tags)),
- event_content: Some(event.content.clone()),
- event_sig: Some(event.sig.clone()),
+ event_id: Some(event.id_str().to_owned()),
+ event_kind: Some(i64::from(event.kind_u32())),
+ event_pubkey: Some(event.author_str().to_owned()),
+ event_created_at: Some(
+ i64::try_from(event.created_at_u64()).expect("event created_at"),
+ ),
+ event_tags_json: Some(json!(event.tags_as_vec())),
+ event_content: Some(event.content().to_owned()),
+ event_sig: Some(event.sig_str().to_owned()),
raw_event_json: Some(json!(event)),
outbox_status: PublishOutboxStatus::Acknowledged,
relay_set_fingerprint: None,
diff --git a/tests/target_cli.rs b/tests/target_cli.rs
@@ -738,7 +738,8 @@ fn handle_proxy_connection(mut stream: TcpStream, expected_token: Option<&str>)
let request: Value = serde_json::from_str(body.as_str()).expect("proxy json");
assert_eq!(request["jsonrpc"], "2.0");
assert_eq!(request["method"], "transport.publish.event");
- let event = &request["params"]["event"];
+ assert!(request["params"].get("event").is_none());
+ let event = proxy_request_raw_event(&request);
let target_result = json!({
"transport_kind": "nostr",
"endpoint_uri": "wss://daemon-resolved.radroots.test",
@@ -756,9 +757,9 @@ fn handle_proxy_connection(mut stream: TcpStream, expected_token: Option<&str>)
"status": "delivery_satisfied",
"terminal": true,
"delivery_satisfied": true,
- "event_id": event["id"],
- "pubkey": event["pubkey"],
- "event_kind": event["kind"],
+ "event_id": event["id"].clone(),
+ "pubkey": event["pubkey"].clone(),
+ "event_kind": event["kind"].clone(),
"target_policy": request["params"]["target_policy"],
"delivery_policy": request["params"]["delivery_policy"],
"target_count": 1,
@@ -782,6 +783,15 @@ fn handle_proxy_connection(mut stream: TcpStream, expected_token: Option<&str>)
request
}
+fn proxy_request_raw_event(request: &Value) -> Value {
+ serde_json::from_str(
+ request["params"]["raw_event_json"]
+ .as_str()
+ .expect("proxy raw event json"),
+ )
+ .expect("proxy raw event")
+}
+
fn http_body_start(bytes: &[u8]) -> Option<usize> {
bytes
.windows(4)
@@ -3607,10 +3617,8 @@ token_file = "{}"
"SDK listing publish ยท configured signer"
);
assert_eq!(value["result"]["dry_run"], false);
- assert_eq!(
- request["params"]["event"]["id"],
- value["result"]["event_id"]
- );
+ let event = proxy_request_raw_event(&request);
+ assert_eq!(event["id"], value["result"]["event_id"]);
assert_eq!(
request["params"]["target_policy"]["relay_urls"]
.as_array()
@@ -3890,15 +3898,10 @@ token_file = "{}"
assert!(report.req_count >= 1);
assert_eq!(report.sign_request_count, 1);
assert_eq!(report.published_events.len(), 0);
- assert_eq!(request["params"]["event"]["kind"], KIND_LISTING);
- assert_eq!(
- request["params"]["event"]["pubkey"],
- user_public.public_key_hex
- );
- assert_eq!(
- request["params"]["event"]["id"],
- value["result"]["event_id"]
- );
+ let event = proxy_request_raw_event(&request);
+ assert_eq!(event["kind"], KIND_LISTING);
+ assert_eq!(event["pubkey"], user_public.public_key_hex);
+ assert_eq!(event["id"], value["result"]["event_id"]);
}
#[test]