Skip to content

Commit c3dbcdd

Browse files
committed
Merge branch 'master' into rand_upgrade_autorename_gen_to_random
2 parents 252725e + 69b90e9 commit c3dbcdd

3 files changed

Lines changed: 411 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/
2222
- `wallet-create`/`wallet-recover`/`wallet-open` support the `ledger` subcommand, in addition to the existing
2323
`software` and `trezor`, which specifies the type of the wallet to operate on.
2424

25+
### Fixed
26+
- Wallet:
27+
- Fixed handling of confirmed and unconfirmed conflicting order transactions in the wallet.
28+
2529
## [1.3.0] - 2026-04-09
2630

2731
### Added

wallet/src/account/output_cache/mod.rs

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -783,9 +783,11 @@ impl OutputCache {
783783
confirmed_tx: &Transaction,
784784
block_id: Id<GenBlock>,
785785
) -> WalletResult<Vec<(Id<Transaction>, WalletTx)>> {
786+
// TODO: maybe make it an enum
786787
struct ConflictCheck {
787788
frozen_token_id: Option<TokenId>,
788789
confirmed_account_nonce: Option<(AccountType, AccountNonce)>,
790+
conflicting_order_command: Option<(OrderAccountCommandTag, OrderId)>,
789791
}
790792

791793
let conflict_checks = confirmed_tx
@@ -804,6 +806,7 @@ impl OutputCache {
804806
outpoint.account().into(),
805807
outpoint.nonce(),
806808
)),
809+
conflicting_order_command: None,
807810
}),
808811
TxInput::AccountCommand(nonce, cmd) => match cmd {
809812
AccountCommand::MintTokens(_, _)
@@ -816,13 +819,34 @@ impl OutputCache {
816819
| AccountCommand::FillOrder(_, _, _) => Some(ConflictCheck {
817820
frozen_token_id: None,
818821
confirmed_account_nonce: Some((cmd.into(), *nonce)),
822+
conflicting_order_command: None,
819823
}),
820824
| AccountCommand::FreezeToken(token_id, _) => Some(ConflictCheck {
821825
frozen_token_id: Some(*token_id),
822826
confirmed_account_nonce: Some((cmd.into(), *nonce)),
827+
conflicting_order_command: None,
823828
}),
824829
},
825-
TxInput::OrderAccountCommand(_) => None,
830+
TxInput::OrderAccountCommand(cmd) => {
831+
let order_id = match cmd {
832+
OrderAccountCommand::FillOrder(order_id, _)
833+
| OrderAccountCommand::FreezeOrder(order_id)
834+
| OrderAccountCommand::ConcludeOrder(order_id) => *order_id,
835+
};
836+
let cmd_tag: OrderAccountCommandTag = cmd.into();
837+
match cmd_tag {
838+
// ConcludeOrder and FreezeOrder are exclusive: only one tx can win.
839+
// Any unconfirmed tx with the same command for the same order conflicts.
840+
OrderAccountCommandTag::ConcludeOrder
841+
| OrderAccountCommandTag::FreezeOrder => Some(ConflictCheck {
842+
frozen_token_id: None,
843+
confirmed_account_nonce: None,
844+
conflicting_order_command: Some((cmd_tag, order_id)),
845+
}),
846+
// Multiple fills for the same order can coexist.
847+
OrderAccountCommandTag::FillOrder => None,
848+
}
849+
}
826850
}
827851
})
828852
.collect::<Vec<_>>();
@@ -857,6 +881,21 @@ impl OutputCache {
857881
continue;
858882
}
859883
}
884+
885+
if let Some((confirmed_cmd_tag, confirmed_order_id)) =
886+
conflict_check.conflicting_order_command
887+
{
888+
if confirmed_tx.get_id() != tx.get_transaction().get_id()
889+
&& uses_conflicting_order_command(
890+
unconfirmed_tx,
891+
confirmed_cmd_tag,
892+
confirmed_order_id,
893+
)
894+
{
895+
conflicting_txs.insert(tx.get_transaction().get_id());
896+
continue;
897+
}
898+
}
860899
}
861900
WalletTx::Block(_) => {
862901
utils::debug_panic_or_log!("Cannot be block reward");
@@ -2062,6 +2101,41 @@ fn uses_conflicting_nonce(
20622101
})
20632102
}
20642103

2104+
fn uses_conflicting_order_command(
2105+
unconfirmed_tx: &WalletTx,
2106+
confirmed_cmd_tag: OrderAccountCommandTag,
2107+
confirmed_order_id: OrderId,
2108+
) -> bool {
2109+
unconfirmed_tx.inputs().iter().any(|input| match input {
2110+
TxInput::OrderAccountCommand(cmd) => {
2111+
let unconfirmed_order_id = match cmd {
2112+
OrderAccountCommand::FillOrder(id, _)
2113+
| OrderAccountCommand::FreezeOrder(id)
2114+
| OrderAccountCommand::ConcludeOrder(id) => *id,
2115+
};
2116+
// It is only a conflict if it is the same order id
2117+
if unconfirmed_order_id != confirmed_order_id {
2118+
return false;
2119+
}
2120+
2121+
let unconfirmed_cmd_tag: OrderAccountCommandTag = cmd.into();
2122+
match confirmed_cmd_tag {
2123+
// Confirmed fill orders do not conflict with anything
2124+
OrderAccountCommandTag::FillOrder => false,
2125+
// Confirmed conclude order conflict with any other unconfirmed operation on the
2126+
// order
2127+
OrderAccountCommandTag::ConcludeOrder => true,
2128+
// Confirmed Freeze order conflicts with any unconfirmed Fill or Freeze order
2129+
OrderAccountCommandTag::FreezeOrder => match unconfirmed_cmd_tag {
2130+
OrderAccountCommandTag::FillOrder | OrderAccountCommandTag::FreezeOrder => true,
2131+
OrderAccountCommandTag::ConcludeOrder => false,
2132+
},
2133+
}
2134+
}
2135+
TxInput::Utxo(_) | TxInput::Account(_) | TxInput::AccountCommand(_, _) => false,
2136+
})
2137+
}
2138+
20652139
#[derive(thiserror::Error, Debug, Eq, PartialEq)]
20662140
pub enum OutputCacheInconsistencyError {
20672141
#[error("Transaction from {0:?} is confirmed and among unconfirmed descendants")]

0 commit comments

Comments
 (0)