order.rs (173315B)
1 #![forbid(unsafe_code)] 2 3 #[cfg(not(feature = "std"))] 4 use alloc::{ 5 string::{String, ToString}, 6 vec::Vec, 7 }; 8 9 #[cfg(feature = "event_store")] 10 use radroots_event_store::{RadrootsEventStore, RadrootsEventStoreError, RadrootsStoredEvent}; 11 #[cfg(feature = "serde_json")] 12 use radroots_events::RadrootsNostrEvent; 13 use radroots_events::ids::{ 14 RadrootsEventId, RadrootsIdParseError, RadrootsInventoryBinId, RadrootsListingAddress, 15 RadrootsOrderId, RadrootsPublicKey, 16 }; 17 #[cfg(feature = "serde_json")] 18 use radroots_events::order::RadrootsOrderEventType; 19 use radroots_events::order::{ 20 RadrootsOrderCancellation, RadrootsOrderDecision, RadrootsOrderDecisionOutcome, 21 RadrootsOrderEconomics, RadrootsOrderInventoryCommitment, RadrootsOrderItem, 22 RadrootsOrderRequest, RadrootsOrderRevisionDecision, RadrootsOrderRevisionOutcome, 23 RadrootsOrderRevisionProposal, 24 }; 25 #[cfg(feature = "event_store")] 26 use radroots_events::tags::TAG_D; 27 #[cfg(feature = "serde_json")] 28 use radroots_events_codec::order::{ 29 RadrootsOrderEnvelopeParseError, order_cancellation_from_event, order_decision_from_event, 30 order_event_context_from_tags, order_request_from_event, order_revision_decision_from_event, 31 order_revision_proposal_from_event, 32 }; 33 #[cfg(feature = "serde_json")] 34 use sha2::{Digest, Sha256}; 35 use thiserror::Error; 36 37 use crate::listing::{ 38 RadrootsPublicListingAddress, RadrootsPublicListingAddressError, parse_public_listing_address, 39 }; 40 41 #[derive(Debug, Error)] 42 pub enum RadrootsOrderCanonicalizationError { 43 #[error("{0} cannot be empty")] 44 EmptyField(&'static str), 45 #[error("invalid listing_addr: {0}")] 46 InvalidListingAddress(String), 47 #[error("listing_addr must reference a public NIP-99 listing")] 48 InvalidListingKind, 49 #[error("buyer_pubkey must match the requested signer identity")] 50 InvalidBuyerSigner, 51 #[error("seller_pubkey must match listing_addr seller")] 52 InvalidSellerListing, 53 #[error("items must contain at least one item")] 54 MissingItems, 55 #[error("items[{index}].bin_count must be greater than zero")] 56 InvalidBinCount { index: usize }, 57 #[error("accepted decisions must contain at least one inventory commitment")] 58 MissingInventoryCommitments, 59 #[error("inventory_commitments[{index}].bin_count must be greater than zero")] 60 InvalidInventoryCommitmentCount { index: usize }, 61 } 62 63 pub const ORDER_EVENT_CONTRACT_IDS: [&str; 5] = [ 64 "radroots.order.request.v1", 65 "radroots.order.decision.v1", 66 "radroots.order.revision_proposal.v1", 67 "radroots.order.revision_decision.v1", 68 "radroots.order.cancellation.v1", 69 ]; 70 71 #[derive(Clone, Debug, PartialEq, Eq)] 72 pub struct RadrootsOrderRequestRecord { 73 pub event_id: RadrootsEventId, 74 pub author_pubkey: RadrootsPublicKey, 75 pub payload: RadrootsOrderRequest, 76 } 77 78 #[derive(Clone, Debug, PartialEq, Eq)] 79 pub struct RadrootsOrderDecisionRecord { 80 pub event_id: RadrootsEventId, 81 pub author_pubkey: RadrootsPublicKey, 82 pub counterparty_pubkey: RadrootsPublicKey, 83 pub root_event_id: RadrootsEventId, 84 pub prev_event_id: RadrootsEventId, 85 pub payload: RadrootsOrderDecision, 86 } 87 88 #[derive(Clone, Debug, PartialEq, Eq)] 89 pub struct RadrootsOrderRevisionProposalRecord { 90 pub event_id: RadrootsEventId, 91 pub author_pubkey: RadrootsPublicKey, 92 pub counterparty_pubkey: RadrootsPublicKey, 93 pub root_event_id: RadrootsEventId, 94 pub prev_event_id: RadrootsEventId, 95 pub payload: RadrootsOrderRevisionProposal, 96 } 97 98 #[derive(Clone, Debug, PartialEq, Eq)] 99 pub struct RadrootsOrderRevisionDecisionRecord { 100 pub event_id: RadrootsEventId, 101 pub author_pubkey: RadrootsPublicKey, 102 pub counterparty_pubkey: RadrootsPublicKey, 103 pub root_event_id: RadrootsEventId, 104 pub prev_event_id: RadrootsEventId, 105 pub payload: RadrootsOrderRevisionDecision, 106 } 107 108 #[derive(Clone, Debug, PartialEq, Eq)] 109 pub struct RadrootsOrderCancellationRecord { 110 pub event_id: RadrootsEventId, 111 pub author_pubkey: RadrootsPublicKey, 112 pub counterparty_pubkey: RadrootsPublicKey, 113 pub root_event_id: RadrootsEventId, 114 pub prev_event_id: RadrootsEventId, 115 pub payload: RadrootsOrderCancellation, 116 } 117 118 #[derive(Clone, Debug, PartialEq, Eq)] 119 pub enum RadrootsOrderEventRecord { 120 Request(RadrootsOrderRequestRecord), 121 Decision(RadrootsOrderDecisionRecord), 122 RevisionProposal(RadrootsOrderRevisionProposalRecord), 123 RevisionDecision(RadrootsOrderRevisionDecisionRecord), 124 Cancellation(RadrootsOrderCancellationRecord), 125 } 126 127 impl RadrootsOrderEventRecord { 128 pub fn event_id(&self) -> &RadrootsEventId { 129 match self { 130 Self::Request(record) => &record.event_id, 131 Self::Decision(record) => &record.event_id, 132 Self::RevisionProposal(record) => &record.event_id, 133 Self::RevisionDecision(record) => &record.event_id, 134 Self::Cancellation(record) => &record.event_id, 135 } 136 } 137 138 pub fn order_id(&self) -> &RadrootsOrderId { 139 match self { 140 Self::Request(record) => &record.payload.order_id, 141 Self::Decision(record) => &record.payload.order_id, 142 Self::RevisionProposal(record) => &record.payload.order_id, 143 Self::RevisionDecision(record) => &record.payload.order_id, 144 Self::Cancellation(record) => &record.payload.order_id, 145 } 146 } 147 } 148 149 #[cfg(feature = "serde_json")] 150 #[derive(Debug, Error)] 151 pub enum RadrootsOrderEventDecodeError { 152 #[error("unsupported order event kind: {kind}")] 153 UnsupportedKind { kind: u32 }, 154 #[error("invalid order event id: {0}")] 155 InvalidEventId(RadrootsIdParseError), 156 #[error("invalid order event author: {0}")] 157 InvalidAuthor(RadrootsIdParseError), 158 #[error("order event context is missing root event id")] 159 MissingRootEventId, 160 #[error("order event context is missing previous event id")] 161 MissingPreviousEventId, 162 #[error("{0}")] 163 Envelope(#[from] RadrootsOrderEnvelopeParseError), 164 } 165 166 #[cfg(feature = "serde_json")] 167 pub fn order_event_record_from_event( 168 event: &RadrootsNostrEvent, 169 ) -> Result<RadrootsOrderEventRecord, RadrootsOrderEventDecodeError> { 170 let message_type = RadrootsOrderEventType::from_kind(event.kind) 171 .ok_or(RadrootsOrderEventDecodeError::UnsupportedKind { kind: event.kind })?; 172 let context = order_event_context_from_tags(message_type, &event.tags)?; 173 let event_id = 174 RadrootsEventId::parse(&event.id).map_err(RadrootsOrderEventDecodeError::InvalidEventId)?; 175 let author_pubkey = RadrootsPublicKey::parse(&event.author) 176 .map_err(RadrootsOrderEventDecodeError::InvalidAuthor)?; 177 178 match message_type { 179 RadrootsOrderEventType::OrderRequested => { 180 let envelope = order_request_from_event(event)?; 181 Ok(RadrootsOrderEventRecord::Request( 182 RadrootsOrderRequestRecord { 183 event_id, 184 author_pubkey, 185 payload: envelope.payload, 186 }, 187 )) 188 } 189 RadrootsOrderEventType::OrderDecision => { 190 let envelope = order_decision_from_event(event)?; 191 Ok(RadrootsOrderEventRecord::Decision( 192 RadrootsOrderDecisionRecord { 193 event_id, 194 author_pubkey, 195 counterparty_pubkey: context.counterparty_pubkey.clone(), 196 root_event_id: require_context_root_event_id(&context)?, 197 prev_event_id: require_context_prev_event_id(&context)?, 198 payload: envelope.payload, 199 }, 200 )) 201 } 202 RadrootsOrderEventType::OrderRevisionProposed => { 203 let envelope = order_revision_proposal_from_event(event)?; 204 Ok(RadrootsOrderEventRecord::RevisionProposal( 205 RadrootsOrderRevisionProposalRecord { 206 event_id, 207 author_pubkey, 208 counterparty_pubkey: context.counterparty_pubkey.clone(), 209 root_event_id: require_context_root_event_id(&context)?, 210 prev_event_id: require_context_prev_event_id(&context)?, 211 payload: envelope.payload, 212 }, 213 )) 214 } 215 RadrootsOrderEventType::OrderRevisionDecision => { 216 let envelope = order_revision_decision_from_event(event)?; 217 Ok(RadrootsOrderEventRecord::RevisionDecision( 218 RadrootsOrderRevisionDecisionRecord { 219 event_id, 220 author_pubkey, 221 counterparty_pubkey: context.counterparty_pubkey.clone(), 222 root_event_id: require_context_root_event_id(&context)?, 223 prev_event_id: require_context_prev_event_id(&context)?, 224 payload: envelope.payload, 225 }, 226 )) 227 } 228 RadrootsOrderEventType::OrderCancelled => { 229 let envelope = order_cancellation_from_event(event)?; 230 Ok(RadrootsOrderEventRecord::Cancellation( 231 RadrootsOrderCancellationRecord { 232 event_id, 233 author_pubkey, 234 counterparty_pubkey: context.counterparty_pubkey.clone(), 235 root_event_id: require_context_root_event_id(&context)?, 236 prev_event_id: require_context_prev_event_id(&context)?, 237 payload: envelope.payload, 238 }, 239 )) 240 } 241 } 242 } 243 244 #[cfg(feature = "event_store")] 245 #[derive(Debug, Error)] 246 pub enum RadrootsOrderStoreQueryError { 247 #[error("{0}")] 248 Store(#[from] RadrootsEventStoreError), 249 #[error("stored order event {event_id} contains invalid tags_json: {source}")] 250 InvalidStoredTagsJson { 251 event_id: String, 252 source: serde_json::Error, 253 }, 254 #[error("stored order event {event_id} could not decode as an order record: {source}")] 255 Decode { 256 event_id: String, 257 source: RadrootsOrderEventDecodeError, 258 }, 259 } 260 261 #[cfg(feature = "event_store")] 262 #[derive(Clone, Debug, PartialEq, Eq)] 263 pub struct RadrootsOrderProjectionQueryResult { 264 pub projection: RadrootsOrderProjection, 265 pub event_count: usize, 266 pub limit_applied: u32, 267 pub event_ids: Vec<RadrootsEventId>, 268 } 269 270 #[cfg(feature = "event_store")] 271 pub async fn order_events_for_order_id( 272 store: &RadrootsEventStore, 273 order_id: &RadrootsOrderId, 274 limit: u32, 275 ) -> Result<Vec<RadrootsOrderEventRecord>, RadrootsOrderStoreQueryError> { 276 let stored_events = store 277 .events_by_contract_and_tag(&ORDER_EVENT_CONTRACT_IDS, TAG_D, order_id.as_str(), limit) 278 .await?; 279 let mut records = Vec::with_capacity(stored_events.len()); 280 for stored_event in stored_events { 281 let event = stored_order_event_to_nostr_event(&stored_event)?; 282 let record = order_event_record_from_event(&event).map_err(|source| { 283 RadrootsOrderStoreQueryError::Decode { 284 event_id: stored_event.event_id.clone(), 285 source, 286 } 287 })?; 288 if record.order_id() == order_id { 289 records.push(record); 290 } 291 } 292 Ok(records) 293 } 294 295 #[cfg(feature = "event_store")] 296 pub async fn order_projection_for_order_id( 297 store: &RadrootsEventStore, 298 order_id: &RadrootsOrderId, 299 limit: u32, 300 ) -> Result<RadrootsOrderProjection, RadrootsOrderStoreQueryError> { 301 order_projection_query_for_order_id(store, order_id, limit) 302 .await 303 .map(|result| result.projection) 304 } 305 306 #[cfg(feature = "event_store")] 307 pub async fn order_projection_query_for_order_id( 308 store: &RadrootsEventStore, 309 order_id: &RadrootsOrderId, 310 limit: u32, 311 ) -> Result<RadrootsOrderProjectionQueryResult, RadrootsOrderStoreQueryError> { 312 let records = order_events_for_order_id(store, order_id, limit).await?; 313 let event_ids = records 314 .iter() 315 .map(|record| record.event_id().clone()) 316 .collect::<Vec<_>>(); 317 let event_count = records.len(); 318 Ok(RadrootsOrderProjectionQueryResult { 319 projection: reduce_order_event_records(order_id, records), 320 event_count, 321 limit_applied: limit, 322 event_ids, 323 }) 324 } 325 326 #[cfg(feature = "event_store")] 327 fn stored_order_event_to_nostr_event( 328 stored_event: &RadrootsStoredEvent, 329 ) -> Result<RadrootsNostrEvent, RadrootsOrderStoreQueryError> { 330 let tags = serde_json::from_str(&stored_event.tags_json).map_err(|source| { 331 RadrootsOrderStoreQueryError::InvalidStoredTagsJson { 332 event_id: stored_event.event_id.clone(), 333 source, 334 } 335 })?; 336 Ok(RadrootsNostrEvent { 337 id: stored_event.event_id.clone(), 338 author: stored_event.pubkey.clone(), 339 created_at: stored_event.created_at, 340 kind: stored_event.kind, 341 tags, 342 content: stored_event.content.clone(), 343 sig: stored_event.sig.clone(), 344 }) 345 } 346 347 #[cfg(feature = "serde_json")] 348 fn require_context_root_event_id( 349 context: &radroots_events_codec::order::RadrootsOrderEventContext, 350 ) -> Result<RadrootsEventId, RadrootsOrderEventDecodeError> { 351 context 352 .root_event_id 353 .clone() 354 .ok_or(RadrootsOrderEventDecodeError::MissingRootEventId) 355 } 356 357 #[cfg(feature = "serde_json")] 358 fn require_context_prev_event_id( 359 context: &radroots_events_codec::order::RadrootsOrderEventContext, 360 ) -> Result<RadrootsEventId, RadrootsOrderEventDecodeError> { 361 context 362 .prev_event_id 363 .clone() 364 .ok_or(RadrootsOrderEventDecodeError::MissingPreviousEventId) 365 } 366 367 #[derive(Clone, Debug, PartialEq, Eq)] 368 pub enum RadrootsOrderStatus { 369 Missing, 370 Requested, 371 Accepted, 372 Declined, 373 Cancelled, 374 Invalid, 375 } 376 377 #[derive(Clone, Debug, PartialEq, Eq)] 378 pub enum RadrootsOrderIssue { 379 MissingRequest, 380 MultipleRequests { event_ids: Vec<RadrootsEventId> }, 381 RequestPayloadInvalid { event_id: RadrootsEventId }, 382 RequestOrderIdMismatch { event_id: RadrootsEventId }, 383 RequestAuthorMismatch { event_id: RadrootsEventId }, 384 RequestListingAddressInvalid { event_id: RadrootsEventId }, 385 RequestSellerListingMismatch { event_id: RadrootsEventId }, 386 DecisionPayloadInvalid { event_id: RadrootsEventId }, 387 DecisionOrderIdMismatch { event_id: RadrootsEventId }, 388 DecisionAuthorMismatch { event_id: RadrootsEventId }, 389 DecisionCounterpartyMismatch { event_id: RadrootsEventId }, 390 DecisionBuyerMismatch { event_id: RadrootsEventId }, 391 DecisionSellerMismatch { event_id: RadrootsEventId }, 392 DecisionListingAddressInvalid { event_id: RadrootsEventId }, 393 DecisionListingMismatch { event_id: RadrootsEventId }, 394 DecisionRootMismatch { event_id: RadrootsEventId }, 395 DecisionPreviousMismatch { event_id: RadrootsEventId }, 396 DecisionMissingInventoryCommitments { event_id: RadrootsEventId }, 397 DecisionInventoryCommitmentMismatch { event_id: RadrootsEventId }, 398 DecisionMissingReason { event_id: RadrootsEventId }, 399 ConflictingDecisions { event_ids: Vec<RadrootsEventId> }, 400 RevisionProposalPayloadInvalid { event_id: RadrootsEventId }, 401 RevisionProposalOrderIdMismatch { event_id: RadrootsEventId }, 402 RevisionProposalAuthorMismatch { event_id: RadrootsEventId }, 403 RevisionProposalCounterpartyMismatch { event_id: RadrootsEventId }, 404 RevisionProposalBuyerMismatch { event_id: RadrootsEventId }, 405 RevisionProposalSellerMismatch { event_id: RadrootsEventId }, 406 RevisionProposalListingAddressInvalid { event_id: RadrootsEventId }, 407 RevisionProposalListingMismatch { event_id: RadrootsEventId }, 408 RevisionProposalRootMismatch { event_id: RadrootsEventId }, 409 RevisionProposalPreviousMismatch { event_id: RadrootsEventId }, 410 RevisionDecisionWithoutProposal { event_id: RadrootsEventId }, 411 RevisionDecisionPayloadInvalid { event_id: RadrootsEventId }, 412 RevisionDecisionOrderIdMismatch { event_id: RadrootsEventId }, 413 RevisionDecisionAuthorMismatch { event_id: RadrootsEventId }, 414 RevisionDecisionCounterpartyMismatch { event_id: RadrootsEventId }, 415 RevisionDecisionBuyerMismatch { event_id: RadrootsEventId }, 416 RevisionDecisionSellerMismatch { event_id: RadrootsEventId }, 417 RevisionDecisionListingAddressInvalid { event_id: RadrootsEventId }, 418 RevisionDecisionListingMismatch { event_id: RadrootsEventId }, 419 RevisionDecisionRootMismatch { event_id: RadrootsEventId }, 420 RevisionDecisionPreviousMismatch { event_id: RadrootsEventId }, 421 RevisionDecisionRevisionIdMismatch { event_id: RadrootsEventId }, 422 CancellationWithoutCancellableOrder { event_id: RadrootsEventId }, 423 CancellationPayloadInvalid { event_id: RadrootsEventId }, 424 CancellationOrderIdMismatch { event_id: RadrootsEventId }, 425 CancellationAuthorMismatch { event_id: RadrootsEventId }, 426 CancellationCounterpartyMismatch { event_id: RadrootsEventId }, 427 CancellationBuyerMismatch { event_id: RadrootsEventId }, 428 CancellationSellerMismatch { event_id: RadrootsEventId }, 429 CancellationListingAddressInvalid { event_id: RadrootsEventId }, 430 CancellationListingMismatch { event_id: RadrootsEventId }, 431 CancellationRootMismatch { event_id: RadrootsEventId }, 432 CancellationPreviousMismatch { event_id: RadrootsEventId }, 433 ForkedLifecycle { event_ids: Vec<RadrootsEventId> }, 434 } 435 436 #[derive(Clone, Debug, PartialEq, Eq)] 437 pub struct RadrootsOrderProjection { 438 pub order_id: RadrootsOrderId, 439 pub status: RadrootsOrderStatus, 440 pub request_event_id: Option<RadrootsEventId>, 441 pub decision_event_id: Option<RadrootsEventId>, 442 pub cancellation_event_id: Option<RadrootsEventId>, 443 pub lifecycle_terminal: bool, 444 pub economics: Option<RadrootsOrderEconomics>, 445 pub agreement_event_id: Option<RadrootsEventId>, 446 pub pending_revision_event_id: Option<RadrootsEventId>, 447 pub listing_addr: Option<RadrootsListingAddress>, 448 pub buyer_pubkey: Option<RadrootsPublicKey>, 449 pub seller_pubkey: Option<RadrootsPublicKey>, 450 pub last_event_id: Option<RadrootsEventId>, 451 pub issues: Vec<RadrootsOrderIssue>, 452 } 453 454 #[cfg(feature = "serde_json")] 455 #[derive(Debug, Error)] 456 pub enum RadrootsOrderEconomicsDigestError { 457 #[error("failed to serialize order economics for digest: {0}")] 458 Serialize(#[from] serde_json::Error), 459 } 460 461 #[derive(Clone, Debug, PartialEq, Eq)] 462 pub struct RadrootsListingInventoryBinAvailability { 463 pub bin_id: RadrootsInventoryBinId, 464 pub available_count: u64, 465 } 466 467 #[derive(Clone, Debug, PartialEq, Eq)] 468 pub struct RadrootsListingInventoryOrderReservation { 469 pub order_id: RadrootsOrderId, 470 pub agreement_event_id: RadrootsEventId, 471 pub bin_count: u64, 472 } 473 474 #[derive(Clone, Debug, PartialEq, Eq)] 475 pub struct RadrootsListingInventoryBinAccounting { 476 pub bin_id: RadrootsInventoryBinId, 477 pub available_count: u64, 478 pub accepted_reserved_count: u64, 479 pub remaining_count: u64, 480 pub over_reserved: bool, 481 pub accepted_orders: Vec<RadrootsListingInventoryOrderReservation>, 482 } 483 484 #[derive(Clone, Debug, PartialEq, Eq)] 485 pub enum RadrootsListingInventoryAccountingIssue { 486 InvalidOrder { 487 order_id: RadrootsOrderId, 488 event_ids: Vec<RadrootsEventId>, 489 }, 490 ArithmeticOverflow { 491 bin_id: RadrootsInventoryBinId, 492 event_ids: Vec<RadrootsEventId>, 493 }, 494 UnknownInventoryBin { 495 bin_id: RadrootsInventoryBinId, 496 event_ids: Vec<RadrootsEventId>, 497 }, 498 OverReserved { 499 bin_id: RadrootsInventoryBinId, 500 available_count: u64, 501 reserved_count: u64, 502 event_ids: Vec<RadrootsEventId>, 503 }, 504 } 505 506 #[derive(Clone, Debug, PartialEq, Eq)] 507 pub struct RadrootsListingInventoryAccountingProjection { 508 pub listing_addr: RadrootsListingAddress, 509 pub listing_event_id: RadrootsEventId, 510 pub bins: Vec<RadrootsListingInventoryBinAccounting>, 511 pub declined_order_ids: Vec<RadrootsOrderId>, 512 pub cancelled_order_ids: Vec<RadrootsOrderId>, 513 pub invalid_event_ids: Vec<RadrootsEventId>, 514 pub issues: Vec<RadrootsListingInventoryAccountingIssue>, 515 } 516 517 #[derive(Clone, Debug, PartialEq, Eq)] 518 pub struct RadrootsOrderReductionInputs<I, J, K, L, M> { 519 pub requests: I, 520 pub decisions: J, 521 pub revision_proposals: K, 522 pub revision_decisions: L, 523 pub cancellations: M, 524 } 525 526 #[derive(Clone, Debug, Default, PartialEq, Eq)] 527 pub struct RadrootsGroupedOrderEventRecords { 528 pub requests: Vec<RadrootsOrderRequestRecord>, 529 pub decisions: Vec<RadrootsOrderDecisionRecord>, 530 pub revision_proposals: Vec<RadrootsOrderRevisionProposalRecord>, 531 pub revision_decisions: Vec<RadrootsOrderRevisionDecisionRecord>, 532 pub cancellations: Vec<RadrootsOrderCancellationRecord>, 533 } 534 535 #[derive(Clone, Debug, PartialEq, Eq)] 536 pub struct RadrootsListingInventoryAccountingInputs<I, J, K, L, M, N> { 537 pub bins: I, 538 pub requests: J, 539 pub decisions: K, 540 pub revision_proposals: L, 541 pub revision_decisions: M, 542 pub cancellations: N, 543 } 544 545 #[derive(Clone, Debug, Default, PartialEq, Eq)] 546 struct RadrootsListingInventoryAccountingRecords { 547 bins: Vec<RadrootsListingInventoryBinAvailability>, 548 requests: Vec<RadrootsOrderRequestRecord>, 549 decisions: Vec<RadrootsOrderDecisionRecord>, 550 revision_proposals: Vec<RadrootsOrderRevisionProposalRecord>, 551 revision_decisions: Vec<RadrootsOrderRevisionDecisionRecord>, 552 cancellations: Vec<RadrootsOrderCancellationRecord>, 553 } 554 555 pub fn reduce_order_events<I, J, K, L, M>( 556 order_id: &RadrootsOrderId, 557 inputs: RadrootsOrderReductionInputs<I, J, K, L, M>, 558 ) -> RadrootsOrderProjection 559 where 560 I: IntoIterator<Item = RadrootsOrderRequestRecord>, 561 J: IntoIterator<Item = RadrootsOrderDecisionRecord>, 562 K: IntoIterator<Item = RadrootsOrderRevisionProposalRecord>, 563 L: IntoIterator<Item = RadrootsOrderRevisionDecisionRecord>, 564 M: IntoIterator<Item = RadrootsOrderCancellationRecord>, 565 { 566 reduce_grouped_order_event_records( 567 order_id, 568 RadrootsGroupedOrderEventRecords { 569 requests: inputs.requests.into_iter().collect(), 570 decisions: inputs.decisions.into_iter().collect(), 571 revision_proposals: inputs.revision_proposals.into_iter().collect(), 572 revision_decisions: inputs.revision_decisions.into_iter().collect(), 573 cancellations: inputs.cancellations.into_iter().collect(), 574 }, 575 ) 576 } 577 578 pub fn reduce_order_event_records<I>( 579 order_id: &RadrootsOrderId, 580 records: I, 581 ) -> RadrootsOrderProjection 582 where 583 I: IntoIterator<Item = RadrootsOrderEventRecord>, 584 { 585 let mut seen_event_ids = Vec::new(); 586 let mut grouped = RadrootsGroupedOrderEventRecords::default(); 587 588 for record in records { 589 let event_id = record.event_id().clone(); 590 if seen_event_ids.iter().any(|seen| seen == &event_id) { 591 continue; 592 } 593 seen_event_ids.push(event_id); 594 match record { 595 RadrootsOrderEventRecord::Request(record) => grouped.requests.push(record), 596 RadrootsOrderEventRecord::Decision(record) => grouped.decisions.push(record), 597 RadrootsOrderEventRecord::RevisionProposal(record) => { 598 grouped.revision_proposals.push(record); 599 } 600 RadrootsOrderEventRecord::RevisionDecision(record) => { 601 grouped.revision_decisions.push(record); 602 } 603 RadrootsOrderEventRecord::Cancellation(record) => grouped.cancellations.push(record), 604 } 605 } 606 607 reduce_grouped_order_event_records(order_id, grouped) 608 } 609 610 fn reduce_grouped_order_event_records( 611 order_id: &RadrootsOrderId, 612 records: RadrootsGroupedOrderEventRecords, 613 ) -> RadrootsOrderProjection { 614 let requests = unique_request_records(records.requests); 615 let decisions = unique_decision_records(records.decisions); 616 let revision_proposals = unique_revision_proposal_records(records.revision_proposals); 617 let revision_decisions = unique_revision_decision_records(records.revision_decisions); 618 let cancellations = unique_cancellation_records(records.cancellations); 619 if requests.is_empty() 620 && decisions.is_empty() 621 && revision_proposals.is_empty() 622 && revision_decisions.is_empty() 623 && cancellations.is_empty() 624 { 625 return empty_projection(order_id, RadrootsOrderStatus::Missing, false); 626 } 627 628 let mut issues = Vec::new(); 629 let mut valid_requests = Vec::new(); 630 for request in requests { 631 if validate_order_request_record(order_id, &request, &mut issues) { 632 valid_requests.push(request); 633 } 634 } 635 636 if valid_requests.len() > 1 { 637 let mut event_ids = valid_requests 638 .iter() 639 .map(|request| request.event_id.clone()) 640 .collect::<Vec<_>>(); 641 sort_and_dedup_values(&mut event_ids); 642 issues.push(RadrootsOrderIssue::MultipleRequests { event_ids }); 643 } 644 645 let Some(request) = valid_requests.first() else { 646 if !decisions.is_empty() 647 || !revision_proposals.is_empty() 648 || !revision_decisions.is_empty() 649 || !cancellations.is_empty() 650 { 651 issues.push(RadrootsOrderIssue::MissingRequest); 652 } 653 return invalid_projection(order_id, None, issues); 654 }; 655 656 if valid_requests.len() > 1 { 657 return invalid_projection(order_id, Some(request), issues); 658 } 659 660 let mut valid_decisions = Vec::new(); 661 for decision in decisions { 662 if validate_order_decision_record(request, &decision, &mut issues) { 663 valid_decisions.push(decision); 664 } 665 } 666 667 let mut valid_revision_proposals = Vec::new(); 668 for proposal in revision_proposals { 669 if validate_order_revision_proposal_record(request, &proposal, &mut issues) { 670 valid_revision_proposals.push(proposal); 671 } 672 } 673 674 let mut valid_revision_decisions = Vec::new(); 675 for decision in revision_decisions { 676 if validate_order_revision_decision_record(request, &decision, &mut issues) { 677 valid_revision_decisions.push(decision); 678 } 679 } 680 681 let mut valid_cancellations = Vec::new(); 682 for cancellation in cancellations { 683 if validate_order_cancellation_record(request, &cancellation, &mut issues) { 684 valid_cancellations.push(cancellation); 685 } 686 } 687 688 if !issues.is_empty() { 689 return invalid_projection(order_id, Some(request), issues); 690 } 691 692 if valid_cancellations.len() > 1 { 693 let mut event_ids = valid_cancellations 694 .iter() 695 .map(|cancellation| cancellation.event_id.clone()) 696 .collect::<Vec<_>>(); 697 sort_and_dedup_values(&mut event_ids); 698 return invalid_projection( 699 order_id, 700 Some(request), 701 vec![RadrootsOrderIssue::ForkedLifecycle { event_ids }], 702 ); 703 } 704 705 if let Some(cancellation) = valid_cancellations.first() { 706 return cancelled_projection( 707 order_id, 708 request, 709 cancellation, 710 &valid_decisions, 711 &valid_revision_proposals, 712 &valid_revision_decisions, 713 ); 714 } 715 716 match valid_decisions.len() { 717 0 => negotiation_projection( 718 order_id, 719 request, 720 &valid_revision_proposals, 721 &valid_revision_decisions, 722 ), 723 1 => decided_projection( 724 order_id, 725 request, 726 &valid_decisions[0], 727 &valid_revision_proposals, 728 &valid_revision_decisions, 729 ), 730 _ => { 731 let mut event_ids = valid_decisions 732 .iter() 733 .map(|decision| decision.event_id.clone()) 734 .collect::<Vec<_>>(); 735 sort_and_dedup_values(&mut event_ids); 736 invalid_projection( 737 order_id, 738 Some(request), 739 vec![RadrootsOrderIssue::ConflictingDecisions { event_ids }], 740 ) 741 } 742 } 743 } 744 745 pub fn reduce_listing_inventory_accounting<I, J, K, L, M, N>( 746 listing_addr: &RadrootsListingAddress, 747 listing_event_id: &RadrootsEventId, 748 inputs: RadrootsListingInventoryAccountingInputs<I, J, K, L, M, N>, 749 ) -> RadrootsListingInventoryAccountingProjection 750 where 751 I: IntoIterator<Item = RadrootsListingInventoryBinAvailability>, 752 J: IntoIterator<Item = RadrootsOrderRequestRecord>, 753 K: IntoIterator<Item = RadrootsOrderDecisionRecord>, 754 L: IntoIterator<Item = RadrootsOrderRevisionProposalRecord>, 755 M: IntoIterator<Item = RadrootsOrderRevisionDecisionRecord>, 756 N: IntoIterator<Item = RadrootsOrderCancellationRecord>, 757 { 758 reduce_listing_inventory_accounting_records( 759 listing_addr, 760 listing_event_id, 761 RadrootsListingInventoryAccountingRecords { 762 bins: inputs.bins.into_iter().collect(), 763 requests: inputs.requests.into_iter().collect(), 764 decisions: inputs.decisions.into_iter().collect(), 765 revision_proposals: inputs.revision_proposals.into_iter().collect(), 766 revision_decisions: inputs.revision_decisions.into_iter().collect(), 767 cancellations: inputs.cancellations.into_iter().collect(), 768 }, 769 ) 770 } 771 772 fn reduce_listing_inventory_accounting_records( 773 listing_addr: &RadrootsListingAddress, 774 listing_event_id: &RadrootsEventId, 775 records: RadrootsListingInventoryAccountingRecords, 776 ) -> RadrootsListingInventoryAccountingProjection { 777 let (mut bins, mut issues) = normalized_listing_inventory_bins(records.bins); 778 let requests = unique_request_records(records.requests) 779 .into_iter() 780 .filter(|request| request.payload.listing_addr.as_str() == listing_addr.as_str()) 781 .collect::<Vec<_>>(); 782 let decisions = unique_decision_records(records.decisions) 783 .into_iter() 784 .filter(|decision| decision.payload.listing_addr.as_str() == listing_addr.as_str()) 785 .collect::<Vec<_>>(); 786 let revision_proposals = unique_revision_proposal_records(records.revision_proposals) 787 .into_iter() 788 .filter(|proposal| proposal.payload.listing_addr.as_str() == listing_addr.as_str()) 789 .collect::<Vec<_>>(); 790 let revision_decisions = unique_revision_decision_records(records.revision_decisions) 791 .into_iter() 792 .filter(|decision| decision.payload.listing_addr.as_str() == listing_addr.as_str()) 793 .collect::<Vec<_>>(); 794 let cancellations = unique_cancellation_records(records.cancellations) 795 .into_iter() 796 .filter(|cancellation| cancellation.payload.listing_addr.as_str() == listing_addr.as_str()) 797 .collect::<Vec<_>>(); 798 let mut order_ids = listing_order_ids( 799 &requests, 800 &decisions, 801 &revision_proposals, 802 &revision_decisions, 803 &cancellations, 804 ); 805 let mut declined_order_ids = Vec::new(); 806 let mut cancelled_order_ids = Vec::new(); 807 let mut invalid_event_ids = Vec::new(); 808 809 for order_id in order_ids.drain(..) { 810 let order_requests = requests 811 .iter() 812 .filter(|request| request.payload.order_id == order_id) 813 .cloned() 814 .collect::<Vec<_>>(); 815 let order_decisions = decisions 816 .iter() 817 .filter(|decision| decision.payload.order_id == order_id) 818 .cloned() 819 .collect::<Vec<_>>(); 820 let order_revision_proposals = revision_proposals 821 .iter() 822 .filter(|proposal| proposal.payload.order_id == order_id) 823 .cloned() 824 .collect::<Vec<_>>(); 825 let order_revision_decisions = revision_decisions 826 .iter() 827 .filter(|decision| decision.payload.order_id == order_id) 828 .cloned() 829 .collect::<Vec<_>>(); 830 let order_cancellations = cancellations 831 .iter() 832 .filter(|cancellation| cancellation.payload.order_id == order_id) 833 .cloned() 834 .collect::<Vec<_>>(); 835 let projection = reduce_order_events( 836 &order_id, 837 RadrootsOrderReductionInputs { 838 requests: order_requests.clone(), 839 decisions: order_decisions.clone(), 840 revision_proposals: order_revision_proposals.clone(), 841 revision_decisions: order_revision_decisions.clone(), 842 cancellations: order_cancellations.clone(), 843 }, 844 ); 845 match projection.status { 846 RadrootsOrderStatus::Accepted => { 847 for (agreement_event_id, economics) in projection 848 .agreement_event_id 849 .iter() 850 .zip(projection.economics.iter()) 851 { 852 add_accepted_inventory_reservations_from_economics( 853 &mut bins, 854 &order_id, 855 agreement_event_id, 856 economics, 857 &mut issues, 858 ); 859 } 860 } 861 RadrootsOrderStatus::Cancelled => cancelled_order_ids.push(order_id), 862 RadrootsOrderStatus::Declined => declined_order_ids.push(order_id), 863 RadrootsOrderStatus::Invalid => { 864 let mut event_ids = projection_issue_event_ids(&projection.issues); 865 if event_ids.is_empty() { 866 event_ids = fallback_order_event_ids( 867 &order_requests, 868 &order_decisions, 869 &order_revision_proposals, 870 &order_revision_decisions, 871 &order_cancellations, 872 ); 873 } 874 invalid_event_ids.extend(event_ids.iter().cloned()); 875 issues.push(RadrootsListingInventoryAccountingIssue::InvalidOrder { 876 order_id, 877 event_ids, 878 }); 879 } 880 RadrootsOrderStatus::Missing | RadrootsOrderStatus::Requested => {} 881 } 882 } 883 884 sort_and_dedup_values(&mut declined_order_ids); 885 sort_and_dedup_values(&mut cancelled_order_ids); 886 sort_and_dedup_values(&mut invalid_event_ids); 887 finish_inventory_accounting_bins(&mut bins, &mut issues); 888 issues.sort_by(inventory_issue_sort_key); 889 RadrootsListingInventoryAccountingProjection { 890 listing_addr: listing_addr.clone(), 891 listing_event_id: listing_event_id.clone(), 892 bins, 893 declined_order_ids, 894 cancelled_order_ids, 895 invalid_event_ids, 896 issues, 897 } 898 } 899 900 fn fallback_order_event_ids( 901 requests: &[RadrootsOrderRequestRecord], 902 decisions: &[RadrootsOrderDecisionRecord], 903 revision_proposals: &[RadrootsOrderRevisionProposalRecord], 904 revision_decisions: &[RadrootsOrderRevisionDecisionRecord], 905 cancellations: &[RadrootsOrderCancellationRecord], 906 ) -> Vec<RadrootsEventId> { 907 let mut event_ids = Vec::new(); 908 event_ids.extend(requests.iter().map(|request| request.event_id.clone())); 909 event_ids.extend(decisions.iter().map(|decision| decision.event_id.clone())); 910 event_ids.extend( 911 revision_proposals 912 .iter() 913 .map(|proposal| proposal.event_id.clone()), 914 ); 915 event_ids.extend( 916 revision_decisions 917 .iter() 918 .map(|decision| decision.event_id.clone()), 919 ); 920 event_ids.extend( 921 cancellations 922 .iter() 923 .map(|cancellation| cancellation.event_id.clone()), 924 ); 925 sort_and_dedup_values(&mut event_ids); 926 event_ids 927 } 928 929 pub fn canonicalize_order_request_for_signer( 930 mut request: RadrootsOrderRequest, 931 signer_pubkey: &str, 932 ) -> Result<RadrootsOrderRequest, RadrootsOrderCanonicalizationError> { 933 let order_id = request.order_id.clone(); 934 let listing_addr_raw = request.listing_addr.to_string(); 935 let listing_addr = parse_public_listing_addr(&listing_addr_raw)?; 936 937 let buyer_pubkey = request.buyer_pubkey.clone(); 938 if buyer_pubkey.as_str() != signer_pubkey { 939 return Err(RadrootsOrderCanonicalizationError::InvalidBuyerSigner); 940 } 941 942 let seller_pubkey = request.seller_pubkey.clone(); 943 if seller_pubkey != listing_addr.seller_pubkey { 944 return Err(RadrootsOrderCanonicalizationError::InvalidSellerListing); 945 } 946 947 canonicalize_items(&mut request.items)?; 948 request.economics.canonicalize(); 949 request.order_id = order_id; 950 request.listing_addr = listing_addr.address; 951 request.buyer_pubkey = buyer_pubkey; 952 request.seller_pubkey = seller_pubkey; 953 Ok(request) 954 } 955 956 pub fn canonicalize_order_decision_for_signer( 957 mut decision_event: RadrootsOrderDecision, 958 signer_pubkey: &str, 959 ) -> Result<RadrootsOrderDecision, RadrootsOrderCanonicalizationError> { 960 let order_id = decision_event.order_id.clone(); 961 let listing_addr_raw = decision_event.listing_addr.to_string(); 962 let listing_addr = parse_public_listing_addr(&listing_addr_raw)?; 963 964 let seller_pubkey = decision_event.seller_pubkey.clone(); 965 if seller_pubkey.as_str() != signer_pubkey || seller_pubkey != listing_addr.seller_pubkey { 966 return Err(RadrootsOrderCanonicalizationError::InvalidSellerListing); 967 } 968 969 let buyer_pubkey = decision_event.buyer_pubkey.clone(); 970 canonicalize_decision(&mut decision_event.decision)?; 971 972 decision_event.order_id = order_id; 973 decision_event.listing_addr = listing_addr.address; 974 decision_event.buyer_pubkey = buyer_pubkey; 975 decision_event.seller_pubkey = seller_pubkey; 976 Ok(decision_event) 977 } 978 979 #[cfg(feature = "serde_json")] 980 pub fn radroots_order_economics_digest( 981 economics: &RadrootsOrderEconomics, 982 ) -> Result<String, RadrootsOrderEconomicsDigestError> { 983 let encoded = serde_json::to_vec(economics)?; 984 let digest = Sha256::digest(encoded); 985 let mut value = String::from("sha256:"); 986 value.push_str(&hex::encode(digest)); 987 Ok(value) 988 } 989 990 fn cancelled_projection( 991 order_id: &RadrootsOrderId, 992 request: &RadrootsOrderRequestRecord, 993 cancellation: &RadrootsOrderCancellationRecord, 994 decisions: &[RadrootsOrderDecisionRecord], 995 revision_proposals: &[RadrootsOrderRevisionProposalRecord], 996 revision_decisions: &[RadrootsOrderRevisionDecisionRecord], 997 ) -> RadrootsOrderProjection { 998 if !decisions.is_empty() || !revision_decisions.is_empty() { 999 let mut event_ids = Vec::new(); 1000 event_ids.extend(decisions.iter().map(|decision| decision.event_id.clone())); 1001 event_ids.extend( 1002 revision_decisions 1003 .iter() 1004 .map(|decision| decision.event_id.clone()), 1005 ); 1006 event_ids.push(cancellation.event_id.clone()); 1007 sort_and_dedup_values(&mut event_ids); 1008 return invalid_projection( 1009 order_id, 1010 Some(request), 1011 vec![RadrootsOrderIssue::ForkedLifecycle { event_ids }], 1012 ); 1013 } 1014 if revision_proposals.len() > 1 { 1015 let mut event_ids = revision_proposals 1016 .iter() 1017 .map(|proposal| proposal.event_id.clone()) 1018 .collect::<Vec<_>>(); 1019 event_ids.push(cancellation.event_id.clone()); 1020 sort_and_dedup_values(&mut event_ids); 1021 return invalid_projection( 1022 order_id, 1023 Some(request), 1024 vec![RadrootsOrderIssue::ForkedLifecycle { event_ids }], 1025 ); 1026 } 1027 let expected_prev_event_id = revision_proposals 1028 .first() 1029 .map(|proposal| &proposal.event_id) 1030 .unwrap_or(&request.event_id); 1031 if &cancellation.prev_event_id != expected_prev_event_id { 1032 return invalid_projection( 1033 order_id, 1034 Some(request), 1035 vec![RadrootsOrderIssue::CancellationPreviousMismatch { 1036 event_id: cancellation.event_id.clone(), 1037 }], 1038 ); 1039 } 1040 1041 let mut projection = request_projection(order_id, request, RadrootsOrderStatus::Cancelled); 1042 projection.cancellation_event_id = Some(cancellation.event_id.clone()); 1043 projection.lifecycle_terminal = true; 1044 projection.last_event_id = Some(cancellation.event_id.clone()); 1045 projection 1046 } 1047 1048 fn negotiation_projection( 1049 order_id: &RadrootsOrderId, 1050 request: &RadrootsOrderRequestRecord, 1051 revision_proposals: &[RadrootsOrderRevisionProposalRecord], 1052 revision_decisions: &[RadrootsOrderRevisionDecisionRecord], 1053 ) -> RadrootsOrderProjection { 1054 match revision_proposals.len() { 1055 0 => { 1056 if revision_decisions.is_empty() { 1057 request_projection(order_id, request, RadrootsOrderStatus::Requested) 1058 } else { 1059 invalid_projection( 1060 order_id, 1061 Some(request), 1062 revision_decisions 1063 .iter() 1064 .map( 1065 |decision| RadrootsOrderIssue::RevisionDecisionWithoutProposal { 1066 event_id: decision.event_id.clone(), 1067 }, 1068 ) 1069 .collect(), 1070 ) 1071 } 1072 } 1073 1 => { 1074 let proposal = &revision_proposals[0]; 1075 if proposal.prev_event_id != request.event_id { 1076 return invalid_projection( 1077 order_id, 1078 Some(request), 1079 vec![RadrootsOrderIssue::RevisionProposalPreviousMismatch { 1080 event_id: proposal.event_id.clone(), 1081 }], 1082 ); 1083 } 1084 match revision_decisions.len() { 1085 0 => { 1086 let mut projection = 1087 request_projection(order_id, request, RadrootsOrderStatus::Requested); 1088 projection.pending_revision_event_id = Some(proposal.event_id.clone()); 1089 projection.economics = Some(proposal.payload.economics.clone()); 1090 projection.last_event_id = Some(proposal.event_id.clone()); 1091 projection 1092 } 1093 1 => revision_decision_projection( 1094 order_id, 1095 request, 1096 proposal, 1097 &revision_decisions[0], 1098 ), 1099 _ => { 1100 let mut event_ids = revision_decisions 1101 .iter() 1102 .map(|decision| decision.event_id.clone()) 1103 .collect::<Vec<_>>(); 1104 sort_and_dedup_values(&mut event_ids); 1105 invalid_projection( 1106 order_id, 1107 Some(request), 1108 vec![RadrootsOrderIssue::ForkedLifecycle { event_ids }], 1109 ) 1110 } 1111 } 1112 } 1113 _ => { 1114 let mut event_ids = revision_proposals 1115 .iter() 1116 .map(|proposal| proposal.event_id.clone()) 1117 .collect::<Vec<_>>(); 1118 sort_and_dedup_values(&mut event_ids); 1119 invalid_projection( 1120 order_id, 1121 Some(request), 1122 vec![RadrootsOrderIssue::ForkedLifecycle { event_ids }], 1123 ) 1124 } 1125 } 1126 } 1127 1128 fn decided_projection( 1129 order_id: &RadrootsOrderId, 1130 request: &RadrootsOrderRequestRecord, 1131 decision: &RadrootsOrderDecisionRecord, 1132 revision_proposals: &[RadrootsOrderRevisionProposalRecord], 1133 revision_decisions: &[RadrootsOrderRevisionDecisionRecord], 1134 ) -> RadrootsOrderProjection { 1135 if !revision_proposals.is_empty() || !revision_decisions.is_empty() { 1136 let mut event_ids = Vec::new(); 1137 event_ids.extend( 1138 revision_proposals 1139 .iter() 1140 .map(|proposal| proposal.event_id.clone()), 1141 ); 1142 event_ids.extend( 1143 revision_decisions 1144 .iter() 1145 .map(|decision| decision.event_id.clone()), 1146 ); 1147 event_ids.push(decision.event_id.clone()); 1148 sort_and_dedup_values(&mut event_ids); 1149 return invalid_projection( 1150 order_id, 1151 Some(request), 1152 vec![RadrootsOrderIssue::ForkedLifecycle { event_ids }], 1153 ); 1154 } 1155 1156 match &decision.payload.decision { 1157 RadrootsOrderDecisionOutcome::Accepted { .. } => { 1158 let mut projection = 1159 request_projection(order_id, request, RadrootsOrderStatus::Accepted); 1160 projection.decision_event_id = Some(decision.event_id.clone()); 1161 projection.lifecycle_terminal = true; 1162 projection.economics = Some(request.payload.economics.clone()); 1163 projection.agreement_event_id = Some(decision.event_id.clone()); 1164 projection.last_event_id = Some(decision.event_id.clone()); 1165 projection 1166 } 1167 RadrootsOrderDecisionOutcome::Declined { .. } => { 1168 let mut projection = 1169 request_projection(order_id, request, RadrootsOrderStatus::Declined); 1170 projection.decision_event_id = Some(decision.event_id.clone()); 1171 projection.lifecycle_terminal = true; 1172 projection.last_event_id = Some(decision.event_id.clone()); 1173 projection 1174 } 1175 } 1176 } 1177 1178 fn revision_decision_projection( 1179 order_id: &RadrootsOrderId, 1180 request: &RadrootsOrderRequestRecord, 1181 proposal: &RadrootsOrderRevisionProposalRecord, 1182 decision: &RadrootsOrderRevisionDecisionRecord, 1183 ) -> RadrootsOrderProjection { 1184 if decision.prev_event_id != proposal.event_id { 1185 return invalid_projection( 1186 order_id, 1187 Some(request), 1188 vec![RadrootsOrderIssue::RevisionDecisionPreviousMismatch { 1189 event_id: decision.event_id.clone(), 1190 }], 1191 ); 1192 } 1193 if decision.payload.revision_id != proposal.payload.revision_id { 1194 return invalid_projection( 1195 order_id, 1196 Some(request), 1197 vec![RadrootsOrderIssue::RevisionDecisionRevisionIdMismatch { 1198 event_id: decision.event_id.clone(), 1199 }], 1200 ); 1201 } 1202 1203 match &decision.payload.decision { 1204 RadrootsOrderRevisionOutcome::Accepted => { 1205 let mut projection = 1206 request_projection(order_id, request, RadrootsOrderStatus::Accepted); 1207 projection.economics = Some(proposal.payload.economics.clone()); 1208 projection.agreement_event_id = Some(decision.event_id.clone()); 1209 projection.lifecycle_terminal = true; 1210 projection.last_event_id = Some(decision.event_id.clone()); 1211 projection 1212 } 1213 RadrootsOrderRevisionOutcome::Declined { .. } => { 1214 let mut projection = 1215 request_projection(order_id, request, RadrootsOrderStatus::Declined); 1216 projection.lifecycle_terminal = true; 1217 projection.pending_revision_event_id = Some(proposal.event_id.clone()); 1218 projection.last_event_id = Some(decision.event_id.clone()); 1219 projection 1220 } 1221 } 1222 } 1223 1224 fn request_projection( 1225 order_id: &RadrootsOrderId, 1226 request: &RadrootsOrderRequestRecord, 1227 status: RadrootsOrderStatus, 1228 ) -> RadrootsOrderProjection { 1229 RadrootsOrderProjection { 1230 order_id: order_id.clone(), 1231 status, 1232 request_event_id: Some(request.event_id.clone()), 1233 decision_event_id: None, 1234 cancellation_event_id: None, 1235 lifecycle_terminal: false, 1236 economics: Some(request.payload.economics.clone()), 1237 agreement_event_id: None, 1238 pending_revision_event_id: None, 1239 listing_addr: Some(request.payload.listing_addr.clone()), 1240 buyer_pubkey: Some(request.payload.buyer_pubkey.clone()), 1241 seller_pubkey: Some(request.payload.seller_pubkey.clone()), 1242 last_event_id: Some(request.event_id.clone()), 1243 issues: Vec::new(), 1244 } 1245 } 1246 1247 fn invalid_projection( 1248 order_id: &RadrootsOrderId, 1249 request: Option<&RadrootsOrderRequestRecord>, 1250 mut issues: Vec<RadrootsOrderIssue>, 1251 ) -> RadrootsOrderProjection { 1252 issues.sort_by(order_issue_sort_key); 1253 let last_event_id = projection_issue_event_ids(&issues).into_iter().last(); 1254 match request { 1255 Some(request) => { 1256 let mut projection = 1257 request_projection(order_id, request, RadrootsOrderStatus::Invalid); 1258 projection.lifecycle_terminal = true; 1259 projection.last_event_id = last_event_id.or_else(|| Some(request.event_id.clone())); 1260 projection.issues = issues; 1261 projection 1262 } 1263 None => { 1264 let mut projection = empty_projection(order_id, RadrootsOrderStatus::Invalid, true); 1265 projection.last_event_id = last_event_id; 1266 projection.issues = issues; 1267 projection 1268 } 1269 } 1270 } 1271 1272 fn empty_projection( 1273 order_id: &RadrootsOrderId, 1274 status: RadrootsOrderStatus, 1275 lifecycle_terminal: bool, 1276 ) -> RadrootsOrderProjection { 1277 RadrootsOrderProjection { 1278 order_id: order_id.clone(), 1279 status, 1280 request_event_id: None, 1281 decision_event_id: None, 1282 cancellation_event_id: None, 1283 lifecycle_terminal, 1284 economics: None, 1285 agreement_event_id: None, 1286 pending_revision_event_id: None, 1287 listing_addr: None, 1288 buyer_pubkey: None, 1289 seller_pubkey: None, 1290 last_event_id: None, 1291 issues: Vec::new(), 1292 } 1293 } 1294 1295 fn validate_order_request_record( 1296 order_id: &RadrootsOrderId, 1297 request: &RadrootsOrderRequestRecord, 1298 issues: &mut Vec<RadrootsOrderIssue>, 1299 ) -> bool { 1300 let mut valid = true; 1301 if request.payload.validate().is_err() { 1302 issues.push(RadrootsOrderIssue::RequestPayloadInvalid { 1303 event_id: request.event_id.clone(), 1304 }); 1305 valid = false; 1306 } 1307 if request.payload.order_id.as_str() != order_id.as_str() { 1308 issues.push(RadrootsOrderIssue::RequestOrderIdMismatch { 1309 event_id: request.event_id.clone(), 1310 }); 1311 valid = false; 1312 } 1313 if request.author_pubkey != request.payload.buyer_pubkey { 1314 issues.push(RadrootsOrderIssue::RequestAuthorMismatch { 1315 event_id: request.event_id.clone(), 1316 }); 1317 valid = false; 1318 } 1319 match parse_public_listing_addr(&request.payload.listing_addr) { 1320 Ok(listing_addr) => { 1321 if listing_addr.seller_pubkey != request.payload.seller_pubkey { 1322 issues.push(RadrootsOrderIssue::RequestSellerListingMismatch { 1323 event_id: request.event_id.clone(), 1324 }); 1325 valid = false; 1326 } 1327 } 1328 Err(_) => { 1329 issues.push(RadrootsOrderIssue::RequestListingAddressInvalid { 1330 event_id: request.event_id.clone(), 1331 }); 1332 valid = false; 1333 } 1334 } 1335 valid 1336 } 1337 1338 fn validate_order_decision_record( 1339 request: &RadrootsOrderRequestRecord, 1340 decision: &RadrootsOrderDecisionRecord, 1341 issues: &mut Vec<RadrootsOrderIssue>, 1342 ) -> bool { 1343 let mut valid = true; 1344 if decision_payload_issue(&decision.payload.decision, &decision.event_id, issues) { 1345 valid = false; 1346 } 1347 if decision.payload.validate().is_err() { 1348 issues.push(RadrootsOrderIssue::DecisionPayloadInvalid { 1349 event_id: decision.event_id.clone(), 1350 }); 1351 valid = false; 1352 } 1353 if decision.payload.order_id != request.payload.order_id { 1354 issues.push(RadrootsOrderIssue::DecisionOrderIdMismatch { 1355 event_id: decision.event_id.clone(), 1356 }); 1357 valid = false; 1358 } 1359 if decision.author_pubkey != decision.payload.seller_pubkey { 1360 issues.push(RadrootsOrderIssue::DecisionAuthorMismatch { 1361 event_id: decision.event_id.clone(), 1362 }); 1363 valid = false; 1364 } 1365 if decision.counterparty_pubkey != request.payload.buyer_pubkey { 1366 issues.push(RadrootsOrderIssue::DecisionCounterpartyMismatch { 1367 event_id: decision.event_id.clone(), 1368 }); 1369 valid = false; 1370 } 1371 if decision.payload.buyer_pubkey != request.payload.buyer_pubkey { 1372 issues.push(RadrootsOrderIssue::DecisionBuyerMismatch { 1373 event_id: decision.event_id.clone(), 1374 }); 1375 valid = false; 1376 } 1377 if decision.payload.seller_pubkey != request.payload.seller_pubkey { 1378 issues.push(RadrootsOrderIssue::DecisionSellerMismatch { 1379 event_id: decision.event_id.clone(), 1380 }); 1381 valid = false; 1382 } 1383 match parse_public_listing_addr(&decision.payload.listing_addr) { 1384 Ok(listing_addr) => { 1385 if decision.payload.listing_addr != request.payload.listing_addr 1386 || listing_addr.seller_pubkey != decision.payload.seller_pubkey 1387 { 1388 issues.push(RadrootsOrderIssue::DecisionListingMismatch { 1389 event_id: decision.event_id.clone(), 1390 }); 1391 valid = false; 1392 } 1393 } 1394 Err(_) => { 1395 issues.push(RadrootsOrderIssue::DecisionListingAddressInvalid { 1396 event_id: decision.event_id.clone(), 1397 }); 1398 valid = false; 1399 } 1400 } 1401 if decision.root_event_id != request.event_id { 1402 issues.push(RadrootsOrderIssue::DecisionRootMismatch { 1403 event_id: decision.event_id.clone(), 1404 }); 1405 valid = false; 1406 } 1407 if decision.prev_event_id != request.event_id { 1408 issues.push(RadrootsOrderIssue::DecisionPreviousMismatch { 1409 event_id: decision.event_id.clone(), 1410 }); 1411 valid = false; 1412 } 1413 if let RadrootsOrderDecisionOutcome::Accepted { 1414 inventory_commitments, 1415 } = &decision.payload.decision 1416 && decision.payload.validate().is_ok() 1417 && !inventory_commitments_match_request(&request.payload.items, inventory_commitments) 1418 { 1419 issues.push(RadrootsOrderIssue::DecisionInventoryCommitmentMismatch { 1420 event_id: decision.event_id.clone(), 1421 }); 1422 valid = false; 1423 } 1424 valid 1425 } 1426 1427 fn validate_order_revision_proposal_record( 1428 request: &RadrootsOrderRequestRecord, 1429 proposal: &RadrootsOrderRevisionProposalRecord, 1430 issues: &mut Vec<RadrootsOrderIssue>, 1431 ) -> bool { 1432 let mut valid = true; 1433 if proposal.payload.validate().is_err() { 1434 issues.push(RadrootsOrderIssue::RevisionProposalPayloadInvalid { 1435 event_id: proposal.event_id.clone(), 1436 }); 1437 valid = false; 1438 } 1439 if proposal.payload.order_id != request.payload.order_id { 1440 issues.push(RadrootsOrderIssue::RevisionProposalOrderIdMismatch { 1441 event_id: proposal.event_id.clone(), 1442 }); 1443 valid = false; 1444 } 1445 if proposal.author_pubkey != proposal.payload.seller_pubkey { 1446 issues.push(RadrootsOrderIssue::RevisionProposalAuthorMismatch { 1447 event_id: proposal.event_id.clone(), 1448 }); 1449 valid = false; 1450 } 1451 if proposal.counterparty_pubkey != request.payload.buyer_pubkey { 1452 issues.push(RadrootsOrderIssue::RevisionProposalCounterpartyMismatch { 1453 event_id: proposal.event_id.clone(), 1454 }); 1455 valid = false; 1456 } 1457 if proposal.payload.buyer_pubkey != request.payload.buyer_pubkey { 1458 issues.push(RadrootsOrderIssue::RevisionProposalBuyerMismatch { 1459 event_id: proposal.event_id.clone(), 1460 }); 1461 valid = false; 1462 } 1463 if proposal.payload.seller_pubkey != request.payload.seller_pubkey { 1464 issues.push(RadrootsOrderIssue::RevisionProposalSellerMismatch { 1465 event_id: proposal.event_id.clone(), 1466 }); 1467 valid = false; 1468 } 1469 match parse_public_listing_addr(&proposal.payload.listing_addr) { 1470 Ok(listing_addr) => { 1471 if proposal.payload.listing_addr != request.payload.listing_addr 1472 || listing_addr.seller_pubkey != proposal.payload.seller_pubkey 1473 { 1474 issues.push(RadrootsOrderIssue::RevisionProposalListingMismatch { 1475 event_id: proposal.event_id.clone(), 1476 }); 1477 valid = false; 1478 } 1479 } 1480 Err(_) => { 1481 issues.push(RadrootsOrderIssue::RevisionProposalListingAddressInvalid { 1482 event_id: proposal.event_id.clone(), 1483 }); 1484 valid = false; 1485 } 1486 } 1487 if proposal.root_event_id != request.event_id 1488 || proposal.payload.root_event_id != request.event_id 1489 { 1490 issues.push(RadrootsOrderIssue::RevisionProposalRootMismatch { 1491 event_id: proposal.event_id.clone(), 1492 }); 1493 valid = false; 1494 } 1495 if proposal.prev_event_id == proposal.event_id 1496 || proposal.payload.prev_event_id != proposal.prev_event_id 1497 { 1498 issues.push(RadrootsOrderIssue::RevisionProposalPreviousMismatch { 1499 event_id: proposal.event_id.clone(), 1500 }); 1501 valid = false; 1502 } 1503 valid 1504 } 1505 1506 fn validate_order_revision_decision_record( 1507 request: &RadrootsOrderRequestRecord, 1508 decision: &RadrootsOrderRevisionDecisionRecord, 1509 issues: &mut Vec<RadrootsOrderIssue>, 1510 ) -> bool { 1511 let mut valid = true; 1512 if decision.payload.validate().is_err() { 1513 issues.push(RadrootsOrderIssue::RevisionDecisionPayloadInvalid { 1514 event_id: decision.event_id.clone(), 1515 }); 1516 valid = false; 1517 } 1518 if decision.payload.order_id != request.payload.order_id { 1519 issues.push(RadrootsOrderIssue::RevisionDecisionOrderIdMismatch { 1520 event_id: decision.event_id.clone(), 1521 }); 1522 valid = false; 1523 } 1524 if decision.author_pubkey != decision.payload.buyer_pubkey { 1525 issues.push(RadrootsOrderIssue::RevisionDecisionAuthorMismatch { 1526 event_id: decision.event_id.clone(), 1527 }); 1528 valid = false; 1529 } 1530 if decision.counterparty_pubkey != request.payload.seller_pubkey { 1531 issues.push(RadrootsOrderIssue::RevisionDecisionCounterpartyMismatch { 1532 event_id: decision.event_id.clone(), 1533 }); 1534 valid = false; 1535 } 1536 if decision.payload.buyer_pubkey != request.payload.buyer_pubkey { 1537 issues.push(RadrootsOrderIssue::RevisionDecisionBuyerMismatch { 1538 event_id: decision.event_id.clone(), 1539 }); 1540 valid = false; 1541 } 1542 if decision.payload.seller_pubkey != request.payload.seller_pubkey { 1543 issues.push(RadrootsOrderIssue::RevisionDecisionSellerMismatch { 1544 event_id: decision.event_id.clone(), 1545 }); 1546 valid = false; 1547 } 1548 match parse_public_listing_addr(&decision.payload.listing_addr) { 1549 Ok(listing_addr) => { 1550 if decision.payload.listing_addr != request.payload.listing_addr 1551 || listing_addr.seller_pubkey != decision.payload.seller_pubkey 1552 { 1553 issues.push(RadrootsOrderIssue::RevisionDecisionListingMismatch { 1554 event_id: decision.event_id.clone(), 1555 }); 1556 valid = false; 1557 } 1558 } 1559 Err(_) => { 1560 issues.push(RadrootsOrderIssue::RevisionDecisionListingAddressInvalid { 1561 event_id: decision.event_id.clone(), 1562 }); 1563 valid = false; 1564 } 1565 } 1566 if decision.root_event_id != request.event_id 1567 || decision.payload.root_event_id != request.event_id 1568 { 1569 issues.push(RadrootsOrderIssue::RevisionDecisionRootMismatch { 1570 event_id: decision.event_id.clone(), 1571 }); 1572 valid = false; 1573 } 1574 if decision.prev_event_id == decision.event_id 1575 || decision.payload.prev_event_id != decision.prev_event_id 1576 { 1577 issues.push(RadrootsOrderIssue::RevisionDecisionPreviousMismatch { 1578 event_id: decision.event_id.clone(), 1579 }); 1580 valid = false; 1581 } 1582 valid 1583 } 1584 1585 fn validate_order_cancellation_record( 1586 request: &RadrootsOrderRequestRecord, 1587 cancellation: &RadrootsOrderCancellationRecord, 1588 issues: &mut Vec<RadrootsOrderIssue>, 1589 ) -> bool { 1590 let mut valid = true; 1591 if cancellation.payload.validate().is_err() { 1592 issues.push(RadrootsOrderIssue::CancellationPayloadInvalid { 1593 event_id: cancellation.event_id.clone(), 1594 }); 1595 valid = false; 1596 } 1597 if cancellation.payload.order_id != request.payload.order_id { 1598 issues.push(RadrootsOrderIssue::CancellationOrderIdMismatch { 1599 event_id: cancellation.event_id.clone(), 1600 }); 1601 valid = false; 1602 } 1603 if cancellation.author_pubkey != cancellation.payload.buyer_pubkey { 1604 issues.push(RadrootsOrderIssue::CancellationAuthorMismatch { 1605 event_id: cancellation.event_id.clone(), 1606 }); 1607 valid = false; 1608 } 1609 if cancellation.counterparty_pubkey != request.payload.seller_pubkey { 1610 issues.push(RadrootsOrderIssue::CancellationCounterpartyMismatch { 1611 event_id: cancellation.event_id.clone(), 1612 }); 1613 valid = false; 1614 } 1615 if cancellation.payload.buyer_pubkey != request.payload.buyer_pubkey { 1616 issues.push(RadrootsOrderIssue::CancellationBuyerMismatch { 1617 event_id: cancellation.event_id.clone(), 1618 }); 1619 valid = false; 1620 } 1621 if cancellation.payload.seller_pubkey != request.payload.seller_pubkey { 1622 issues.push(RadrootsOrderIssue::CancellationSellerMismatch { 1623 event_id: cancellation.event_id.clone(), 1624 }); 1625 valid = false; 1626 } 1627 match parse_public_listing_addr(&cancellation.payload.listing_addr) { 1628 Ok(listing_addr) => { 1629 if cancellation.payload.listing_addr != request.payload.listing_addr 1630 || listing_addr.seller_pubkey != cancellation.payload.seller_pubkey 1631 { 1632 issues.push(RadrootsOrderIssue::CancellationListingMismatch { 1633 event_id: cancellation.event_id.clone(), 1634 }); 1635 valid = false; 1636 } 1637 } 1638 Err(_) => { 1639 issues.push(RadrootsOrderIssue::CancellationListingAddressInvalid { 1640 event_id: cancellation.event_id.clone(), 1641 }); 1642 valid = false; 1643 } 1644 } 1645 if cancellation.root_event_id != request.event_id { 1646 issues.push(RadrootsOrderIssue::CancellationRootMismatch { 1647 event_id: cancellation.event_id.clone(), 1648 }); 1649 valid = false; 1650 } 1651 if cancellation.prev_event_id == cancellation.event_id { 1652 issues.push(RadrootsOrderIssue::CancellationPreviousMismatch { 1653 event_id: cancellation.event_id.clone(), 1654 }); 1655 valid = false; 1656 } 1657 valid 1658 } 1659 1660 fn decision_payload_issue( 1661 decision: &RadrootsOrderDecisionOutcome, 1662 event_id: &RadrootsEventId, 1663 issues: &mut Vec<RadrootsOrderIssue>, 1664 ) -> bool { 1665 match decision { 1666 RadrootsOrderDecisionOutcome::Accepted { 1667 inventory_commitments, 1668 } => { 1669 if inventory_commitments.is_empty() { 1670 issues.push(RadrootsOrderIssue::DecisionMissingInventoryCommitments { 1671 event_id: event_id.clone(), 1672 }); 1673 true 1674 } else { 1675 false 1676 } 1677 } 1678 RadrootsOrderDecisionOutcome::Declined { reason } => { 1679 if reason.trim().is_empty() { 1680 issues.push(RadrootsOrderIssue::DecisionMissingReason { 1681 event_id: event_id.clone(), 1682 }); 1683 true 1684 } else { 1685 false 1686 } 1687 } 1688 } 1689 } 1690 1691 fn unique_request_records( 1692 requests: Vec<RadrootsOrderRequestRecord>, 1693 ) -> Vec<RadrootsOrderRequestRecord> { 1694 unique_records_by_event_id(requests, |record| &record.event_id) 1695 } 1696 1697 fn unique_decision_records( 1698 decisions: Vec<RadrootsOrderDecisionRecord>, 1699 ) -> Vec<RadrootsOrderDecisionRecord> { 1700 unique_records_by_event_id(decisions, |record| &record.event_id) 1701 } 1702 1703 fn unique_revision_proposal_records( 1704 revision_proposals: Vec<RadrootsOrderRevisionProposalRecord>, 1705 ) -> Vec<RadrootsOrderRevisionProposalRecord> { 1706 unique_records_by_event_id(revision_proposals, |record| &record.event_id) 1707 } 1708 1709 fn unique_revision_decision_records( 1710 revision_decisions: Vec<RadrootsOrderRevisionDecisionRecord>, 1711 ) -> Vec<RadrootsOrderRevisionDecisionRecord> { 1712 unique_records_by_event_id(revision_decisions, |record| &record.event_id) 1713 } 1714 1715 fn unique_cancellation_records( 1716 cancellations: Vec<RadrootsOrderCancellationRecord>, 1717 ) -> Vec<RadrootsOrderCancellationRecord> { 1718 unique_records_by_event_id(cancellations, |record| &record.event_id) 1719 } 1720 1721 fn unique_records_by_event_id<T>( 1722 mut records: Vec<T>, 1723 event_id: impl Fn(&T) -> &RadrootsEventId, 1724 ) -> Vec<T> { 1725 let mut unique = Vec::new(); 1726 records.sort_by(|left, right| event_id(left).cmp(event_id(right))); 1727 for record in records { 1728 if unique 1729 .iter() 1730 .all(|existing: &T| event_id(existing) != event_id(&record)) 1731 { 1732 unique.push(record); 1733 } 1734 } 1735 unique 1736 } 1737 1738 fn normalized_listing_inventory_bins<I>( 1739 bins: I, 1740 ) -> ( 1741 Vec<RadrootsListingInventoryBinAccounting>, 1742 Vec<RadrootsListingInventoryAccountingIssue>, 1743 ) 1744 where 1745 I: IntoIterator<Item = RadrootsListingInventoryBinAvailability>, 1746 { 1747 let mut normalized: Vec<RadrootsListingInventoryBinAccounting> = Vec::new(); 1748 let mut issues = Vec::new(); 1749 for bin in bins { 1750 let bin_id = bin.bin_id; 1751 if let Some(existing) = normalized 1752 .iter_mut() 1753 .find(|existing| existing.bin_id == bin_id) 1754 { 1755 if let Some(next_count) = existing.available_count.checked_add(bin.available_count) { 1756 existing.available_count = next_count; 1757 existing.remaining_count = next_count; 1758 } else { 1759 existing.available_count = u64::MAX; 1760 existing.remaining_count = u64::MAX; 1761 issues.push( 1762 RadrootsListingInventoryAccountingIssue::ArithmeticOverflow { 1763 bin_id: existing.bin_id.clone(), 1764 event_ids: Vec::new(), 1765 }, 1766 ); 1767 } 1768 } else { 1769 normalized.push(RadrootsListingInventoryBinAccounting { 1770 bin_id, 1771 available_count: bin.available_count, 1772 accepted_reserved_count: 0, 1773 remaining_count: bin.available_count, 1774 over_reserved: false, 1775 accepted_orders: Vec::new(), 1776 }); 1777 } 1778 } 1779 normalized.sort_by(|left, right| left.bin_id.cmp(&right.bin_id)); 1780 (normalized, issues) 1781 } 1782 1783 fn listing_order_ids( 1784 requests: &[RadrootsOrderRequestRecord], 1785 decisions: &[RadrootsOrderDecisionRecord], 1786 revision_proposals: &[RadrootsOrderRevisionProposalRecord], 1787 revision_decisions: &[RadrootsOrderRevisionDecisionRecord], 1788 cancellations: &[RadrootsOrderCancellationRecord], 1789 ) -> Vec<RadrootsOrderId> { 1790 let mut order_ids = Vec::new(); 1791 order_ids.extend( 1792 requests 1793 .iter() 1794 .map(|request| request.payload.order_id.clone()), 1795 ); 1796 order_ids.extend( 1797 decisions 1798 .iter() 1799 .map(|decision| decision.payload.order_id.clone()), 1800 ); 1801 order_ids.extend( 1802 revision_proposals 1803 .iter() 1804 .map(|proposal| proposal.payload.order_id.clone()), 1805 ); 1806 order_ids.extend( 1807 revision_decisions 1808 .iter() 1809 .map(|decision| decision.payload.order_id.clone()), 1810 ); 1811 order_ids.extend( 1812 cancellations 1813 .iter() 1814 .map(|cancellation| cancellation.payload.order_id.clone()), 1815 ); 1816 sort_and_dedup_values(&mut order_ids); 1817 order_ids 1818 } 1819 1820 fn add_accepted_inventory_reservations_from_economics( 1821 bins: &mut [RadrootsListingInventoryBinAccounting], 1822 order_id: &RadrootsOrderId, 1823 agreement_event_id: &RadrootsEventId, 1824 economics: &RadrootsOrderEconomics, 1825 issues: &mut Vec<RadrootsListingInventoryAccountingIssue>, 1826 ) { 1827 for item in &economics.items { 1828 if let Some(bin) = bins.iter_mut().find(|bin| bin.bin_id == item.bin_id) { 1829 add_inventory_reservation_event( 1830 bin, 1831 order_id, 1832 agreement_event_id, 1833 u64::from(item.bin_count), 1834 issues, 1835 ); 1836 } else { 1837 issues.push( 1838 RadrootsListingInventoryAccountingIssue::UnknownInventoryBin { 1839 bin_id: item.bin_id.clone(), 1840 event_ids: vec![agreement_event_id.clone()], 1841 }, 1842 ); 1843 } 1844 } 1845 } 1846 1847 fn add_inventory_reservation_event( 1848 bin: &mut RadrootsListingInventoryBinAccounting, 1849 order_id: &RadrootsOrderId, 1850 event_id: &RadrootsEventId, 1851 bin_count: u64, 1852 issues: &mut Vec<RadrootsListingInventoryAccountingIssue>, 1853 ) { 1854 if let Some(next_count) = bin.accepted_reserved_count.checked_add(bin_count) { 1855 bin.accepted_reserved_count = next_count; 1856 bin.accepted_orders 1857 .push(RadrootsListingInventoryOrderReservation { 1858 order_id: order_id.clone(), 1859 agreement_event_id: event_id.clone(), 1860 bin_count, 1861 }); 1862 } else { 1863 issues.push( 1864 RadrootsListingInventoryAccountingIssue::ArithmeticOverflow { 1865 bin_id: bin.bin_id.clone(), 1866 event_ids: vec![event_id.clone()], 1867 }, 1868 ); 1869 } 1870 } 1871 1872 fn finish_inventory_accounting_bins( 1873 bins: &mut [RadrootsListingInventoryBinAccounting], 1874 issues: &mut Vec<RadrootsListingInventoryAccountingIssue>, 1875 ) { 1876 for bin in bins.iter_mut() { 1877 bin.accepted_orders.sort_by(|left, right| { 1878 left.order_id 1879 .cmp(&right.order_id) 1880 .then_with(|| left.agreement_event_id.cmp(&right.agreement_event_id)) 1881 }); 1882 bin.remaining_count = bin 1883 .available_count 1884 .saturating_sub(bin.accepted_reserved_count); 1885 bin.over_reserved = bin.accepted_reserved_count > bin.available_count; 1886 if bin.over_reserved { 1887 let mut event_ids = bin 1888 .accepted_orders 1889 .iter() 1890 .map(|reservation| reservation.agreement_event_id.clone()) 1891 .collect::<Vec<_>>(); 1892 sort_and_dedup_values(&mut event_ids); 1893 issues.push(RadrootsListingInventoryAccountingIssue::OverReserved { 1894 bin_id: bin.bin_id.clone(), 1895 available_count: bin.available_count, 1896 reserved_count: bin.accepted_reserved_count, 1897 event_ids, 1898 }); 1899 } 1900 } 1901 bins.sort_by(|left, right| left.bin_id.cmp(&right.bin_id)); 1902 } 1903 1904 fn projection_issue_event_ids(issues: &[RadrootsOrderIssue]) -> Vec<RadrootsEventId> { 1905 let mut event_ids = Vec::new(); 1906 for issue in issues { 1907 match issue { 1908 RadrootsOrderIssue::MissingRequest => {} 1909 RadrootsOrderIssue::MultipleRequests { event_ids: ids } 1910 | RadrootsOrderIssue::ConflictingDecisions { event_ids: ids } 1911 | RadrootsOrderIssue::ForkedLifecycle { event_ids: ids } => { 1912 event_ids.extend(ids.iter().cloned()); 1913 } 1914 RadrootsOrderIssue::RequestPayloadInvalid { event_id } 1915 | RadrootsOrderIssue::RequestOrderIdMismatch { event_id } 1916 | RadrootsOrderIssue::RequestAuthorMismatch { event_id } 1917 | RadrootsOrderIssue::RequestListingAddressInvalid { event_id } 1918 | RadrootsOrderIssue::RequestSellerListingMismatch { event_id } 1919 | RadrootsOrderIssue::DecisionPayloadInvalid { event_id } 1920 | RadrootsOrderIssue::DecisionOrderIdMismatch { event_id } 1921 | RadrootsOrderIssue::DecisionAuthorMismatch { event_id } 1922 | RadrootsOrderIssue::DecisionCounterpartyMismatch { event_id } 1923 | RadrootsOrderIssue::DecisionBuyerMismatch { event_id } 1924 | RadrootsOrderIssue::DecisionSellerMismatch { event_id } 1925 | RadrootsOrderIssue::DecisionListingAddressInvalid { event_id } 1926 | RadrootsOrderIssue::DecisionListingMismatch { event_id } 1927 | RadrootsOrderIssue::DecisionRootMismatch { event_id } 1928 | RadrootsOrderIssue::DecisionPreviousMismatch { event_id } 1929 | RadrootsOrderIssue::DecisionMissingInventoryCommitments { event_id } 1930 | RadrootsOrderIssue::DecisionInventoryCommitmentMismatch { event_id } 1931 | RadrootsOrderIssue::DecisionMissingReason { event_id } 1932 | RadrootsOrderIssue::RevisionProposalPayloadInvalid { event_id } 1933 | RadrootsOrderIssue::RevisionProposalOrderIdMismatch { event_id } 1934 | RadrootsOrderIssue::RevisionProposalAuthorMismatch { event_id } 1935 | RadrootsOrderIssue::RevisionProposalCounterpartyMismatch { event_id } 1936 | RadrootsOrderIssue::RevisionProposalBuyerMismatch { event_id } 1937 | RadrootsOrderIssue::RevisionProposalSellerMismatch { event_id } 1938 | RadrootsOrderIssue::RevisionProposalListingAddressInvalid { event_id } 1939 | RadrootsOrderIssue::RevisionProposalListingMismatch { event_id } 1940 | RadrootsOrderIssue::RevisionProposalRootMismatch { event_id } 1941 | RadrootsOrderIssue::RevisionProposalPreviousMismatch { event_id } 1942 | RadrootsOrderIssue::RevisionDecisionWithoutProposal { event_id } 1943 | RadrootsOrderIssue::RevisionDecisionPayloadInvalid { event_id } 1944 | RadrootsOrderIssue::RevisionDecisionOrderIdMismatch { event_id } 1945 | RadrootsOrderIssue::RevisionDecisionAuthorMismatch { event_id } 1946 | RadrootsOrderIssue::RevisionDecisionCounterpartyMismatch { event_id } 1947 | RadrootsOrderIssue::RevisionDecisionBuyerMismatch { event_id } 1948 | RadrootsOrderIssue::RevisionDecisionSellerMismatch { event_id } 1949 | RadrootsOrderIssue::RevisionDecisionListingAddressInvalid { event_id } 1950 | RadrootsOrderIssue::RevisionDecisionListingMismatch { event_id } 1951 | RadrootsOrderIssue::RevisionDecisionRootMismatch { event_id } 1952 | RadrootsOrderIssue::RevisionDecisionPreviousMismatch { event_id } 1953 | RadrootsOrderIssue::RevisionDecisionRevisionIdMismatch { event_id } 1954 | RadrootsOrderIssue::CancellationWithoutCancellableOrder { event_id } 1955 | RadrootsOrderIssue::CancellationPayloadInvalid { event_id } 1956 | RadrootsOrderIssue::CancellationOrderIdMismatch { event_id } 1957 | RadrootsOrderIssue::CancellationAuthorMismatch { event_id } 1958 | RadrootsOrderIssue::CancellationCounterpartyMismatch { event_id } 1959 | RadrootsOrderIssue::CancellationBuyerMismatch { event_id } 1960 | RadrootsOrderIssue::CancellationSellerMismatch { event_id } 1961 | RadrootsOrderIssue::CancellationListingAddressInvalid { event_id } 1962 | RadrootsOrderIssue::CancellationListingMismatch { event_id } 1963 | RadrootsOrderIssue::CancellationRootMismatch { event_id } 1964 | RadrootsOrderIssue::CancellationPreviousMismatch { event_id } => { 1965 event_ids.push(event_id.clone()); 1966 } 1967 } 1968 } 1969 sort_and_dedup_values(&mut event_ids); 1970 event_ids 1971 } 1972 1973 fn parse_public_listing_addr( 1974 value: impl AsRef<str>, 1975 ) -> Result<RadrootsPublicListingAddress, RadrootsOrderCanonicalizationError> { 1976 parse_public_listing_address(value).map_err(|error| match error { 1977 RadrootsPublicListingAddressError::InvalidAddress(error) => { 1978 RadrootsOrderCanonicalizationError::InvalidListingAddress(error.to_string()) 1979 } 1980 RadrootsPublicListingAddressError::InvalidListingKind { .. } => { 1981 RadrootsOrderCanonicalizationError::InvalidListingKind 1982 } 1983 RadrootsPublicListingAddressError::InvalidKind { .. } => { 1984 RadrootsOrderCanonicalizationError::InvalidListingKind 1985 } 1986 }) 1987 } 1988 1989 fn canonicalize_items( 1990 items: &mut [RadrootsOrderItem], 1991 ) -> Result<(), RadrootsOrderCanonicalizationError> { 1992 if items.is_empty() { 1993 return Err(RadrootsOrderCanonicalizationError::MissingItems); 1994 } 1995 for (index, item) in items.iter().enumerate() { 1996 if item.bin_count == 0 { 1997 return Err(RadrootsOrderCanonicalizationError::InvalidBinCount { index }); 1998 } 1999 } 2000 items.sort_by(|left, right| left.bin_id.cmp(&right.bin_id)); 2001 Ok(()) 2002 } 2003 2004 fn canonicalize_decision( 2005 decision: &mut RadrootsOrderDecisionOutcome, 2006 ) -> Result<(), RadrootsOrderCanonicalizationError> { 2007 match decision { 2008 RadrootsOrderDecisionOutcome::Accepted { 2009 inventory_commitments, 2010 } => { 2011 if inventory_commitments.is_empty() { 2012 return Err(RadrootsOrderCanonicalizationError::MissingInventoryCommitments); 2013 } 2014 for (index, commitment) in inventory_commitments.iter().enumerate() { 2015 if commitment.bin_count == 0 { 2016 return Err( 2017 RadrootsOrderCanonicalizationError::InvalidInventoryCommitmentCount { 2018 index, 2019 }, 2020 ); 2021 } 2022 } 2023 inventory_commitments.sort_by(|left, right| left.bin_id.cmp(&right.bin_id)); 2024 Ok(()) 2025 } 2026 RadrootsOrderDecisionOutcome::Declined { reason } => { 2027 if reason.trim().is_empty() { 2028 return Err(RadrootsOrderCanonicalizationError::EmptyField("reason")); 2029 } 2030 *reason = reason.trim().to_string(); 2031 Ok(()) 2032 } 2033 } 2034 } 2035 2036 fn inventory_commitments_match_request( 2037 items: &[RadrootsOrderItem], 2038 commitments: &[RadrootsOrderInventoryCommitment], 2039 ) -> bool { 2040 if items.len() != commitments.len() { 2041 return false; 2042 } 2043 let mut expected = items.to_vec(); 2044 expected.sort_by(|left, right| left.bin_id.cmp(&right.bin_id)); 2045 let mut actual = commitments.to_vec(); 2046 actual.sort_by(|left, right| left.bin_id.cmp(&right.bin_id)); 2047 expected 2048 .iter() 2049 .zip(actual.iter()) 2050 .all(|(item, commitment)| { 2051 item.bin_id == commitment.bin_id && item.bin_count == commitment.bin_count 2052 }) 2053 } 2054 2055 fn sort_and_dedup_values<T: Ord>(values: &mut Vec<T>) { 2056 values.sort(); 2057 values.dedup(); 2058 } 2059 2060 fn inventory_issue_sort_key( 2061 left: &RadrootsListingInventoryAccountingIssue, 2062 right: &RadrootsListingInventoryAccountingIssue, 2063 ) -> core::cmp::Ordering { 2064 inventory_issue_rank(left) 2065 .cmp(&inventory_issue_rank(right)) 2066 .then_with(|| inventory_issue_id(left).cmp(inventory_issue_id(right))) 2067 .then_with(|| inventory_issue_event_ids(left).cmp(inventory_issue_event_ids(right))) 2068 } 2069 2070 fn inventory_issue_rank(issue: &RadrootsListingInventoryAccountingIssue) -> u8 { 2071 match issue { 2072 RadrootsListingInventoryAccountingIssue::InvalidOrder { .. } => 0, 2073 RadrootsListingInventoryAccountingIssue::ArithmeticOverflow { .. } => 1, 2074 RadrootsListingInventoryAccountingIssue::UnknownInventoryBin { .. } => 2, 2075 RadrootsListingInventoryAccountingIssue::OverReserved { .. } => 3, 2076 } 2077 } 2078 2079 fn inventory_issue_id(issue: &RadrootsListingInventoryAccountingIssue) -> &str { 2080 match issue { 2081 RadrootsListingInventoryAccountingIssue::InvalidOrder { order_id, .. } => order_id, 2082 RadrootsListingInventoryAccountingIssue::ArithmeticOverflow { bin_id, .. } 2083 | RadrootsListingInventoryAccountingIssue::UnknownInventoryBin { bin_id, .. } 2084 | RadrootsListingInventoryAccountingIssue::OverReserved { bin_id, .. } => bin_id, 2085 } 2086 } 2087 2088 fn inventory_issue_event_ids( 2089 issue: &RadrootsListingInventoryAccountingIssue, 2090 ) -> &[RadrootsEventId] { 2091 match issue { 2092 RadrootsListingInventoryAccountingIssue::InvalidOrder { event_ids, .. } 2093 | RadrootsListingInventoryAccountingIssue::ArithmeticOverflow { event_ids, .. } 2094 | RadrootsListingInventoryAccountingIssue::UnknownInventoryBin { event_ids, .. } 2095 | RadrootsListingInventoryAccountingIssue::OverReserved { event_ids, .. } => event_ids, 2096 } 2097 } 2098 2099 fn order_issue_sort_key( 2100 left: &RadrootsOrderIssue, 2101 right: &RadrootsOrderIssue, 2102 ) -> core::cmp::Ordering { 2103 order_issue_rank(left) 2104 .cmp(&order_issue_rank(right)) 2105 .then_with(|| { 2106 projection_issue_event_ids(core::slice::from_ref(left)) 2107 .cmp(&projection_issue_event_ids(core::slice::from_ref(right))) 2108 }) 2109 } 2110 2111 fn order_issue_rank(issue: &RadrootsOrderIssue) -> u8 { 2112 match issue { 2113 RadrootsOrderIssue::MissingRequest => 0, 2114 RadrootsOrderIssue::MultipleRequests { .. } => 1, 2115 RadrootsOrderIssue::RequestPayloadInvalid { .. } => 2, 2116 RadrootsOrderIssue::RequestOrderIdMismatch { .. } => 3, 2117 RadrootsOrderIssue::RequestAuthorMismatch { .. } => 4, 2118 RadrootsOrderIssue::RequestListingAddressInvalid { .. } => 5, 2119 RadrootsOrderIssue::RequestSellerListingMismatch { .. } => 6, 2120 RadrootsOrderIssue::DecisionPayloadInvalid { .. } => 7, 2121 RadrootsOrderIssue::DecisionOrderIdMismatch { .. } => 8, 2122 RadrootsOrderIssue::DecisionAuthorMismatch { .. } => 9, 2123 RadrootsOrderIssue::DecisionCounterpartyMismatch { .. } => 10, 2124 RadrootsOrderIssue::DecisionBuyerMismatch { .. } => 11, 2125 RadrootsOrderIssue::DecisionSellerMismatch { .. } => 12, 2126 RadrootsOrderIssue::DecisionListingAddressInvalid { .. } => 13, 2127 RadrootsOrderIssue::DecisionListingMismatch { .. } => 14, 2128 RadrootsOrderIssue::DecisionRootMismatch { .. } => 15, 2129 RadrootsOrderIssue::DecisionPreviousMismatch { .. } => 16, 2130 RadrootsOrderIssue::DecisionMissingInventoryCommitments { .. } => 17, 2131 RadrootsOrderIssue::DecisionInventoryCommitmentMismatch { .. } => 18, 2132 RadrootsOrderIssue::DecisionMissingReason { .. } => 19, 2133 RadrootsOrderIssue::ConflictingDecisions { .. } => 20, 2134 RadrootsOrderIssue::RevisionProposalPayloadInvalid { .. } => 21, 2135 RadrootsOrderIssue::RevisionProposalOrderIdMismatch { .. } => 22, 2136 RadrootsOrderIssue::RevisionProposalAuthorMismatch { .. } => 23, 2137 RadrootsOrderIssue::RevisionProposalCounterpartyMismatch { .. } => 24, 2138 RadrootsOrderIssue::RevisionProposalBuyerMismatch { .. } => 25, 2139 RadrootsOrderIssue::RevisionProposalSellerMismatch { .. } => 26, 2140 RadrootsOrderIssue::RevisionProposalListingAddressInvalid { .. } => 27, 2141 RadrootsOrderIssue::RevisionProposalListingMismatch { .. } => 28, 2142 RadrootsOrderIssue::RevisionProposalRootMismatch { .. } => 29, 2143 RadrootsOrderIssue::RevisionProposalPreviousMismatch { .. } => 30, 2144 RadrootsOrderIssue::RevisionDecisionWithoutProposal { .. } => 31, 2145 RadrootsOrderIssue::RevisionDecisionPayloadInvalid { .. } => 32, 2146 RadrootsOrderIssue::RevisionDecisionOrderIdMismatch { .. } => 33, 2147 RadrootsOrderIssue::RevisionDecisionAuthorMismatch { .. } => 34, 2148 RadrootsOrderIssue::RevisionDecisionCounterpartyMismatch { .. } => 35, 2149 RadrootsOrderIssue::RevisionDecisionBuyerMismatch { .. } => 36, 2150 RadrootsOrderIssue::RevisionDecisionSellerMismatch { .. } => 37, 2151 RadrootsOrderIssue::RevisionDecisionListingAddressInvalid { .. } => 38, 2152 RadrootsOrderIssue::RevisionDecisionListingMismatch { .. } => 39, 2153 RadrootsOrderIssue::RevisionDecisionRootMismatch { .. } => 40, 2154 RadrootsOrderIssue::RevisionDecisionPreviousMismatch { .. } => 41, 2155 RadrootsOrderIssue::RevisionDecisionRevisionIdMismatch { .. } => 42, 2156 RadrootsOrderIssue::CancellationWithoutCancellableOrder { .. } => 43, 2157 RadrootsOrderIssue::CancellationPayloadInvalid { .. } => 44, 2158 RadrootsOrderIssue::CancellationOrderIdMismatch { .. } => 45, 2159 RadrootsOrderIssue::CancellationAuthorMismatch { .. } => 46, 2160 RadrootsOrderIssue::CancellationCounterpartyMismatch { .. } => 47, 2161 RadrootsOrderIssue::CancellationBuyerMismatch { .. } => 48, 2162 RadrootsOrderIssue::CancellationSellerMismatch { .. } => 49, 2163 RadrootsOrderIssue::CancellationListingAddressInvalid { .. } => 50, 2164 RadrootsOrderIssue::CancellationListingMismatch { .. } => 51, 2165 RadrootsOrderIssue::CancellationRootMismatch { .. } => 52, 2166 RadrootsOrderIssue::CancellationPreviousMismatch { .. } => 53, 2167 RadrootsOrderIssue::ForkedLifecycle { .. } => 54, 2168 } 2169 } 2170 2171 #[cfg(test)] 2172 #[cfg_attr(coverage_nightly, coverage(off))] 2173 mod tests { 2174 use super::{ 2175 RadrootsListingInventoryAccountingInputs, RadrootsListingInventoryAccountingIssue, 2176 RadrootsListingInventoryBinAvailability, RadrootsOrderCancellationRecord, 2177 RadrootsOrderDecisionRecord, RadrootsOrderEventRecord, RadrootsOrderIssue, 2178 RadrootsOrderReductionInputs, RadrootsOrderRequestRecord, 2179 RadrootsOrderRevisionDecisionRecord, RadrootsOrderRevisionProposalRecord, 2180 RadrootsOrderStatus, reduce_listing_inventory_accounting, reduce_order_event_records, 2181 reduce_order_events, 2182 }; 2183 use core::mem::discriminant; 2184 use radroots_core::{ 2185 RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreUnit, 2186 }; 2187 use radroots_events::{ 2188 RadrootsNostrEvent, RadrootsNostrEventPtr, 2189 ids::{ 2190 RadrootsEventId, RadrootsInventoryBinId, RadrootsListingAddress, RadrootsOrderId, 2191 RadrootsOrderQuoteId, RadrootsOrderRevisionId, RadrootsPublicKey, 2192 }, 2193 kinds::{KIND_LISTING, KIND_LISTING_DRAFT}, 2194 order::{ 2195 RadrootsOrderCancellation, RadrootsOrderDecision, RadrootsOrderDecisionOutcome, 2196 RadrootsOrderEconomicItem, RadrootsOrderEconomics, RadrootsOrderInventoryCommitment, 2197 RadrootsOrderItem, RadrootsOrderPricingBasis, RadrootsOrderRequest, 2198 RadrootsOrderRevisionDecision, RadrootsOrderRevisionOutcome, 2199 RadrootsOrderRevisionProposal, 2200 }, 2201 }; 2202 #[cfg(feature = "serde_json")] 2203 use radroots_events_codec::{ 2204 order::{ 2205 order_cancellation_event_build, order_decision_event_build, order_request_event_build, 2206 order_revision_decision_event_build, order_revision_proposal_event_build, 2207 }, 2208 wire::WireEventParts, 2209 }; 2210 2211 const BUYER: &str = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; 2212 const SELLER: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 2213 const OTHER: &str = "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"; 2214 2215 fn event_id(raw: u8) -> RadrootsEventId { 2216 RadrootsEventId::parse(format!("{raw:064x}")).expect("event id") 2217 } 2218 2219 fn public_key(raw: &str) -> RadrootsPublicKey { 2220 RadrootsPublicKey::parse(raw).expect("public key") 2221 } 2222 2223 fn order_id(raw: &str) -> RadrootsOrderId { 2224 RadrootsOrderId::parse(raw).expect("order id") 2225 } 2226 2227 fn revision_id(raw: &str) -> RadrootsOrderRevisionId { 2228 RadrootsOrderRevisionId::parse(raw).expect("revision id") 2229 } 2230 2231 fn quote_id(raw: &str) -> RadrootsOrderQuoteId { 2232 RadrootsOrderQuoteId::parse(raw).expect("quote id") 2233 } 2234 2235 fn bin_id(raw: &str) -> RadrootsInventoryBinId { 2236 RadrootsInventoryBinId::parse(raw).expect("bin id") 2237 } 2238 2239 fn listing_addr() -> RadrootsListingAddress { 2240 RadrootsListingAddress::parse(format!("{KIND_LISTING}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg")) 2241 .expect("listing address") 2242 } 2243 2244 fn draft_listing_addr() -> RadrootsListingAddress { 2245 RadrootsListingAddress::parse(format!( 2246 "{KIND_LISTING_DRAFT}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg" 2247 )) 2248 .expect("draft listing address") 2249 } 2250 2251 fn other_seller_listing_addr() -> RadrootsListingAddress { 2252 RadrootsListingAddress::parse(format!("{KIND_LISTING}:{OTHER}:AAAAAAAAAAAAAAAAAAAAAg")) 2253 .expect("other seller listing address") 2254 } 2255 2256 #[cfg(feature = "serde_json")] 2257 fn listing_event_ptr() -> RadrootsNostrEventPtr { 2258 RadrootsNostrEventPtr { 2259 id: event_id(80).into_string(), 2260 relays: Some("wss://relay.example.test".into()), 2261 } 2262 } 2263 2264 #[cfg(feature = "serde_json")] 2265 fn event_from_parts(raw_id: u8, author: &str, parts: WireEventParts) -> RadrootsNostrEvent { 2266 RadrootsNostrEvent { 2267 id: event_id(raw_id).into_string(), 2268 author: author.into(), 2269 created_at: 1, 2270 kind: parts.kind, 2271 tags: parts.tags, 2272 content: parts.content, 2273 sig: "sig".into(), 2274 } 2275 } 2276 2277 fn economics(bin_count: u32) -> RadrootsOrderEconomics { 2278 let currency = RadrootsCoreCurrency::USD; 2279 let amount = RadrootsCoreDecimal::from(1200u32); 2280 RadrootsOrderEconomics { 2281 quote_id: quote_id("quote-1"), 2282 quote_version: 1, 2283 pricing_basis: RadrootsOrderPricingBasis::ListingEvent, 2284 currency, 2285 items: vec![RadrootsOrderEconomicItem { 2286 bin_id: bin_id("bin-1"), 2287 bin_count, 2288 quantity_amount: RadrootsCoreDecimal::ONE, 2289 quantity_unit: RadrootsCoreUnit::Each, 2290 unit_price_amount: amount, 2291 unit_price_currency: currency, 2292 line_subtotal: RadrootsCoreMoney::new( 2293 RadrootsCoreDecimal::from(u64::from(bin_count) * 1200), 2294 currency, 2295 ), 2296 }], 2297 discounts: Vec::new(), 2298 adjustments: Vec::new(), 2299 subtotal: RadrootsCoreMoney::new( 2300 RadrootsCoreDecimal::from(u64::from(bin_count) * 1200), 2301 currency, 2302 ), 2303 discount_total: RadrootsCoreMoney::zero(currency), 2304 adjustment_total: RadrootsCoreMoney::zero(currency), 2305 total: RadrootsCoreMoney::new( 2306 RadrootsCoreDecimal::from(u64::from(bin_count) * 1200), 2307 currency, 2308 ), 2309 } 2310 } 2311 2312 fn request_record() -> RadrootsOrderRequestRecord { 2313 RadrootsOrderRequestRecord { 2314 event_id: event_id(1), 2315 author_pubkey: public_key(BUYER), 2316 payload: RadrootsOrderRequest { 2317 order_id: order_id("order-1"), 2318 listing_addr: listing_addr(), 2319 buyer_pubkey: public_key(BUYER), 2320 seller_pubkey: public_key(SELLER), 2321 items: vec![RadrootsOrderItem { 2322 bin_id: bin_id("bin-1"), 2323 bin_count: 2, 2324 }], 2325 economics: economics(2), 2326 }, 2327 } 2328 } 2329 2330 fn accepted_decision() -> RadrootsOrderDecisionRecord { 2331 RadrootsOrderDecisionRecord { 2332 event_id: event_id(2), 2333 author_pubkey: public_key(SELLER), 2334 counterparty_pubkey: public_key(BUYER), 2335 root_event_id: event_id(1), 2336 prev_event_id: event_id(1), 2337 payload: RadrootsOrderDecision { 2338 order_id: order_id("order-1"), 2339 listing_addr: listing_addr(), 2340 buyer_pubkey: public_key(BUYER), 2341 seller_pubkey: public_key(SELLER), 2342 decision: RadrootsOrderDecisionOutcome::Accepted { 2343 inventory_commitments: vec![RadrootsOrderInventoryCommitment { 2344 bin_id: bin_id("bin-1"), 2345 bin_count: 2, 2346 }], 2347 }, 2348 }, 2349 } 2350 } 2351 2352 fn declined_decision() -> RadrootsOrderDecisionRecord { 2353 RadrootsOrderDecisionRecord { 2354 event_id: event_id(2), 2355 author_pubkey: public_key(SELLER), 2356 counterparty_pubkey: public_key(BUYER), 2357 root_event_id: event_id(1), 2358 prev_event_id: event_id(1), 2359 payload: RadrootsOrderDecision { 2360 order_id: order_id("order-1"), 2361 listing_addr: listing_addr(), 2362 buyer_pubkey: public_key(BUYER), 2363 seller_pubkey: public_key(SELLER), 2364 decision: RadrootsOrderDecisionOutcome::Declined { 2365 reason: "not available".into(), 2366 }, 2367 }, 2368 } 2369 } 2370 2371 fn revision_proposal() -> RadrootsOrderRevisionProposalRecord { 2372 RadrootsOrderRevisionProposalRecord { 2373 event_id: event_id(3), 2374 author_pubkey: public_key(SELLER), 2375 counterparty_pubkey: public_key(BUYER), 2376 root_event_id: event_id(1), 2377 prev_event_id: event_id(1), 2378 payload: RadrootsOrderRevisionProposal { 2379 revision_id: revision_id("revision-1"), 2380 order_id: order_id("order-1"), 2381 listing_addr: listing_addr(), 2382 buyer_pubkey: public_key(BUYER), 2383 seller_pubkey: public_key(SELLER), 2384 root_event_id: event_id(1), 2385 prev_event_id: event_id(1), 2386 items: vec![RadrootsOrderItem { 2387 bin_id: bin_id("bin-1"), 2388 bin_count: 1, 2389 }], 2390 economics: economics(1), 2391 reason: "one bin remains".into(), 2392 }, 2393 } 2394 } 2395 2396 fn accepted_revision_decision() -> RadrootsOrderRevisionDecisionRecord { 2397 RadrootsOrderRevisionDecisionRecord { 2398 event_id: event_id(4), 2399 author_pubkey: public_key(BUYER), 2400 counterparty_pubkey: public_key(SELLER), 2401 root_event_id: event_id(1), 2402 prev_event_id: event_id(3), 2403 payload: RadrootsOrderRevisionDecision { 2404 revision_id: revision_id("revision-1"), 2405 order_id: order_id("order-1"), 2406 listing_addr: listing_addr(), 2407 buyer_pubkey: public_key(BUYER), 2408 seller_pubkey: public_key(SELLER), 2409 root_event_id: event_id(1), 2410 prev_event_id: event_id(3), 2411 decision: RadrootsOrderRevisionOutcome::Accepted, 2412 }, 2413 } 2414 } 2415 2416 fn cancellation(prev_event_id: RadrootsEventId) -> RadrootsOrderCancellationRecord { 2417 RadrootsOrderCancellationRecord { 2418 event_id: event_id(5), 2419 author_pubkey: public_key(BUYER), 2420 counterparty_pubkey: public_key(SELLER), 2421 root_event_id: event_id(1), 2422 prev_event_id, 2423 payload: RadrootsOrderCancellation { 2424 order_id: order_id("order-1"), 2425 listing_addr: listing_addr(), 2426 buyer_pubkey: public_key(BUYER), 2427 seller_pubkey: public_key(SELLER), 2428 reason: "changed plans".into(), 2429 }, 2430 } 2431 } 2432 2433 fn assert_order_issue_kind(issues: &[RadrootsOrderIssue], expected: RadrootsOrderIssue) { 2434 let expected_kind = discriminant(&expected); 2435 assert!( 2436 issues 2437 .iter() 2438 .any(|issue| discriminant(issue) == expected_kind), 2439 "missing issue kind {expected:?} in {issues:?}" 2440 ); 2441 } 2442 2443 fn assert_inventory_issue_kind( 2444 issues: &[RadrootsListingInventoryAccountingIssue], 2445 expected: RadrootsListingInventoryAccountingIssue, 2446 ) { 2447 let expected_kind = discriminant(&expected); 2448 assert!( 2449 issues 2450 .iter() 2451 .any(|issue| discriminant(issue) == expected_kind), 2452 "missing inventory issue kind {expected:?} in {issues:?}" 2453 ); 2454 } 2455 2456 fn assert_request_issue( 2457 mutate: impl FnOnce(&mut RadrootsOrderRequestRecord), 2458 expected: RadrootsOrderIssue, 2459 ) { 2460 let mut request = request_record(); 2461 mutate(&mut request); 2462 let mut issues = Vec::new(); 2463 assert!(!super::validate_order_request_record( 2464 &order_id("order-1"), 2465 &request, 2466 &mut issues 2467 )); 2468 assert_order_issue_kind(&issues, expected); 2469 } 2470 2471 fn assert_decision_issue( 2472 mutate: impl FnOnce(&mut RadrootsOrderDecisionRecord), 2473 expected: RadrootsOrderIssue, 2474 ) { 2475 let request = request_record(); 2476 let mut decision = accepted_decision(); 2477 mutate(&mut decision); 2478 let mut issues = Vec::new(); 2479 assert!(!super::validate_order_decision_record( 2480 &request, 2481 &decision, 2482 &mut issues 2483 )); 2484 assert_order_issue_kind(&issues, expected); 2485 } 2486 2487 fn assert_revision_proposal_issue( 2488 mutate: impl FnOnce(&mut RadrootsOrderRevisionProposalRecord), 2489 expected: RadrootsOrderIssue, 2490 ) { 2491 let request = request_record(); 2492 let mut proposal = revision_proposal(); 2493 mutate(&mut proposal); 2494 let mut issues = Vec::new(); 2495 assert!(!super::validate_order_revision_proposal_record( 2496 &request, 2497 &proposal, 2498 &mut issues 2499 )); 2500 assert_order_issue_kind(&issues, expected); 2501 } 2502 2503 fn assert_revision_decision_issue( 2504 mutate: impl FnOnce(&mut RadrootsOrderRevisionDecisionRecord), 2505 expected: RadrootsOrderIssue, 2506 ) { 2507 let request = request_record(); 2508 let mut decision = accepted_revision_decision(); 2509 mutate(&mut decision); 2510 let mut issues = Vec::new(); 2511 assert!(!super::validate_order_revision_decision_record( 2512 &request, 2513 &decision, 2514 &mut issues 2515 )); 2516 assert_order_issue_kind(&issues, expected); 2517 } 2518 2519 fn assert_cancellation_issue( 2520 mutate: impl FnOnce(&mut RadrootsOrderCancellationRecord), 2521 expected: RadrootsOrderIssue, 2522 ) { 2523 let request = request_record(); 2524 let mut cancellation = cancellation(event_id(1)); 2525 mutate(&mut cancellation); 2526 let mut issues = Vec::new(); 2527 assert!(!super::validate_order_cancellation_record( 2528 &request, 2529 &cancellation, 2530 &mut issues 2531 )); 2532 assert_order_issue_kind(&issues, expected); 2533 } 2534 2535 fn reduce( 2536 decisions: Vec<RadrootsOrderDecisionRecord>, 2537 revision_proposals: Vec<RadrootsOrderRevisionProposalRecord>, 2538 revision_decisions: Vec<RadrootsOrderRevisionDecisionRecord>, 2539 cancellations: Vec<RadrootsOrderCancellationRecord>, 2540 ) -> super::RadrootsOrderProjection { 2541 reduce_order_events( 2542 &order_id("order-1"), 2543 RadrootsOrderReductionInputs { 2544 requests: vec![request_record()], 2545 decisions, 2546 revision_proposals, 2547 revision_decisions, 2548 cancellations, 2549 }, 2550 ) 2551 } 2552 2553 #[test] 2554 fn order_event_record_accessors_cover_all_variants() { 2555 let records = vec![ 2556 RadrootsOrderEventRecord::Request(request_record()), 2557 RadrootsOrderEventRecord::Decision(accepted_decision()), 2558 RadrootsOrderEventRecord::RevisionProposal(revision_proposal()), 2559 RadrootsOrderEventRecord::RevisionDecision(accepted_revision_decision()), 2560 RadrootsOrderEventRecord::Cancellation(cancellation(event_id(1))), 2561 ]; 2562 2563 let event_ids = records 2564 .iter() 2565 .map(RadrootsOrderEventRecord::event_id) 2566 .cloned() 2567 .collect::<Vec<_>>(); 2568 let order_ids = records 2569 .iter() 2570 .map(RadrootsOrderEventRecord::order_id) 2571 .cloned() 2572 .collect::<Vec<_>>(); 2573 2574 assert_eq!( 2575 event_ids, 2576 vec![ 2577 event_id(1), 2578 event_id(2), 2579 event_id(3), 2580 event_id(4), 2581 event_id(5) 2582 ] 2583 ); 2584 assert_eq!(order_ids, vec![order_id("order-1"); 5]); 2585 } 2586 2587 #[cfg(feature = "serde_json")] 2588 #[test] 2589 fn order_event_records_decode_wire_events_and_decode_errors() { 2590 let request = request_record(); 2591 let request_parts = 2592 order_request_event_build(&listing_event_ptr(), &request.payload).unwrap(); 2593 let request_record = 2594 super::order_event_record_from_event(&event_from_parts(11, BUYER, request_parts)) 2595 .unwrap(); 2596 assert!(matches!( 2597 request_record, 2598 RadrootsOrderEventRecord::Request(record) 2599 if record.event_id == event_id(11) 2600 && record.author_pubkey == public_key(BUYER) 2601 && record.payload.order_id == order_id("order-1") 2602 )); 2603 2604 let decision = accepted_decision(); 2605 let decision_parts = order_decision_event_build( 2606 &decision.root_event_id, 2607 &decision.prev_event_id, 2608 &decision.payload, 2609 ) 2610 .unwrap(); 2611 let decision_record = 2612 super::order_event_record_from_event(&event_from_parts(12, SELLER, decision_parts)) 2613 .unwrap(); 2614 assert!(matches!( 2615 decision_record, 2616 RadrootsOrderEventRecord::Decision(record) 2617 if record.event_id == event_id(12) 2618 && record.counterparty_pubkey == public_key(BUYER) 2619 && record.root_event_id == event_id(1) 2620 && record.prev_event_id == event_id(1) 2621 )); 2622 2623 let proposal = revision_proposal(); 2624 let proposal_parts = order_revision_proposal_event_build( 2625 &proposal.root_event_id, 2626 &proposal.prev_event_id, 2627 &proposal.payload, 2628 ) 2629 .unwrap(); 2630 let proposal_record = 2631 super::order_event_record_from_event(&event_from_parts(13, SELLER, proposal_parts)) 2632 .unwrap(); 2633 assert!(matches!( 2634 proposal_record, 2635 RadrootsOrderEventRecord::RevisionProposal(record) 2636 if record.event_id == event_id(13) 2637 && record.counterparty_pubkey == public_key(BUYER) 2638 && record.payload.revision_id == revision_id("revision-1") 2639 )); 2640 2641 let revision_decision = accepted_revision_decision(); 2642 let revision_decision_parts = order_revision_decision_event_build( 2643 &revision_decision.root_event_id, 2644 &revision_decision.prev_event_id, 2645 &revision_decision.payload, 2646 ) 2647 .unwrap(); 2648 let revision_decision_record = super::order_event_record_from_event(&event_from_parts( 2649 14, 2650 BUYER, 2651 revision_decision_parts, 2652 )) 2653 .unwrap(); 2654 assert!(matches!( 2655 revision_decision_record, 2656 RadrootsOrderEventRecord::RevisionDecision(record) 2657 if record.event_id == event_id(14) 2658 && record.counterparty_pubkey == public_key(SELLER) 2659 && record.payload.revision_id == revision_id("revision-1") 2660 )); 2661 2662 let cancellation = cancellation(event_id(1)); 2663 let cancellation_parts = order_cancellation_event_build( 2664 &cancellation.root_event_id, 2665 &cancellation.prev_event_id, 2666 &cancellation.payload, 2667 ) 2668 .unwrap(); 2669 let cancellation_record = 2670 super::order_event_record_from_event(&event_from_parts(15, BUYER, cancellation_parts)) 2671 .unwrap(); 2672 assert!(matches!( 2673 cancellation_record, 2674 RadrootsOrderEventRecord::Cancellation(record) 2675 if record.event_id == event_id(15) 2676 && record.counterparty_pubkey == public_key(SELLER) 2677 && record.payload.reason == "changed plans" 2678 )); 2679 2680 let unsupported = RadrootsNostrEvent { 2681 id: event_id(16).into_string(), 2682 author: BUYER.into(), 2683 created_at: 1, 2684 kind: 1, 2685 tags: Vec::new(), 2686 content: "{}".into(), 2687 sig: "sig".into(), 2688 }; 2689 assert!(matches!( 2690 super::order_event_record_from_event(&unsupported), 2691 Err(super::RadrootsOrderEventDecodeError::UnsupportedKind { kind: 1 }) 2692 )); 2693 2694 let request_parts = 2695 order_request_event_build(&listing_event_ptr(), &request.payload).unwrap(); 2696 let mut invalid_id_event = event_from_parts(17, BUYER, request_parts); 2697 invalid_id_event.id = "not-an-event-id".into(); 2698 assert!(matches!( 2699 super::order_event_record_from_event(&invalid_id_event), 2700 Err(super::RadrootsOrderEventDecodeError::InvalidEventId(_)) 2701 )); 2702 2703 let request_parts = 2704 order_request_event_build(&listing_event_ptr(), &request.payload).unwrap(); 2705 let mut invalid_author_event = event_from_parts(18, BUYER, request_parts); 2706 invalid_author_event.author = "not-a-pubkey".into(); 2707 assert!(matches!( 2708 super::order_event_record_from_event(&invalid_author_event), 2709 Err(super::RadrootsOrderEventDecodeError::InvalidAuthor(_)) 2710 )); 2711 } 2712 2713 #[cfg(feature = "serde_json")] 2714 #[test] 2715 fn order_event_context_requirements_report_missing_chain_ids() { 2716 let context = radroots_events_codec::order::RadrootsOrderEventContext { 2717 counterparty_pubkey: public_key(BUYER), 2718 listing_event: None, 2719 root_event_id: None, 2720 prev_event_id: None, 2721 }; 2722 2723 assert!(matches!( 2724 super::require_context_root_event_id(&context), 2725 Err(super::RadrootsOrderEventDecodeError::MissingRootEventId) 2726 )); 2727 assert!(matches!( 2728 super::require_context_prev_event_id(&context), 2729 Err(super::RadrootsOrderEventDecodeError::MissingPreviousEventId) 2730 )); 2731 } 2732 2733 #[test] 2734 fn reducer_groups_all_record_variants_and_skips_duplicate_event_ids() { 2735 let mut duplicate_decision = declined_decision(); 2736 duplicate_decision.event_id = event_id(2); 2737 let projection = reduce_order_event_records( 2738 &order_id("order-1"), 2739 vec![ 2740 RadrootsOrderEventRecord::Cancellation(cancellation(event_id(1))), 2741 RadrootsOrderEventRecord::RevisionDecision(accepted_revision_decision()), 2742 RadrootsOrderEventRecord::RevisionProposal(revision_proposal()), 2743 RadrootsOrderEventRecord::Decision(accepted_decision()), 2744 RadrootsOrderEventRecord::Decision(duplicate_decision), 2745 RadrootsOrderEventRecord::Request(request_record()), 2746 ], 2747 ); 2748 2749 assert_eq!(projection.status, RadrootsOrderStatus::Invalid); 2750 assert_order_issue_kind( 2751 &projection.issues, 2752 RadrootsOrderIssue::ForkedLifecycle { 2753 event_ids: Vec::new(), 2754 }, 2755 ); 2756 assert_eq!( 2757 super::projection_issue_event_ids(&projection.issues), 2758 vec![event_id(2), event_id(4), event_id(5)] 2759 ); 2760 2761 let mut duplicate_request = request_record(); 2762 duplicate_request.payload.order_id = order_id("order-duplicate"); 2763 let mut duplicate_request_later = duplicate_request.clone(); 2764 duplicate_request_later.payload.buyer_pubkey = public_key(OTHER); 2765 let deduped = 2766 super::unique_request_records(vec![duplicate_request.clone(), duplicate_request_later]); 2767 assert_eq!(deduped.len(), 1); 2768 assert_eq!( 2769 deduped[0].payload.order_id, 2770 duplicate_request.payload.order_id 2771 ); 2772 assert_eq!( 2773 deduped[0].payload.buyer_pubkey, 2774 duplicate_request.payload.buyer_pubkey 2775 ); 2776 } 2777 2778 #[test] 2779 fn canonicalize_order_request_reports_signer_listing_and_item_errors() { 2780 let canonical = 2781 super::canonicalize_order_request_for_signer(request_record().payload, BUYER).unwrap(); 2782 assert_eq!(canonical.buyer_pubkey, public_key(BUYER)); 2783 assert_eq!(canonical.seller_pubkey, public_key(SELLER)); 2784 2785 let mut unsorted_items = request_record().payload; 2786 unsorted_items.items.push(RadrootsOrderItem { 2787 bin_id: bin_id("bin-0"), 2788 bin_count: 1, 2789 }); 2790 let canonical = 2791 super::canonicalize_order_request_for_signer(unsorted_items, BUYER).unwrap(); 2792 assert_eq!(canonical.items[0].bin_id, bin_id("bin-0")); 2793 assert_eq!(canonical.items[1].bin_id, bin_id("bin-1")); 2794 2795 assert!(matches!( 2796 super::parse_public_listing_addr("not-an-address"), 2797 Err(super::RadrootsOrderCanonicalizationError::InvalidListingAddress(_)) 2798 )); 2799 assert!(matches!( 2800 super::parse_public_listing_addr(format!( 2801 "{KIND_LISTING_DRAFT}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg" 2802 )), 2803 Err(super::RadrootsOrderCanonicalizationError::InvalidListingKind) 2804 )); 2805 assert!(matches!( 2806 super::parse_public_listing_addr(format!("30023:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg")), 2807 Err(super::RadrootsOrderCanonicalizationError::InvalidListingKind) 2808 )); 2809 2810 assert!(matches!( 2811 super::canonicalize_order_request_for_signer(request_record().payload, SELLER), 2812 Err(super::RadrootsOrderCanonicalizationError::InvalidBuyerSigner) 2813 )); 2814 2815 let mut seller_mismatch = request_record().payload; 2816 seller_mismatch.seller_pubkey = public_key(OTHER); 2817 assert!(matches!( 2818 super::canonicalize_order_request_for_signer(seller_mismatch, BUYER), 2819 Err(super::RadrootsOrderCanonicalizationError::InvalidSellerListing) 2820 )); 2821 2822 let mut invalid_kind = request_record().payload; 2823 invalid_kind.listing_addr = draft_listing_addr(); 2824 assert!(matches!( 2825 super::canonicalize_order_request_for_signer(invalid_kind, BUYER), 2826 Err(super::RadrootsOrderCanonicalizationError::InvalidListingKind) 2827 )); 2828 2829 let mut missing_items = request_record().payload; 2830 missing_items.items.clear(); 2831 assert!(matches!( 2832 super::canonicalize_order_request_for_signer(missing_items, BUYER), 2833 Err(super::RadrootsOrderCanonicalizationError::MissingItems) 2834 )); 2835 2836 let mut zero_count = request_record().payload; 2837 zero_count.items[0].bin_count = 0; 2838 assert!(matches!( 2839 super::canonicalize_order_request_for_signer(zero_count, BUYER), 2840 Err(super::RadrootsOrderCanonicalizationError::InvalidBinCount { index: 0 }) 2841 )); 2842 } 2843 2844 #[test] 2845 fn canonicalize_order_decision_reports_signer_and_decision_errors() { 2846 let canonical = 2847 super::canonicalize_order_decision_for_signer(accepted_decision().payload, SELLER) 2848 .unwrap(); 2849 assert_eq!(canonical.seller_pubkey, public_key(SELLER)); 2850 2851 let mut unsorted_commitments = accepted_decision().payload; 2852 if let RadrootsOrderDecisionOutcome::Accepted { 2853 inventory_commitments, 2854 } = &mut unsorted_commitments.decision 2855 { 2856 inventory_commitments.push(RadrootsOrderInventoryCommitment { 2857 bin_id: bin_id("bin-0"), 2858 bin_count: 1, 2859 }); 2860 } 2861 let canonical = 2862 super::canonicalize_order_decision_for_signer(unsorted_commitments, SELLER).unwrap(); 2863 if let RadrootsOrderDecisionOutcome::Accepted { 2864 inventory_commitments, 2865 } = canonical.decision 2866 { 2867 assert_eq!(inventory_commitments[0].bin_id, bin_id("bin-0")); 2868 assert_eq!(inventory_commitments[1].bin_id, bin_id("bin-1")); 2869 } 2870 2871 let mut listing_seller_mismatch = accepted_decision().payload; 2872 listing_seller_mismatch.listing_addr = other_seller_listing_addr(); 2873 assert!(matches!( 2874 super::canonicalize_order_decision_for_signer(listing_seller_mismatch, SELLER), 2875 Err(super::RadrootsOrderCanonicalizationError::InvalidSellerListing) 2876 )); 2877 2878 assert!(matches!( 2879 super::canonicalize_order_decision_for_signer(accepted_decision().payload, BUYER), 2880 Err(super::RadrootsOrderCanonicalizationError::InvalidSellerListing) 2881 )); 2882 2883 let mut missing_commitments = accepted_decision().payload; 2884 missing_commitments.decision = RadrootsOrderDecisionOutcome::Accepted { 2885 inventory_commitments: Vec::new(), 2886 }; 2887 assert!(matches!( 2888 super::canonicalize_order_decision_for_signer(missing_commitments, SELLER), 2889 Err(super::RadrootsOrderCanonicalizationError::MissingInventoryCommitments) 2890 )); 2891 2892 let mut zero_commitment = accepted_decision().payload; 2893 if let RadrootsOrderDecisionOutcome::Accepted { 2894 inventory_commitments, 2895 } = &mut zero_commitment.decision 2896 { 2897 inventory_commitments[0].bin_count = 0; 2898 } 2899 assert!(matches!( 2900 super::canonicalize_order_decision_for_signer(zero_commitment, SELLER), 2901 Err( 2902 super::RadrootsOrderCanonicalizationError::InvalidInventoryCommitmentCount { 2903 index: 0 2904 } 2905 ) 2906 )); 2907 2908 let mut declined = declined_decision().payload; 2909 declined.decision = RadrootsOrderDecisionOutcome::Declined { 2910 reason: " already sold ".into(), 2911 }; 2912 let declined = super::canonicalize_order_decision_for_signer(declined, SELLER).unwrap(); 2913 assert_eq!( 2914 declined.decision, 2915 RadrootsOrderDecisionOutcome::Declined { 2916 reason: "already sold".into() 2917 } 2918 ); 2919 2920 let mut blank_reason = declined_decision().payload; 2921 blank_reason.decision = RadrootsOrderDecisionOutcome::Declined { reason: " ".into() }; 2922 assert!(matches!( 2923 super::canonicalize_order_decision_for_signer(blank_reason, SELLER), 2924 Err(super::RadrootsOrderCanonicalizationError::EmptyField( 2925 "reason" 2926 )) 2927 )); 2928 } 2929 2930 #[cfg(feature = "serde_json")] 2931 #[test] 2932 fn order_economics_digest_is_stable_sha256_hex() { 2933 let digest = super::radroots_order_economics_digest(&economics(2)).unwrap(); 2934 assert_eq!( 2935 digest, 2936 super::radroots_order_economics_digest(&economics(2)).unwrap() 2937 ); 2938 assert!(digest.starts_with("sha256:")); 2939 assert_eq!(digest.len(), "sha256:".len() + 64); 2940 } 2941 2942 #[test] 2943 fn order_helper_sorting_and_matching_paths_are_deterministic() { 2944 let request_items = vec![ 2945 RadrootsOrderItem { 2946 bin_id: bin_id("bin-2"), 2947 bin_count: 1, 2948 }, 2949 RadrootsOrderItem { 2950 bin_id: bin_id("bin-1"), 2951 bin_count: 2, 2952 }, 2953 ]; 2954 let matching_commitments = vec![ 2955 RadrootsOrderInventoryCommitment { 2956 bin_id: bin_id("bin-1"), 2957 bin_count: 2, 2958 }, 2959 RadrootsOrderInventoryCommitment { 2960 bin_id: bin_id("bin-2"), 2961 bin_count: 1, 2962 }, 2963 ]; 2964 assert!(super::inventory_commitments_match_request( 2965 &request_items, 2966 &matching_commitments 2967 )); 2968 assert!(!super::inventory_commitments_match_request( 2969 &request_items, 2970 &matching_commitments[..1] 2971 )); 2972 let mut count_mismatch = matching_commitments.clone(); 2973 count_mismatch[0].bin_count = 1; 2974 assert!(!super::inventory_commitments_match_request( 2975 &request_items, 2976 &count_mismatch 2977 )); 2978 let mut bin_mismatch = matching_commitments.clone(); 2979 bin_mismatch[0].bin_id = bin_id("bin-3"); 2980 assert!(!super::inventory_commitments_match_request( 2981 &request_items, 2982 &bin_mismatch 2983 )); 2984 2985 let mut order_issues = vec![ 2986 RadrootsOrderIssue::ForkedLifecycle { 2987 event_ids: vec![event_id(9), event_id(3)], 2988 }, 2989 RadrootsOrderIssue::CancellationWithoutCancellableOrder { 2990 event_id: event_id(5), 2991 }, 2992 RadrootsOrderIssue::DecisionPayloadInvalid { 2993 event_id: event_id(2), 2994 }, 2995 RadrootsOrderIssue::MissingRequest, 2996 ]; 2997 assert_eq!( 2998 super::projection_issue_event_ids(&order_issues), 2999 vec![event_id(2), event_id(3), event_id(5), event_id(9)] 3000 ); 3001 order_issues.sort_by(super::order_issue_sort_key); 3002 assert!(matches!( 3003 order_issues[0], 3004 RadrootsOrderIssue::MissingRequest 3005 )); 3006 assert!(matches!( 3007 order_issues[1], 3008 RadrootsOrderIssue::DecisionPayloadInvalid { .. } 3009 )); 3010 assert!(matches!( 3011 order_issues[2], 3012 RadrootsOrderIssue::CancellationWithoutCancellableOrder { .. } 3013 )); 3014 assert!(matches!( 3015 order_issues[3], 3016 RadrootsOrderIssue::ForkedLifecycle { .. } 3017 )); 3018 3019 let mut tied_order_issues = vec![ 3020 RadrootsOrderIssue::DecisionPayloadInvalid { 3021 event_id: event_id(8), 3022 }, 3023 RadrootsOrderIssue::DecisionPayloadInvalid { 3024 event_id: event_id(7), 3025 }, 3026 ]; 3027 tied_order_issues.sort_by(super::order_issue_sort_key); 3028 let RadrootsOrderIssue::DecisionPayloadInvalid { 3029 event_id: issue_event_id, 3030 } = &tied_order_issues[0] 3031 else { 3032 panic!("expected decision issue"); 3033 }; 3034 assert_eq!(issue_event_id, &event_id(7)); 3035 3036 let mut inventory_issues = vec![ 3037 RadrootsListingInventoryAccountingIssue::OverReserved { 3038 bin_id: bin_id("bin-2"), 3039 available_count: 1, 3040 reserved_count: 2, 3041 event_ids: vec![event_id(8)], 3042 }, 3043 RadrootsListingInventoryAccountingIssue::UnknownInventoryBin { 3044 bin_id: bin_id("bin-1"), 3045 event_ids: vec![event_id(7)], 3046 }, 3047 RadrootsListingInventoryAccountingIssue::ArithmeticOverflow { 3048 bin_id: bin_id("bin-3"), 3049 event_ids: vec![event_id(6)], 3050 }, 3051 RadrootsListingInventoryAccountingIssue::InvalidOrder { 3052 order_id: order_id("order-1"), 3053 event_ids: vec![event_id(5)], 3054 }, 3055 ]; 3056 inventory_issues.sort_by(super::inventory_issue_sort_key); 3057 assert!(matches!( 3058 inventory_issues[0], 3059 RadrootsListingInventoryAccountingIssue::InvalidOrder { .. } 3060 )); 3061 assert!(matches!( 3062 inventory_issues[1], 3063 RadrootsListingInventoryAccountingIssue::ArithmeticOverflow { .. } 3064 )); 3065 assert!(matches!( 3066 inventory_issues[2], 3067 RadrootsListingInventoryAccountingIssue::UnknownInventoryBin { .. } 3068 )); 3069 assert!(matches!( 3070 inventory_issues[3], 3071 RadrootsListingInventoryAccountingIssue::OverReserved { .. } 3072 )); 3073 3074 let mut tied_inventory_issues = vec![ 3075 RadrootsListingInventoryAccountingIssue::UnknownInventoryBin { 3076 bin_id: bin_id("bin-2"), 3077 event_ids: vec![event_id(9)], 3078 }, 3079 RadrootsListingInventoryAccountingIssue::UnknownInventoryBin { 3080 bin_id: bin_id("bin-1"), 3081 event_ids: vec![event_id(8)], 3082 }, 3083 RadrootsListingInventoryAccountingIssue::UnknownInventoryBin { 3084 bin_id: bin_id("bin-1"), 3085 event_ids: vec![event_id(7)], 3086 }, 3087 ]; 3088 tied_inventory_issues.sort_by(super::inventory_issue_sort_key); 3089 assert_eq!( 3090 super::inventory_issue_id(&tied_inventory_issues[0]), 3091 "bin-1" 3092 ); 3093 assert_eq!( 3094 super::inventory_issue_event_ids(&tied_inventory_issues[0]), 3095 &[event_id(7)] 3096 ); 3097 3098 let invalid = super::invalid_projection( 3099 &order_id("order-1"), 3100 Some(&request_record()), 3101 vec![RadrootsOrderIssue::MissingRequest], 3102 ); 3103 assert_eq!(invalid.last_event_id, Some(event_id(1))); 3104 } 3105 3106 #[test] 3107 fn order_issue_rank_and_event_id_helpers_cover_every_issue_variant() { 3108 let id = event_id(42); 3109 let event_ids = vec![id.clone()]; 3110 let issues = vec![ 3111 RadrootsOrderIssue::MissingRequest, 3112 RadrootsOrderIssue::MultipleRequests { 3113 event_ids: event_ids.clone(), 3114 }, 3115 RadrootsOrderIssue::RequestPayloadInvalid { 3116 event_id: id.clone(), 3117 }, 3118 RadrootsOrderIssue::RequestOrderIdMismatch { 3119 event_id: id.clone(), 3120 }, 3121 RadrootsOrderIssue::RequestAuthorMismatch { 3122 event_id: id.clone(), 3123 }, 3124 RadrootsOrderIssue::RequestListingAddressInvalid { 3125 event_id: id.clone(), 3126 }, 3127 RadrootsOrderIssue::RequestSellerListingMismatch { 3128 event_id: id.clone(), 3129 }, 3130 RadrootsOrderIssue::DecisionPayloadInvalid { 3131 event_id: id.clone(), 3132 }, 3133 RadrootsOrderIssue::DecisionOrderIdMismatch { 3134 event_id: id.clone(), 3135 }, 3136 RadrootsOrderIssue::DecisionAuthorMismatch { 3137 event_id: id.clone(), 3138 }, 3139 RadrootsOrderIssue::DecisionCounterpartyMismatch { 3140 event_id: id.clone(), 3141 }, 3142 RadrootsOrderIssue::DecisionBuyerMismatch { 3143 event_id: id.clone(), 3144 }, 3145 RadrootsOrderIssue::DecisionSellerMismatch { 3146 event_id: id.clone(), 3147 }, 3148 RadrootsOrderIssue::DecisionListingAddressInvalid { 3149 event_id: id.clone(), 3150 }, 3151 RadrootsOrderIssue::DecisionListingMismatch { 3152 event_id: id.clone(), 3153 }, 3154 RadrootsOrderIssue::DecisionRootMismatch { 3155 event_id: id.clone(), 3156 }, 3157 RadrootsOrderIssue::DecisionPreviousMismatch { 3158 event_id: id.clone(), 3159 }, 3160 RadrootsOrderIssue::DecisionMissingInventoryCommitments { 3161 event_id: id.clone(), 3162 }, 3163 RadrootsOrderIssue::DecisionInventoryCommitmentMismatch { 3164 event_id: id.clone(), 3165 }, 3166 RadrootsOrderIssue::DecisionMissingReason { 3167 event_id: id.clone(), 3168 }, 3169 RadrootsOrderIssue::ConflictingDecisions { 3170 event_ids: event_ids.clone(), 3171 }, 3172 RadrootsOrderIssue::RevisionProposalPayloadInvalid { 3173 event_id: id.clone(), 3174 }, 3175 RadrootsOrderIssue::RevisionProposalOrderIdMismatch { 3176 event_id: id.clone(), 3177 }, 3178 RadrootsOrderIssue::RevisionProposalAuthorMismatch { 3179 event_id: id.clone(), 3180 }, 3181 RadrootsOrderIssue::RevisionProposalCounterpartyMismatch { 3182 event_id: id.clone(), 3183 }, 3184 RadrootsOrderIssue::RevisionProposalBuyerMismatch { 3185 event_id: id.clone(), 3186 }, 3187 RadrootsOrderIssue::RevisionProposalSellerMismatch { 3188 event_id: id.clone(), 3189 }, 3190 RadrootsOrderIssue::RevisionProposalListingAddressInvalid { 3191 event_id: id.clone(), 3192 }, 3193 RadrootsOrderIssue::RevisionProposalListingMismatch { 3194 event_id: id.clone(), 3195 }, 3196 RadrootsOrderIssue::RevisionProposalRootMismatch { 3197 event_id: id.clone(), 3198 }, 3199 RadrootsOrderIssue::RevisionProposalPreviousMismatch { 3200 event_id: id.clone(), 3201 }, 3202 RadrootsOrderIssue::RevisionDecisionWithoutProposal { 3203 event_id: id.clone(), 3204 }, 3205 RadrootsOrderIssue::RevisionDecisionPayloadInvalid { 3206 event_id: id.clone(), 3207 }, 3208 RadrootsOrderIssue::RevisionDecisionOrderIdMismatch { 3209 event_id: id.clone(), 3210 }, 3211 RadrootsOrderIssue::RevisionDecisionAuthorMismatch { 3212 event_id: id.clone(), 3213 }, 3214 RadrootsOrderIssue::RevisionDecisionCounterpartyMismatch { 3215 event_id: id.clone(), 3216 }, 3217 RadrootsOrderIssue::RevisionDecisionBuyerMismatch { 3218 event_id: id.clone(), 3219 }, 3220 RadrootsOrderIssue::RevisionDecisionSellerMismatch { 3221 event_id: id.clone(), 3222 }, 3223 RadrootsOrderIssue::RevisionDecisionListingAddressInvalid { 3224 event_id: id.clone(), 3225 }, 3226 RadrootsOrderIssue::RevisionDecisionListingMismatch { 3227 event_id: id.clone(), 3228 }, 3229 RadrootsOrderIssue::RevisionDecisionRootMismatch { 3230 event_id: id.clone(), 3231 }, 3232 RadrootsOrderIssue::RevisionDecisionPreviousMismatch { 3233 event_id: id.clone(), 3234 }, 3235 RadrootsOrderIssue::RevisionDecisionRevisionIdMismatch { 3236 event_id: id.clone(), 3237 }, 3238 RadrootsOrderIssue::CancellationWithoutCancellableOrder { 3239 event_id: id.clone(), 3240 }, 3241 RadrootsOrderIssue::CancellationPayloadInvalid { 3242 event_id: id.clone(), 3243 }, 3244 RadrootsOrderIssue::CancellationOrderIdMismatch { 3245 event_id: id.clone(), 3246 }, 3247 RadrootsOrderIssue::CancellationAuthorMismatch { 3248 event_id: id.clone(), 3249 }, 3250 RadrootsOrderIssue::CancellationCounterpartyMismatch { 3251 event_id: id.clone(), 3252 }, 3253 RadrootsOrderIssue::CancellationBuyerMismatch { 3254 event_id: id.clone(), 3255 }, 3256 RadrootsOrderIssue::CancellationSellerMismatch { 3257 event_id: id.clone(), 3258 }, 3259 RadrootsOrderIssue::CancellationListingAddressInvalid { 3260 event_id: id.clone(), 3261 }, 3262 RadrootsOrderIssue::CancellationListingMismatch { 3263 event_id: id.clone(), 3264 }, 3265 RadrootsOrderIssue::CancellationRootMismatch { 3266 event_id: id.clone(), 3267 }, 3268 RadrootsOrderIssue::CancellationPreviousMismatch { 3269 event_id: id.clone(), 3270 }, 3271 RadrootsOrderIssue::ForkedLifecycle { event_ids }, 3272 ]; 3273 3274 for (rank, issue) in issues.iter().enumerate() { 3275 assert_eq!(super::order_issue_rank(issue), rank as u8); 3276 } 3277 assert_eq!(super::projection_issue_event_ids(&issues), vec![id]); 3278 } 3279 3280 #[test] 3281 fn inventory_issue_helpers_cover_all_issue_variants() { 3282 let id = event_id(9); 3283 let issues = vec![ 3284 RadrootsListingInventoryAccountingIssue::InvalidOrder { 3285 order_id: order_id("order-1"), 3286 event_ids: vec![id.clone()], 3287 }, 3288 RadrootsListingInventoryAccountingIssue::ArithmeticOverflow { 3289 bin_id: bin_id("bin-1"), 3290 event_ids: vec![id.clone()], 3291 }, 3292 RadrootsListingInventoryAccountingIssue::UnknownInventoryBin { 3293 bin_id: bin_id("bin-2"), 3294 event_ids: vec![id.clone()], 3295 }, 3296 RadrootsListingInventoryAccountingIssue::OverReserved { 3297 bin_id: bin_id("bin-3"), 3298 available_count: 1, 3299 reserved_count: 2, 3300 event_ids: vec![id.clone()], 3301 }, 3302 ]; 3303 3304 assert_eq!(super::inventory_issue_rank(&issues[0]), 0); 3305 assert_eq!(super::inventory_issue_rank(&issues[1]), 1); 3306 assert_eq!(super::inventory_issue_rank(&issues[2]), 2); 3307 assert_eq!(super::inventory_issue_rank(&issues[3]), 3); 3308 assert_eq!(super::inventory_issue_id(&issues[0]), "order-1"); 3309 assert_eq!(super::inventory_issue_id(&issues[1]), "bin-1"); 3310 assert_eq!(super::inventory_issue_id(&issues[2]), "bin-2"); 3311 assert_eq!(super::inventory_issue_id(&issues[3]), "bin-3"); 3312 for issue in &issues { 3313 assert_eq!(super::inventory_issue_event_ids(issue), &[id.clone()]); 3314 } 3315 } 3316 3317 #[test] 3318 fn reducer_reports_missing_request_for_each_non_request_input_family() { 3319 let decision_only = reduce_order_events( 3320 &order_id("order-1"), 3321 RadrootsOrderReductionInputs { 3322 requests: Vec::<RadrootsOrderRequestRecord>::new(), 3323 decisions: vec![accepted_decision()], 3324 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(), 3325 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(), 3326 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(), 3327 }, 3328 ); 3329 assert_order_issue_kind(&decision_only.issues, RadrootsOrderIssue::MissingRequest); 3330 3331 let proposal_only = reduce_order_events( 3332 &order_id("order-1"), 3333 RadrootsOrderReductionInputs { 3334 requests: Vec::<RadrootsOrderRequestRecord>::new(), 3335 decisions: Vec::<RadrootsOrderDecisionRecord>::new(), 3336 revision_proposals: vec![revision_proposal()], 3337 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(), 3338 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(), 3339 }, 3340 ); 3341 assert_order_issue_kind(&proposal_only.issues, RadrootsOrderIssue::MissingRequest); 3342 3343 let revision_decision_only = reduce_order_events( 3344 &order_id("order-1"), 3345 RadrootsOrderReductionInputs { 3346 requests: Vec::<RadrootsOrderRequestRecord>::new(), 3347 decisions: Vec::<RadrootsOrderDecisionRecord>::new(), 3348 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(), 3349 revision_decisions: vec![accepted_revision_decision()], 3350 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(), 3351 }, 3352 ); 3353 assert_order_issue_kind( 3354 &revision_decision_only.issues, 3355 RadrootsOrderIssue::MissingRequest, 3356 ); 3357 3358 let cancellation_only = reduce_order_events( 3359 &order_id("order-1"), 3360 RadrootsOrderReductionInputs { 3361 requests: Vec::<RadrootsOrderRequestRecord>::new(), 3362 decisions: Vec::<RadrootsOrderDecisionRecord>::new(), 3363 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(), 3364 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(), 3365 cancellations: vec![cancellation(event_id(1))], 3366 }, 3367 ); 3368 assert_order_issue_kind( 3369 &cancellation_only.issues, 3370 RadrootsOrderIssue::MissingRequest, 3371 ); 3372 } 3373 3374 #[test] 3375 fn reducer_reports_multiple_valid_cancellations_as_forked_lifecycle() { 3376 let mut second_cancellation = cancellation(event_id(1)); 3377 second_cancellation.event_id = event_id(6); 3378 let projection = reduce( 3379 Vec::new(), 3380 Vec::new(), 3381 Vec::new(), 3382 vec![cancellation(event_id(1)), second_cancellation], 3383 ); 3384 3385 assert_order_issue_kind( 3386 &projection.issues, 3387 RadrootsOrderIssue::ForkedLifecycle { 3388 event_ids: Vec::new(), 3389 }, 3390 ); 3391 assert_eq!(projection.last_event_id, Some(event_id(6))); 3392 } 3393 3394 #[test] 3395 fn inventory_accounting_private_helpers_cover_merge_sort_and_overflow_paths() { 3396 let (bins, issues) = super::normalized_listing_inventory_bins(vec![ 3397 RadrootsListingInventoryBinAvailability { 3398 bin_id: bin_id("bin-1"), 3399 available_count: 1, 3400 }, 3401 RadrootsListingInventoryBinAvailability { 3402 bin_id: bin_id("bin-1"), 3403 available_count: 2, 3404 }, 3405 ]); 3406 assert_eq!(issues, Vec::new()); 3407 assert_eq!(bins[0].available_count, 3); 3408 assert_eq!(bins[0].remaining_count, 3); 3409 3410 let mut overflow_bin = super::RadrootsListingInventoryBinAccounting { 3411 bin_id: bin_id("bin-overflow"), 3412 available_count: u64::MAX, 3413 accepted_reserved_count: u64::MAX, 3414 remaining_count: u64::MAX, 3415 over_reserved: false, 3416 accepted_orders: Vec::new(), 3417 }; 3418 let mut overflow_issues = Vec::new(); 3419 super::add_inventory_reservation_event( 3420 &mut overflow_bin, 3421 &order_id("order-overflow"), 3422 &event_id(90), 3423 1, 3424 &mut overflow_issues, 3425 ); 3426 assert_inventory_issue_kind( 3427 &overflow_issues, 3428 RadrootsListingInventoryAccountingIssue::ArithmeticOverflow { 3429 bin_id: bin_id("bin-overflow"), 3430 event_ids: Vec::new(), 3431 }, 3432 ); 3433 3434 let mut sorting_bin = super::RadrootsListingInventoryBinAccounting { 3435 bin_id: bin_id("bin-sort"), 3436 available_count: 1, 3437 accepted_reserved_count: 2, 3438 remaining_count: 1, 3439 over_reserved: false, 3440 accepted_orders: vec![ 3441 super::RadrootsListingInventoryOrderReservation { 3442 order_id: order_id("order-2"), 3443 agreement_event_id: event_id(92), 3444 bin_count: 1, 3445 }, 3446 super::RadrootsListingInventoryOrderReservation { 3447 order_id: order_id("order-1"), 3448 agreement_event_id: event_id(91), 3449 bin_count: 1, 3450 }, 3451 super::RadrootsListingInventoryOrderReservation { 3452 order_id: order_id("order-1"), 3453 agreement_event_id: event_id(90), 3454 bin_count: 1, 3455 }, 3456 ], 3457 }; 3458 let mut finish_issues = Vec::new(); 3459 super::finish_inventory_accounting_bins( 3460 core::slice::from_mut(&mut sorting_bin), 3461 &mut finish_issues, 3462 ); 3463 assert_eq!(sorting_bin.remaining_count, 0); 3464 assert!(sorting_bin.over_reserved); 3465 assert_eq!(sorting_bin.accepted_orders[0].order_id, order_id("order-1")); 3466 assert_eq!( 3467 sorting_bin.accepted_orders[0].agreement_event_id, 3468 event_id(90) 3469 ); 3470 assert_inventory_issue_kind( 3471 &finish_issues, 3472 RadrootsListingInventoryAccountingIssue::OverReserved { 3473 bin_id: bin_id("bin-sort"), 3474 available_count: 1, 3475 reserved_count: 2, 3476 event_ids: Vec::new(), 3477 }, 3478 ); 3479 3480 let mut fallback_request = request_record(); 3481 fallback_request.event_id = event_id(95); 3482 let mut fallback_decision = accepted_decision(); 3483 fallback_decision.event_id = event_id(93); 3484 let mut fallback_proposal = revision_proposal(); 3485 fallback_proposal.event_id = event_id(94); 3486 let mut fallback_revision_decision = accepted_revision_decision(); 3487 fallback_revision_decision.event_id = event_id(92); 3488 let mut fallback_cancellation = cancellation(event_id(3)); 3489 fallback_cancellation.event_id = event_id(91); 3490 let fallback_ids = super::fallback_order_event_ids( 3491 &[fallback_request], 3492 &[fallback_decision], 3493 &[fallback_proposal], 3494 &[fallback_revision_decision], 3495 &[fallback_cancellation], 3496 ); 3497 assert_eq!( 3498 fallback_ids, 3499 vec![ 3500 event_id(91), 3501 event_id(92), 3502 event_id(93), 3503 event_id(94), 3504 event_id(95) 3505 ] 3506 ); 3507 } 3508 3509 #[test] 3510 fn reducer_reports_missing_duplicate_and_forked_lifecycles() { 3511 let missing = reduce_order_events( 3512 &order_id("order-1"), 3513 RadrootsOrderReductionInputs { 3514 requests: Vec::<RadrootsOrderRequestRecord>::new(), 3515 decisions: Vec::<RadrootsOrderDecisionRecord>::new(), 3516 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(), 3517 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(), 3518 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(), 3519 }, 3520 ); 3521 assert_eq!(missing.status, RadrootsOrderStatus::Missing); 3522 3523 let missing_request = reduce_order_events( 3524 &order_id("order-1"), 3525 RadrootsOrderReductionInputs { 3526 requests: Vec::<RadrootsOrderRequestRecord>::new(), 3527 decisions: vec![accepted_decision()], 3528 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(), 3529 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(), 3530 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(), 3531 }, 3532 ); 3533 assert_order_issue_kind(&missing_request.issues, RadrootsOrderIssue::MissingRequest); 3534 3535 let mut duplicate_request = request_record(); 3536 duplicate_request.event_id = event_id(6); 3537 let duplicate = reduce_order_events( 3538 &order_id("order-1"), 3539 RadrootsOrderReductionInputs { 3540 requests: vec![request_record(), duplicate_request], 3541 decisions: Vec::<RadrootsOrderDecisionRecord>::new(), 3542 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(), 3543 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(), 3544 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(), 3545 }, 3546 ); 3547 assert_order_issue_kind( 3548 &duplicate.issues, 3549 RadrootsOrderIssue::MultipleRequests { 3550 event_ids: Vec::new(), 3551 }, 3552 ); 3553 3554 let mut second_decision = declined_decision(); 3555 second_decision.event_id = event_id(6); 3556 let conflicting = reduce( 3557 vec![accepted_decision(), second_decision], 3558 vec![], 3559 vec![], 3560 vec![], 3561 ); 3562 assert_order_issue_kind( 3563 &conflicting.issues, 3564 RadrootsOrderIssue::ConflictingDecisions { 3565 event_ids: Vec::new(), 3566 }, 3567 ); 3568 3569 let without_proposal = reduce( 3570 Vec::new(), 3571 Vec::new(), 3572 vec![accepted_revision_decision()], 3573 Vec::new(), 3574 ); 3575 assert_order_issue_kind( 3576 &without_proposal.issues, 3577 RadrootsOrderIssue::RevisionDecisionWithoutProposal { 3578 event_id: event_id(4), 3579 }, 3580 ); 3581 3582 let mut second_proposal = revision_proposal(); 3583 second_proposal.event_id = event_id(6); 3584 let multiple_proposals = reduce( 3585 Vec::new(), 3586 vec![revision_proposal(), second_proposal], 3587 vec![], 3588 vec![], 3589 ); 3590 assert_order_issue_kind( 3591 &multiple_proposals.issues, 3592 RadrootsOrderIssue::ForkedLifecycle { 3593 event_ids: Vec::new(), 3594 }, 3595 ); 3596 3597 let mut second_revision_decision = accepted_revision_decision(); 3598 second_revision_decision.event_id = event_id(7); 3599 let multiple_revision_decisions = reduce( 3600 Vec::new(), 3601 vec![revision_proposal()], 3602 vec![accepted_revision_decision(), second_revision_decision], 3603 Vec::new(), 3604 ); 3605 assert_order_issue_kind( 3606 &multiple_revision_decisions.issues, 3607 RadrootsOrderIssue::ForkedLifecycle { 3608 event_ids: Vec::new(), 3609 }, 3610 ); 3611 3612 let decided_then_revised = reduce( 3613 vec![accepted_decision()], 3614 vec![revision_proposal()], 3615 Vec::new(), 3616 Vec::new(), 3617 ); 3618 assert_order_issue_kind( 3619 &decided_then_revised.issues, 3620 RadrootsOrderIssue::ForkedLifecycle { 3621 event_ids: Vec::new(), 3622 }, 3623 ); 3624 3625 let decided_then_revision_decision = reduce( 3626 vec![accepted_decision()], 3627 Vec::new(), 3628 vec![accepted_revision_decision()], 3629 Vec::new(), 3630 ); 3631 assert_order_issue_kind( 3632 &decided_then_revision_decision.issues, 3633 RadrootsOrderIssue::ForkedLifecycle { 3634 event_ids: Vec::new(), 3635 }, 3636 ); 3637 } 3638 3639 #[test] 3640 fn reducer_covers_revision_and_cancellation_edge_paths() { 3641 let pending_revision = reduce( 3642 Vec::new(), 3643 vec![revision_proposal()], 3644 Vec::new(), 3645 Vec::new(), 3646 ); 3647 assert_eq!(pending_revision.status, RadrootsOrderStatus::Requested); 3648 assert_eq!( 3649 pending_revision.pending_revision_event_id, 3650 Some(event_id(3)) 3651 ); 3652 assert_eq!( 3653 pending_revision.economics.expect("pending economics").items[0].bin_count, 3654 1 3655 ); 3656 3657 let mut bad_proposal_previous = revision_proposal(); 3658 bad_proposal_previous.prev_event_id = event_id(8); 3659 bad_proposal_previous.payload.prev_event_id = event_id(8); 3660 let bad_proposal = reduce( 3661 Vec::new(), 3662 vec![bad_proposal_previous], 3663 Vec::new(), 3664 Vec::new(), 3665 ); 3666 assert_order_issue_kind( 3667 &bad_proposal.issues, 3668 RadrootsOrderIssue::RevisionProposalPreviousMismatch { 3669 event_id: event_id(3), 3670 }, 3671 ); 3672 3673 let mut bad_decision_previous = accepted_revision_decision(); 3674 bad_decision_previous.prev_event_id = event_id(8); 3675 bad_decision_previous.payload.prev_event_id = event_id(8); 3676 let bad_decision = reduce( 3677 Vec::new(), 3678 vec![revision_proposal()], 3679 vec![bad_decision_previous], 3680 Vec::new(), 3681 ); 3682 assert_order_issue_kind( 3683 &bad_decision.issues, 3684 RadrootsOrderIssue::RevisionDecisionPreviousMismatch { 3685 event_id: event_id(4), 3686 }, 3687 ); 3688 3689 let mut bad_revision_id = accepted_revision_decision(); 3690 bad_revision_id.payload.revision_id = revision_id("revision-2"); 3691 let bad_revision = reduce( 3692 Vec::new(), 3693 vec![revision_proposal()], 3694 vec![bad_revision_id], 3695 Vec::new(), 3696 ); 3697 assert_order_issue_kind( 3698 &bad_revision.issues, 3699 RadrootsOrderIssue::RevisionDecisionRevisionIdMismatch { 3700 event_id: event_id(4), 3701 }, 3702 ); 3703 3704 let mut declined_revision = accepted_revision_decision(); 3705 declined_revision.payload.decision = RadrootsOrderRevisionOutcome::Declined { 3706 reason: "too late".into(), 3707 }; 3708 let declined = reduce( 3709 Vec::new(), 3710 vec![revision_proposal()], 3711 vec![declined_revision], 3712 Vec::new(), 3713 ); 3714 assert_eq!(declined.status, RadrootsOrderStatus::Declined); 3715 assert_eq!(declined.pending_revision_event_id, Some(event_id(3))); 3716 3717 let cancellation_after_decision = reduce( 3718 vec![declined_decision()], 3719 Vec::new(), 3720 Vec::new(), 3721 vec![cancellation(event_id(2))], 3722 ); 3723 assert_order_issue_kind( 3724 &cancellation_after_decision.issues, 3725 RadrootsOrderIssue::ForkedLifecycle { 3726 event_ids: Vec::new(), 3727 }, 3728 ); 3729 3730 let cancellation_after_revision_decision = reduce( 3731 Vec::new(), 3732 vec![revision_proposal()], 3733 vec![accepted_revision_decision()], 3734 vec![cancellation(event_id(4))], 3735 ); 3736 assert_order_issue_kind( 3737 &cancellation_after_revision_decision.issues, 3738 RadrootsOrderIssue::ForkedLifecycle { 3739 event_ids: Vec::new(), 3740 }, 3741 ); 3742 3743 let mut second_proposal = revision_proposal(); 3744 second_proposal.event_id = event_id(6); 3745 let cancellation_after_multiple_proposals = reduce( 3746 Vec::new(), 3747 vec![revision_proposal(), second_proposal], 3748 Vec::new(), 3749 vec![cancellation(event_id(3))], 3750 ); 3751 assert_order_issue_kind( 3752 &cancellation_after_multiple_proposals.issues, 3753 RadrootsOrderIssue::ForkedLifecycle { 3754 event_ids: Vec::new(), 3755 }, 3756 ); 3757 3758 let cancellation_previous_mismatch = reduce( 3759 Vec::new(), 3760 vec![revision_proposal()], 3761 Vec::new(), 3762 vec![cancellation(event_id(1))], 3763 ); 3764 assert_order_issue_kind( 3765 &cancellation_previous_mismatch.issues, 3766 RadrootsOrderIssue::CancellationPreviousMismatch { 3767 event_id: event_id(5), 3768 }, 3769 ); 3770 } 3771 3772 #[test] 3773 fn reducer_validators_report_request_and_decision_issue_kinds() { 3774 assert_request_issue( 3775 |request| request.payload.items.clear(), 3776 RadrootsOrderIssue::RequestPayloadInvalid { 3777 event_id: event_id(1), 3778 }, 3779 ); 3780 assert_request_issue( 3781 |request| request.payload.order_id = order_id("order-2"), 3782 RadrootsOrderIssue::RequestOrderIdMismatch { 3783 event_id: event_id(1), 3784 }, 3785 ); 3786 assert_request_issue( 3787 |request| request.author_pubkey = public_key(SELLER), 3788 RadrootsOrderIssue::RequestAuthorMismatch { 3789 event_id: event_id(1), 3790 }, 3791 ); 3792 assert_request_issue( 3793 |request| request.payload.listing_addr = draft_listing_addr(), 3794 RadrootsOrderIssue::RequestListingAddressInvalid { 3795 event_id: event_id(1), 3796 }, 3797 ); 3798 assert_request_issue( 3799 |request| request.payload.seller_pubkey = public_key(OTHER), 3800 RadrootsOrderIssue::RequestSellerListingMismatch { 3801 event_id: event_id(1), 3802 }, 3803 ); 3804 3805 assert_decision_issue( 3806 |decision| { 3807 decision.payload.decision = RadrootsOrderDecisionOutcome::Accepted { 3808 inventory_commitments: Vec::new(), 3809 }; 3810 }, 3811 RadrootsOrderIssue::DecisionMissingInventoryCommitments { 3812 event_id: event_id(2), 3813 }, 3814 ); 3815 assert_decision_issue( 3816 |decision| { 3817 decision.payload.decision = 3818 RadrootsOrderDecisionOutcome::Declined { reason: " ".into() }; 3819 }, 3820 RadrootsOrderIssue::DecisionMissingReason { 3821 event_id: event_id(2), 3822 }, 3823 ); 3824 assert_decision_issue( 3825 |decision| decision.payload.order_id = order_id("order-2"), 3826 RadrootsOrderIssue::DecisionOrderIdMismatch { 3827 event_id: event_id(2), 3828 }, 3829 ); 3830 assert_decision_issue( 3831 |decision| decision.author_pubkey = public_key(BUYER), 3832 RadrootsOrderIssue::DecisionAuthorMismatch { 3833 event_id: event_id(2), 3834 }, 3835 ); 3836 assert_decision_issue( 3837 |decision| decision.counterparty_pubkey = public_key(SELLER), 3838 RadrootsOrderIssue::DecisionCounterpartyMismatch { 3839 event_id: event_id(2), 3840 }, 3841 ); 3842 assert_decision_issue( 3843 |decision| decision.payload.buyer_pubkey = public_key(SELLER), 3844 RadrootsOrderIssue::DecisionBuyerMismatch { 3845 event_id: event_id(2), 3846 }, 3847 ); 3848 assert_decision_issue( 3849 |decision| decision.payload.seller_pubkey = public_key(BUYER), 3850 RadrootsOrderIssue::DecisionSellerMismatch { 3851 event_id: event_id(2), 3852 }, 3853 ); 3854 assert_decision_issue( 3855 |decision| decision.payload.listing_addr = draft_listing_addr(), 3856 RadrootsOrderIssue::DecisionListingAddressInvalid { 3857 event_id: event_id(2), 3858 }, 3859 ); 3860 assert_decision_issue( 3861 |decision| decision.payload.listing_addr = other_seller_listing_addr(), 3862 RadrootsOrderIssue::DecisionListingMismatch { 3863 event_id: event_id(2), 3864 }, 3865 ); 3866 assert_decision_issue( 3867 |decision| decision.root_event_id = event_id(8), 3868 RadrootsOrderIssue::DecisionRootMismatch { 3869 event_id: event_id(2), 3870 }, 3871 ); 3872 assert_decision_issue( 3873 |decision| decision.prev_event_id = event_id(8), 3874 RadrootsOrderIssue::DecisionPreviousMismatch { 3875 event_id: event_id(2), 3876 }, 3877 ); 3878 assert_decision_issue( 3879 |decision| { 3880 if let RadrootsOrderDecisionOutcome::Accepted { 3881 inventory_commitments, 3882 } = &mut decision.payload.decision 3883 { 3884 inventory_commitments[0].bin_count = 1; 3885 } 3886 }, 3887 RadrootsOrderIssue::DecisionInventoryCommitmentMismatch { 3888 event_id: event_id(2), 3889 }, 3890 ); 3891 } 3892 3893 #[test] 3894 fn reducer_validators_report_revision_and_cancellation_issue_kinds() { 3895 assert_revision_proposal_issue( 3896 |proposal| proposal.payload.items.clear(), 3897 RadrootsOrderIssue::RevisionProposalPayloadInvalid { 3898 event_id: event_id(3), 3899 }, 3900 ); 3901 assert_revision_proposal_issue( 3902 |proposal| proposal.payload.order_id = order_id("order-2"), 3903 RadrootsOrderIssue::RevisionProposalOrderIdMismatch { 3904 event_id: event_id(3), 3905 }, 3906 ); 3907 assert_revision_proposal_issue( 3908 |proposal| proposal.author_pubkey = public_key(BUYER), 3909 RadrootsOrderIssue::RevisionProposalAuthorMismatch { 3910 event_id: event_id(3), 3911 }, 3912 ); 3913 assert_revision_proposal_issue( 3914 |proposal| proposal.counterparty_pubkey = public_key(SELLER), 3915 RadrootsOrderIssue::RevisionProposalCounterpartyMismatch { 3916 event_id: event_id(3), 3917 }, 3918 ); 3919 assert_revision_proposal_issue( 3920 |proposal| proposal.payload.buyer_pubkey = public_key(SELLER), 3921 RadrootsOrderIssue::RevisionProposalBuyerMismatch { 3922 event_id: event_id(3), 3923 }, 3924 ); 3925 assert_revision_proposal_issue( 3926 |proposal| proposal.payload.seller_pubkey = public_key(BUYER), 3927 RadrootsOrderIssue::RevisionProposalSellerMismatch { 3928 event_id: event_id(3), 3929 }, 3930 ); 3931 assert_revision_proposal_issue( 3932 |proposal| proposal.payload.listing_addr = draft_listing_addr(), 3933 RadrootsOrderIssue::RevisionProposalListingAddressInvalid { 3934 event_id: event_id(3), 3935 }, 3936 ); 3937 assert_revision_proposal_issue( 3938 |proposal| proposal.payload.listing_addr = other_seller_listing_addr(), 3939 RadrootsOrderIssue::RevisionProposalListingMismatch { 3940 event_id: event_id(3), 3941 }, 3942 ); 3943 assert_revision_proposal_issue( 3944 |proposal| proposal.root_event_id = event_id(8), 3945 RadrootsOrderIssue::RevisionProposalRootMismatch { 3946 event_id: event_id(3), 3947 }, 3948 ); 3949 assert_revision_proposal_issue( 3950 |proposal| proposal.payload.root_event_id = event_id(8), 3951 RadrootsOrderIssue::RevisionProposalRootMismatch { 3952 event_id: event_id(3), 3953 }, 3954 ); 3955 assert_revision_proposal_issue( 3956 |proposal| proposal.prev_event_id = event_id(8), 3957 RadrootsOrderIssue::RevisionProposalPreviousMismatch { 3958 event_id: event_id(3), 3959 }, 3960 ); 3961 assert_revision_proposal_issue( 3962 |proposal| proposal.prev_event_id = event_id(3), 3963 RadrootsOrderIssue::RevisionProposalPreviousMismatch { 3964 event_id: event_id(3), 3965 }, 3966 ); 3967 assert_revision_proposal_issue( 3968 |proposal| proposal.payload.prev_event_id = event_id(8), 3969 RadrootsOrderIssue::RevisionProposalPreviousMismatch { 3970 event_id: event_id(3), 3971 }, 3972 ); 3973 3974 assert_revision_decision_issue( 3975 |decision| { 3976 decision.payload.decision = 3977 RadrootsOrderRevisionOutcome::Declined { reason: " ".into() }; 3978 }, 3979 RadrootsOrderIssue::RevisionDecisionPayloadInvalid { 3980 event_id: event_id(4), 3981 }, 3982 ); 3983 assert_revision_decision_issue( 3984 |decision| decision.payload.order_id = order_id("order-2"), 3985 RadrootsOrderIssue::RevisionDecisionOrderIdMismatch { 3986 event_id: event_id(4), 3987 }, 3988 ); 3989 assert_revision_decision_issue( 3990 |decision| decision.author_pubkey = public_key(SELLER), 3991 RadrootsOrderIssue::RevisionDecisionAuthorMismatch { 3992 event_id: event_id(4), 3993 }, 3994 ); 3995 assert_revision_decision_issue( 3996 |decision| decision.counterparty_pubkey = public_key(BUYER), 3997 RadrootsOrderIssue::RevisionDecisionCounterpartyMismatch { 3998 event_id: event_id(4), 3999 }, 4000 ); 4001 assert_revision_decision_issue( 4002 |decision| decision.payload.buyer_pubkey = public_key(SELLER), 4003 RadrootsOrderIssue::RevisionDecisionBuyerMismatch { 4004 event_id: event_id(4), 4005 }, 4006 ); 4007 assert_revision_decision_issue( 4008 |decision| decision.payload.seller_pubkey = public_key(BUYER), 4009 RadrootsOrderIssue::RevisionDecisionSellerMismatch { 4010 event_id: event_id(4), 4011 }, 4012 ); 4013 assert_revision_decision_issue( 4014 |decision| decision.payload.listing_addr = draft_listing_addr(), 4015 RadrootsOrderIssue::RevisionDecisionListingAddressInvalid { 4016 event_id: event_id(4), 4017 }, 4018 ); 4019 assert_revision_decision_issue( 4020 |decision| decision.payload.listing_addr = other_seller_listing_addr(), 4021 RadrootsOrderIssue::RevisionDecisionListingMismatch { 4022 event_id: event_id(4), 4023 }, 4024 ); 4025 assert_revision_decision_issue( 4026 |decision| decision.root_event_id = event_id(8), 4027 RadrootsOrderIssue::RevisionDecisionRootMismatch { 4028 event_id: event_id(4), 4029 }, 4030 ); 4031 assert_revision_decision_issue( 4032 |decision| decision.payload.root_event_id = event_id(8), 4033 RadrootsOrderIssue::RevisionDecisionRootMismatch { 4034 event_id: event_id(4), 4035 }, 4036 ); 4037 assert_revision_decision_issue( 4038 |decision| decision.prev_event_id = event_id(8), 4039 RadrootsOrderIssue::RevisionDecisionPreviousMismatch { 4040 event_id: event_id(4), 4041 }, 4042 ); 4043 assert_revision_decision_issue( 4044 |decision| decision.prev_event_id = event_id(4), 4045 RadrootsOrderIssue::RevisionDecisionPreviousMismatch { 4046 event_id: event_id(4), 4047 }, 4048 ); 4049 assert_revision_decision_issue( 4050 |decision| decision.payload.prev_event_id = event_id(8), 4051 RadrootsOrderIssue::RevisionDecisionPreviousMismatch { 4052 event_id: event_id(4), 4053 }, 4054 ); 4055 4056 assert_cancellation_issue( 4057 |cancellation| cancellation.payload.reason = " ".into(), 4058 RadrootsOrderIssue::CancellationPayloadInvalid { 4059 event_id: event_id(5), 4060 }, 4061 ); 4062 assert_cancellation_issue( 4063 |cancellation| cancellation.payload.order_id = order_id("order-2"), 4064 RadrootsOrderIssue::CancellationOrderIdMismatch { 4065 event_id: event_id(5), 4066 }, 4067 ); 4068 assert_cancellation_issue( 4069 |cancellation| cancellation.author_pubkey = public_key(SELLER), 4070 RadrootsOrderIssue::CancellationAuthorMismatch { 4071 event_id: event_id(5), 4072 }, 4073 ); 4074 assert_cancellation_issue( 4075 |cancellation| cancellation.counterparty_pubkey = public_key(BUYER), 4076 RadrootsOrderIssue::CancellationCounterpartyMismatch { 4077 event_id: event_id(5), 4078 }, 4079 ); 4080 assert_cancellation_issue( 4081 |cancellation| cancellation.payload.buyer_pubkey = public_key(SELLER), 4082 RadrootsOrderIssue::CancellationBuyerMismatch { 4083 event_id: event_id(5), 4084 }, 4085 ); 4086 assert_cancellation_issue( 4087 |cancellation| cancellation.payload.seller_pubkey = public_key(BUYER), 4088 RadrootsOrderIssue::CancellationSellerMismatch { 4089 event_id: event_id(5), 4090 }, 4091 ); 4092 assert_cancellation_issue( 4093 |cancellation| cancellation.payload.listing_addr = draft_listing_addr(), 4094 RadrootsOrderIssue::CancellationListingAddressInvalid { 4095 event_id: event_id(5), 4096 }, 4097 ); 4098 assert_cancellation_issue( 4099 |cancellation| cancellation.payload.listing_addr = other_seller_listing_addr(), 4100 RadrootsOrderIssue::CancellationListingMismatch { 4101 event_id: event_id(5), 4102 }, 4103 ); 4104 assert_cancellation_issue( 4105 |cancellation| cancellation.root_event_id = event_id(8), 4106 RadrootsOrderIssue::CancellationRootMismatch { 4107 event_id: event_id(5), 4108 }, 4109 ); 4110 assert_cancellation_issue( 4111 |cancellation| cancellation.prev_event_id = event_id(5), 4112 RadrootsOrderIssue::CancellationPreviousMismatch { 4113 event_id: event_id(5), 4114 }, 4115 ); 4116 } 4117 4118 #[test] 4119 fn reducer_reports_invalid_records_from_all_non_request_families() { 4120 let mut bad_request = request_record(); 4121 bad_request.payload.order_id = order_id("order-2"); 4122 let invalid_request = reduce_order_events( 4123 &order_id("order-1"), 4124 RadrootsOrderReductionInputs { 4125 requests: vec![bad_request], 4126 decisions: Vec::<RadrootsOrderDecisionRecord>::new(), 4127 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(), 4128 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(), 4129 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(), 4130 }, 4131 ); 4132 assert_order_issue_kind( 4133 &invalid_request.issues, 4134 RadrootsOrderIssue::RequestOrderIdMismatch { 4135 event_id: event_id(1), 4136 }, 4137 ); 4138 4139 let mut bad_decision = accepted_decision(); 4140 bad_decision.payload.order_id = order_id("order-2"); 4141 let mut bad_proposal = revision_proposal(); 4142 bad_proposal.payload.order_id = order_id("order-2"); 4143 let mut bad_revision_decision = accepted_revision_decision(); 4144 bad_revision_decision.payload.order_id = order_id("order-2"); 4145 let mut bad_cancellation = cancellation(event_id(1)); 4146 bad_cancellation.payload.order_id = order_id("order-2"); 4147 let invalid_non_requests = reduce_order_events( 4148 &order_id("order-1"), 4149 RadrootsOrderReductionInputs { 4150 requests: vec![request_record()], 4151 decisions: vec![bad_decision], 4152 revision_proposals: vec![bad_proposal], 4153 revision_decisions: vec![bad_revision_decision], 4154 cancellations: vec![bad_cancellation], 4155 }, 4156 ); 4157 4158 assert_order_issue_kind( 4159 &invalid_non_requests.issues, 4160 RadrootsOrderIssue::DecisionOrderIdMismatch { 4161 event_id: event_id(2), 4162 }, 4163 ); 4164 assert_order_issue_kind( 4165 &invalid_non_requests.issues, 4166 RadrootsOrderIssue::RevisionProposalOrderIdMismatch { 4167 event_id: event_id(3), 4168 }, 4169 ); 4170 assert_order_issue_kind( 4171 &invalid_non_requests.issues, 4172 RadrootsOrderIssue::RevisionDecisionOrderIdMismatch { 4173 event_id: event_id(4), 4174 }, 4175 ); 4176 assert_order_issue_kind( 4177 &invalid_non_requests.issues, 4178 RadrootsOrderIssue::CancellationOrderIdMismatch { 4179 event_id: event_id(5), 4180 }, 4181 ); 4182 } 4183 4184 #[test] 4185 fn inventory_accounting_reports_invalid_unknown_overreserved_and_terminal_orders() { 4186 let mut unknown_bin_request = request_record(); 4187 unknown_bin_request.event_id = event_id(40); 4188 unknown_bin_request.payload.order_id = order_id("order-4"); 4189 unknown_bin_request.payload.items[0].bin_id = bin_id("bin-missing"); 4190 unknown_bin_request.payload.economics.items[0].bin_id = bin_id("bin-missing"); 4191 4192 let mut unknown_bin_decision = accepted_decision(); 4193 unknown_bin_decision.event_id = event_id(41); 4194 unknown_bin_decision.root_event_id = event_id(40); 4195 unknown_bin_decision.prev_event_id = event_id(40); 4196 unknown_bin_decision.payload.order_id = order_id("order-4"); 4197 if let RadrootsOrderDecisionOutcome::Accepted { 4198 inventory_commitments, 4199 } = &mut unknown_bin_decision.payload.decision 4200 { 4201 inventory_commitments[0].bin_id = bin_id("bin-missing"); 4202 } 4203 4204 let mut declined_request = request_record(); 4205 declined_request.event_id = event_id(20); 4206 declined_request.payload.order_id = order_id("order-2"); 4207 let mut terminal_decline = declined_decision(); 4208 terminal_decline.event_id = event_id(21); 4209 terminal_decline.root_event_id = event_id(20); 4210 terminal_decline.prev_event_id = event_id(20); 4211 terminal_decline.payload.order_id = order_id("order-2"); 4212 4213 let mut cancelled_request = request_record(); 4214 cancelled_request.event_id = event_id(30); 4215 cancelled_request.payload.order_id = order_id("order-3"); 4216 let mut terminal_cancellation = cancellation(event_id(30)); 4217 terminal_cancellation.event_id = event_id(31); 4218 terminal_cancellation.root_event_id = event_id(30); 4219 terminal_cancellation.payload.order_id = order_id("order-3"); 4220 4221 let projection = reduce_listing_inventory_accounting( 4222 &listing_addr(), 4223 &event_id(9), 4224 RadrootsListingInventoryAccountingInputs { 4225 bins: vec![ 4226 RadrootsListingInventoryBinAvailability { 4227 bin_id: bin_id("bin-1"), 4228 available_count: 1, 4229 }, 4230 RadrootsListingInventoryBinAvailability { 4231 bin_id: bin_id("bin-overflow"), 4232 available_count: u64::MAX, 4233 }, 4234 RadrootsListingInventoryBinAvailability { 4235 bin_id: bin_id("bin-overflow"), 4236 available_count: 1, 4237 }, 4238 ], 4239 requests: vec![ 4240 request_record(), 4241 unknown_bin_request, 4242 declined_request, 4243 cancelled_request, 4244 ], 4245 decisions: vec![accepted_decision(), unknown_bin_decision, terminal_decline], 4246 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(), 4247 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(), 4248 cancellations: vec![terminal_cancellation], 4249 }, 4250 ); 4251 4252 assert_eq!(projection.declined_order_ids, vec![order_id("order-2")]); 4253 assert_eq!(projection.cancelled_order_ids, vec![order_id("order-3")]); 4254 assert_inventory_issue_kind( 4255 &projection.issues, 4256 RadrootsListingInventoryAccountingIssue::ArithmeticOverflow { 4257 bin_id: bin_id("bin-overflow"), 4258 event_ids: Vec::new(), 4259 }, 4260 ); 4261 assert_inventory_issue_kind( 4262 &projection.issues, 4263 RadrootsListingInventoryAccountingIssue::UnknownInventoryBin { 4264 bin_id: bin_id("bin-missing"), 4265 event_ids: Vec::new(), 4266 }, 4267 ); 4268 assert_inventory_issue_kind( 4269 &projection.issues, 4270 RadrootsListingInventoryAccountingIssue::OverReserved { 4271 bin_id: bin_id("bin-1"), 4272 available_count: 0, 4273 reserved_count: 0, 4274 event_ids: Vec::new(), 4275 }, 4276 ); 4277 4278 let invalid_without_request = reduce_listing_inventory_accounting( 4279 &listing_addr(), 4280 &event_id(9), 4281 RadrootsListingInventoryAccountingInputs { 4282 bins: Vec::<RadrootsListingInventoryBinAvailability>::new(), 4283 requests: Vec::<RadrootsOrderRequestRecord>::new(), 4284 decisions: vec![accepted_decision()], 4285 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(), 4286 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(), 4287 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(), 4288 }, 4289 ); 4290 assert_eq!(invalid_without_request.invalid_event_ids, vec![event_id(2)]); 4291 assert_inventory_issue_kind( 4292 &invalid_without_request.issues, 4293 RadrootsListingInventoryAccountingIssue::InvalidOrder { 4294 order_id: order_id("order-1"), 4295 event_ids: Vec::new(), 4296 }, 4297 ); 4298 4299 let mut duplicate_request = request_record(); 4300 duplicate_request.event_id = event_id(11); 4301 let invalid_duplicate_requests = reduce_listing_inventory_accounting( 4302 &listing_addr(), 4303 &event_id(9), 4304 RadrootsListingInventoryAccountingInputs { 4305 bins: Vec::<RadrootsListingInventoryBinAvailability>::new(), 4306 requests: vec![request_record(), duplicate_request], 4307 decisions: Vec::<RadrootsOrderDecisionRecord>::new(), 4308 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(), 4309 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(), 4310 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(), 4311 }, 4312 ); 4313 assert_eq!( 4314 invalid_duplicate_requests.invalid_event_ids, 4315 vec![event_id(1), event_id(11)] 4316 ); 4317 assert_inventory_issue_kind( 4318 &invalid_duplicate_requests.issues, 4319 RadrootsListingInventoryAccountingIssue::InvalidOrder { 4320 order_id: order_id("order-1"), 4321 event_ids: Vec::new(), 4322 }, 4323 ); 4324 4325 let invalid_revision_streams = reduce_listing_inventory_accounting( 4326 &listing_addr(), 4327 &event_id(9), 4328 RadrootsListingInventoryAccountingInputs { 4329 bins: Vec::<RadrootsListingInventoryBinAvailability>::new(), 4330 requests: Vec::<RadrootsOrderRequestRecord>::new(), 4331 decisions: vec![accepted_decision()], 4332 revision_proposals: vec![revision_proposal()], 4333 revision_decisions: vec![accepted_revision_decision()], 4334 cancellations: vec![cancellation(event_id(3))], 4335 }, 4336 ); 4337 assert_eq!( 4338 invalid_revision_streams.invalid_event_ids, 4339 vec![event_id(2), event_id(3), event_id(4), event_id(5)] 4340 ); 4341 assert_inventory_issue_kind( 4342 &invalid_revision_streams.issues, 4343 RadrootsListingInventoryAccountingIssue::InvalidOrder { 4344 order_id: order_id("order-1"), 4345 event_ids: Vec::new(), 4346 }, 4347 ); 4348 } 4349 4350 #[test] 4351 fn reducer_projects_requested_order() { 4352 let projection = reduce(Vec::new(), Vec::new(), Vec::new(), Vec::new()); 4353 4354 assert_eq!(projection.issues, Vec::new()); 4355 assert_eq!(projection.status, RadrootsOrderStatus::Requested); 4356 assert_eq!(projection.request_event_id, Some(event_id(1))); 4357 assert!(!projection.lifecycle_terminal); 4358 assert!(projection.agreement_event_id.is_none()); 4359 } 4360 4361 #[test] 4362 fn reducer_projects_accepted_order_agreement() { 4363 let projection = reduce( 4364 vec![accepted_decision()], 4365 Vec::new(), 4366 Vec::new(), 4367 Vec::new(), 4368 ); 4369 4370 assert_eq!(projection.status, RadrootsOrderStatus::Accepted); 4371 assert_eq!(projection.decision_event_id, Some(event_id(2))); 4372 assert_eq!(projection.agreement_event_id, Some(event_id(2))); 4373 assert!(projection.lifecycle_terminal); 4374 } 4375 4376 #[test] 4377 fn reducer_projects_declined_order() { 4378 let projection = reduce( 4379 vec![declined_decision()], 4380 Vec::new(), 4381 Vec::new(), 4382 Vec::new(), 4383 ); 4384 4385 assert_eq!(projection.status, RadrootsOrderStatus::Declined); 4386 assert_eq!(projection.decision_event_id, Some(event_id(2))); 4387 assert!(projection.lifecycle_terminal); 4388 } 4389 4390 #[test] 4391 fn reducer_projects_revision_acceptance_as_agreement() { 4392 let projection = reduce( 4393 Vec::new(), 4394 vec![revision_proposal()], 4395 vec![accepted_revision_decision()], 4396 Vec::new(), 4397 ); 4398 4399 assert_eq!(projection.status, RadrootsOrderStatus::Accepted); 4400 assert_eq!(projection.agreement_event_id, Some(event_id(4))); 4401 assert_eq!( 4402 projection.economics.expect("economics").items[0].bin_count, 4403 1 4404 ); 4405 assert!(projection.lifecycle_terminal); 4406 } 4407 4408 #[test] 4409 fn reducer_allows_pre_agreement_cancellation() { 4410 let projection = reduce( 4411 Vec::new(), 4412 Vec::new(), 4413 Vec::new(), 4414 vec![cancellation(event_id(1))], 4415 ); 4416 4417 assert_eq!(projection.status, RadrootsOrderStatus::Cancelled); 4418 assert_eq!(projection.cancellation_event_id, Some(event_id(5))); 4419 assert!(projection.lifecycle_terminal); 4420 } 4421 4422 #[test] 4423 fn reducer_rejects_cancellation_after_agreement() { 4424 let projection = reduce( 4425 vec![accepted_decision()], 4426 Vec::new(), 4427 Vec::new(), 4428 vec![cancellation(event_id(2))], 4429 ); 4430 4431 assert_eq!(projection.status, RadrootsOrderStatus::Invalid); 4432 assert!(projection.lifecycle_terminal); 4433 } 4434 4435 #[test] 4436 fn reducer_groups_event_records() { 4437 let projection = reduce_order_event_records( 4438 &order_id("order-1"), 4439 vec![ 4440 RadrootsOrderEventRecord::Request(request_record()), 4441 RadrootsOrderEventRecord::Decision(accepted_decision()), 4442 ], 4443 ); 4444 4445 assert_eq!(projection.status, RadrootsOrderStatus::Accepted); 4446 assert_eq!(projection.agreement_event_id, Some(event_id(2))); 4447 } 4448 4449 #[test] 4450 fn inventory_accounting_reserves_only_accepted_agreements() { 4451 let requested_projection = reduce_listing_inventory_accounting( 4452 &listing_addr(), 4453 &event_id(8), 4454 RadrootsListingInventoryAccountingInputs { 4455 bins: vec![RadrootsListingInventoryBinAvailability { 4456 bin_id: bin_id("bin-1"), 4457 available_count: 3, 4458 }], 4459 requests: vec![request_record()], 4460 decisions: Vec::<RadrootsOrderDecisionRecord>::new(), 4461 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(), 4462 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(), 4463 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(), 4464 }, 4465 ); 4466 4467 assert_eq!(requested_projection.bins[0].accepted_reserved_count, 0); 4468 assert_eq!(requested_projection.bins[0].remaining_count, 3); 4469 4470 let projection = reduce_listing_inventory_accounting( 4471 &listing_addr(), 4472 &event_id(9), 4473 RadrootsListingInventoryAccountingInputs { 4474 bins: vec![RadrootsListingInventoryBinAvailability { 4475 bin_id: bin_id("bin-1"), 4476 available_count: 3, 4477 }], 4478 requests: vec![request_record()], 4479 decisions: vec![accepted_decision()], 4480 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(), 4481 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(), 4482 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(), 4483 }, 4484 ); 4485 4486 assert_eq!(projection.bins[0].accepted_reserved_count, 2); 4487 assert_eq!(projection.bins[0].remaining_count, 1); 4488 assert_eq!( 4489 projection.bins[0].accepted_orders[0].agreement_event_id, 4490 event_id(2) 4491 ); 4492 } 4493 }