Skip to content

Commit 3a3e4f4

Browse files
committed
Fix abandoning conflicting txs
1 parent 74be598 commit 3a3e4f4

File tree

2 files changed

+126
-82
lines changed

2 files changed

+126
-82
lines changed

wallet/src/account/output_cache/mod.rs

+114-82
Original file line numberDiff line numberDiff line change
@@ -813,8 +813,40 @@ impl OutputCache {
813813
let mut conflicting_txs_with_descendants = vec![];
814814

815815
for conflicting_tx in conflicting_txs {
816-
let descendants =
817-
self.remove_descendants_and_mark_as(conflicting_tx, TxState::Conflicted(block_id))?;
816+
let descendants = self.remove_descendants(conflicting_tx);
817+
818+
// Mark conflicting tx and its descendants as Conflicting and update OutputCache data accordingly
819+
for tx_id in descendants.iter().rev().copied() {
820+
match self.txs.entry(tx_id.into()) {
821+
Entry::Occupied(mut entry) => match entry.get_mut() {
822+
WalletTx::Block(_) => Err(WalletError::CannotFindTransactionWithId(tx_id)),
823+
WalletTx::Tx(tx) => match tx.state() {
824+
TxState::Inactive(_) => {
825+
tx.set_state(TxState::Conflicted(block_id));
826+
OutputCache::rollback_tx_data(
827+
tx,
828+
&self.unconfirmed_descendants,
829+
&mut self.consumed,
830+
&mut self.delegations,
831+
&mut self.token_issuance,
832+
&mut self.orders,
833+
);
834+
Ok(())
835+
}
836+
TxState::Abandoned
837+
| TxState::Confirmed(..)
838+
| TxState::InMempool(..)
839+
| TxState::Conflicted(..) => {
840+
Err(WalletError::CannotChangeTransactionState(
841+
*tx.state(),
842+
TxState::Conflicted(block_id),
843+
))
844+
}
845+
},
846+
},
847+
Entry::Vacant(_) => Err(WalletError::CannotFindTransactionWithId(tx_id)),
848+
}?;
849+
}
818850

819851
conflicting_txs_with_descendants.extend(descendants.into_iter());
820852
}
@@ -1446,11 +1478,7 @@ impl OutputCache {
14461478
})
14471479
}
14481480

1449-
fn remove_descendants_and_mark_as(
1450-
&mut self,
1451-
tx_id: Id<Transaction>,
1452-
new_state: TxState,
1453-
) -> WalletResult<Vec<Id<Transaction>>> {
1481+
fn remove_descendants(&mut self, tx_id: Id<Transaction>) -> Vec<Id<Transaction>> {
14541482
let mut all_txs = Vec::new();
14551483
let mut to_update = BTreeSet::from_iter([OutPointSourceId::from(tx_id)]);
14561484

@@ -1462,85 +1490,56 @@ impl OutputCache {
14621490
}
14631491
}
14641492

1465-
for tx_id in all_txs.iter().rev().copied() {
1466-
match self.txs.entry(tx_id.into()) {
1467-
Entry::Occupied(mut entry) => match entry.get_mut() {
1468-
WalletTx::Block(_) => Err(WalletError::CannotFindTransactionWithId(tx_id)),
1469-
WalletTx::Tx(tx) => {
1470-
ensure!(
1471-
tx.state() != &new_state,
1472-
WalletError::CannotChangeTransactionState(*tx.state(), new_state)
1473-
);
1493+
all_txs
1494+
}
14741495

1475-
match tx.state() {
1476-
TxState::Inactive(_) | TxState::Conflicted(_) | TxState::Abandoned => {
1477-
tx.set_state(new_state);
1478-
for input in tx.get_transaction().inputs() {
1479-
match input {
1480-
TxInput::Utxo(outpoint) => {
1481-
self.consumed.insert(outpoint.clone(), *tx.state());
1482-
}
1483-
TxInput::Account(outpoint) => match outpoint.account() {
1484-
AccountSpending::DelegationBalance(
1485-
delegation_id,
1486-
_,
1487-
) => {
1488-
if let Some(data) =
1489-
self.delegations.get_mut(delegation_id)
1490-
{
1491-
data.last_nonce = outpoint.nonce().decrement();
1492-
data.last_parent = find_parent(
1493-
&self.unconfirmed_descendants,
1494-
tx_id.into(),
1495-
);
1496-
}
1497-
}
1498-
},
1499-
TxInput::AccountCommand(nonce, op) => match op {
1500-
AccountCommand::MintTokens(token_id, _)
1501-
| AccountCommand::UnmintTokens(token_id)
1502-
| AccountCommand::LockTokenSupply(token_id)
1503-
| AccountCommand::FreezeToken(token_id, _)
1504-
| AccountCommand::UnfreezeToken(token_id)
1505-
| AccountCommand::ChangeTokenMetadataUri(token_id, _)
1506-
| AccountCommand::ChangeTokenAuthority(token_id, _) => {
1507-
if let Some(data) =
1508-
self.token_issuance.get_mut(token_id)
1509-
{
1510-
data.last_nonce = nonce.decrement();
1511-
data.last_parent = find_parent(
1512-
&self.unconfirmed_descendants,
1513-
tx_id.into(),
1514-
);
1515-
data.unconfirmed_txs.remove(&tx_id.into());
1516-
}
1517-
}
1518-
AccountCommand::ConcludeOrder(order_id)
1519-
| AccountCommand::FillOrder(order_id, _, _) => {
1520-
if let Some(data) = self.orders.get_mut(order_id) {
1521-
data.last_nonce = nonce.decrement();
1522-
data.last_parent = find_parent(
1523-
&self.unconfirmed_descendants,
1524-
tx_id.into(),
1525-
);
1526-
}
1527-
}
1528-
},
1529-
}
1530-
}
1531-
Ok(())
1532-
}
1533-
TxState::Confirmed(..) | TxState::InMempool(..) => Err(
1534-
WalletError::CannotChangeTransactionState(*tx.state(), new_state),
1535-
),
1496+
// After tx is abandoned or marked as conflicted its effect on OutputCache should be rolled back
1497+
fn rollback_tx_data(
1498+
tx: &TxData,
1499+
unconfirmed_descendants: &BTreeMap<OutPointSourceId, BTreeSet<OutPointSourceId>>,
1500+
consumed: &mut BTreeMap<UtxoOutPoint, TxState>,
1501+
delegations: &mut BTreeMap<DelegationId, DelegationData>,
1502+
token_issuance: &mut BTreeMap<TokenId, TokenIssuanceData>,
1503+
orders: &mut BTreeMap<OrderId, OrderData>,
1504+
) {
1505+
let tx_id = tx.get_transaction().get_id();
1506+
for input in tx.get_transaction().inputs() {
1507+
match input {
1508+
TxInput::Utxo(outpoint) => {
1509+
consumed.insert(outpoint.clone(), *tx.state());
1510+
}
1511+
TxInput::Account(outpoint) => match outpoint.account() {
1512+
AccountSpending::DelegationBalance(delegation_id, _) => {
1513+
if let Some(data) = delegations.get_mut(delegation_id) {
1514+
data.last_nonce = outpoint.nonce().decrement();
1515+
data.last_parent = find_parent(unconfirmed_descendants, tx_id.into());
15361516
}
15371517
}
15381518
},
1539-
Entry::Vacant(_) => Err(WalletError::CannotFindTransactionWithId(tx_id)),
1540-
}?;
1519+
TxInput::AccountCommand(nonce, op) => match op {
1520+
AccountCommand::MintTokens(token_id, _)
1521+
| AccountCommand::UnmintTokens(token_id)
1522+
| AccountCommand::LockTokenSupply(token_id)
1523+
| AccountCommand::FreezeToken(token_id, _)
1524+
| AccountCommand::UnfreezeToken(token_id)
1525+
| AccountCommand::ChangeTokenMetadataUri(token_id, _)
1526+
| AccountCommand::ChangeTokenAuthority(token_id, _) => {
1527+
if let Some(data) = token_issuance.get_mut(token_id) {
1528+
data.last_nonce = nonce.decrement();
1529+
data.last_parent = find_parent(unconfirmed_descendants, tx_id.into());
1530+
data.unconfirmed_txs.remove(&tx_id.into());
1531+
}
1532+
}
1533+
AccountCommand::ConcludeOrder(order_id)
1534+
| AccountCommand::FillOrder(order_id, _, _) => {
1535+
if let Some(data) = orders.get_mut(order_id) {
1536+
data.last_nonce = nonce.decrement();
1537+
data.last_parent = find_parent(unconfirmed_descendants, tx_id.into());
1538+
}
1539+
}
1540+
},
1541+
}
15411542
}
1542-
1543-
Ok(all_txs)
15441543
}
15451544

15461545
/// Mark a transaction and its descendants as abandoned
@@ -1549,7 +1548,40 @@ impl OutputCache {
15491548
&mut self,
15501549
tx_id: Id<Transaction>,
15511550
) -> WalletResult<Vec<Id<Transaction>>> {
1552-
self.remove_descendants_and_mark_as(tx_id, TxState::Abandoned)
1551+
let all_abandoned = self.remove_descendants(tx_id);
1552+
1553+
for tx_id in all_abandoned.iter().rev().copied() {
1554+
match self.txs.entry(tx_id.into()) {
1555+
Entry::Occupied(mut entry) => match entry.get_mut() {
1556+
WalletTx::Block(_) => Err(WalletError::CannotFindTransactionWithId(tx_id)),
1557+
WalletTx::Tx(tx) => match tx.state() {
1558+
TxState::Inactive(_) => {
1559+
tx.set_state(TxState::Abandoned);
1560+
OutputCache::rollback_tx_data(
1561+
tx,
1562+
&self.unconfirmed_descendants,
1563+
&mut self.consumed,
1564+
&mut self.delegations,
1565+
&mut self.token_issuance,
1566+
&mut self.orders,
1567+
);
1568+
Ok(())
1569+
}
1570+
TxState::Conflicted(_) => {
1571+
tx.set_state(TxState::Abandoned);
1572+
Ok(())
1573+
}
1574+
state => Err(WalletError::CannotChangeTransactionState(
1575+
*state,
1576+
TxState::Abandoned,
1577+
)),
1578+
},
1579+
},
1580+
Entry::Vacant(_) => Err(WalletError::CannotFindTransactionWithId(tx_id)),
1581+
}?;
1582+
}
1583+
1584+
Ok(all_abandoned)
15531585
}
15541586

15551587
pub fn get_transaction(&self, transaction_id: Id<Transaction>) -> WalletResult<&TxData> {

wallet/src/wallet/tests.rs

+12
Original file line numberDiff line numberDiff line change
@@ -6740,6 +6740,12 @@ fn conflicting_delegation_account_nonce(#[case] seed: Seed) {
67406740
TxState::Abandoned
67416741
);
67426742

6743+
let mut delegations = wallet.get_delegations(DEFAULT_ACCOUNT_INDEX).unwrap().collect_vec();
6744+
assert_eq!(delegations.len(), 1);
6745+
let (deleg_id, deleg_data) = delegations.pop().unwrap();
6746+
assert_eq!(*deleg_id, delegation_id);
6747+
assert_eq!(deleg_data.last_nonce, Some(AccountNonce::new(0)));
6748+
67436749
wallet
67446750
.abandon_transaction(DEFAULT_ACCOUNT_INDEX, spend_from_delegation_tx_2_id)
67456751
.unwrap();
@@ -6750,6 +6756,12 @@ fn conflicting_delegation_account_nonce(#[case] seed: Seed) {
67506756
.state(),
67516757
TxState::Abandoned
67526758
);
6759+
6760+
let mut delegations = wallet.get_delegations(DEFAULT_ACCOUNT_INDEX).unwrap().collect_vec();
6761+
assert_eq!(delegations.len(), 1);
6762+
let (deleg_id, deleg_data) = delegations.pop().unwrap();
6763+
assert_eq!(*deleg_id, delegation_id);
6764+
assert_eq!(deleg_data.last_nonce, Some(AccountNonce::new(0)));
67536765
}
67546766

67556767
// Create a pool and a delegation with some share.

0 commit comments

Comments
 (0)