use std::{
collections::{
BTreeMap,
HashMap,
},
fmt::Debug,
hash::Hash,
};
use fuel_core_types::{
fuel_tx::{
BlobId,
ContractId,
Input,
Output,
TxId,
UtxoId,
field::BlobId as _,
input::{
coin::{
CoinPredicate,
CoinSigned,
},
message::{
MessageCoinPredicate,
MessageCoinSigned,
MessageDataPredicate,
MessageDataSigned,
},
},
},
fuel_types::Nonce,
services::txpool::PoolTransaction,
};
use crate::{
error::{
CollisionReason,
Error,
InputValidationError,
},
storage::StorageData,
};
use super::{
CollisionManager,
Collisions,
};
#[cfg(test)]
use fuel_core_types::services::txpool::ArcPoolTx;
pub struct BasicCollisionManager<StorageIndex> {
messages_spenders: HashMap<Nonce, StorageIndex>,
coins_spenders: BTreeMap<UtxoId, StorageIndex>,
contracts_creators: HashMap<ContractId, StorageIndex>,
blobs_users: HashMap<BlobId, StorageIndex>,
}
impl<StorageIndex> BasicCollisionManager<StorageIndex> {
pub fn new() -> Self {
Self {
messages_spenders: HashMap::new(),
coins_spenders: BTreeMap::new(),
contracts_creators: HashMap::new(),
blobs_users: HashMap::new(),
}
}
#[cfg(test)]
pub fn is_empty(&self) -> bool {
self.messages_spenders.is_empty()
&& self.coins_spenders.is_empty()
&& self.contracts_creators.is_empty()
&& self.blobs_users.is_empty()
}
#[cfg(test)]
pub(crate) fn assert_integrity(&self, expected_txs: &[ArcPoolTx]) {
use std::ops::Deref;
let mut message_spenders = HashMap::new();
let mut coins_spenders = BTreeMap::new();
let mut contracts_creators = HashMap::new();
let mut blobs_users = HashMap::new();
for tx in expected_txs {
if let PoolTransaction::Blob(checked_tx, _) = tx.deref() {
let blob_id = checked_tx.transaction().blob_id();
blobs_users.insert(*blob_id, tx.id());
}
for input in tx.inputs() {
match input {
Input::CoinSigned(CoinSigned { utxo_id, .. })
| Input::CoinPredicate(CoinPredicate { utxo_id, .. }) => {
coins_spenders.insert(*utxo_id, tx.id());
}
Input::MessageCoinSigned(MessageCoinSigned { nonce, .. })
| Input::MessageCoinPredicate(MessageCoinPredicate {
nonce, ..
})
| Input::MessageDataSigned(MessageDataSigned { nonce, .. })
| Input::MessageDataPredicate(MessageDataPredicate {
nonce, ..
}) => {
message_spenders.insert(*nonce, tx.id());
}
Input::Contract { .. } => {}
}
}
for output in tx.outputs() {
if let Output::ContractCreated { contract_id, .. } = output {
contracts_creators.insert(*contract_id, tx.id());
}
}
}
for nonce in self.messages_spenders.keys() {
message_spenders.remove(nonce).unwrap_or_else(|| panic!(
"A message ({}) spender is present on the collision manager that shouldn't be there.",
nonce
));
}
assert!(
message_spenders.is_empty(),
"Some message spenders are missing from the collision manager: {:?}",
message_spenders
);
for utxo_id in self.coins_spenders.keys() {
coins_spenders.remove(utxo_id).unwrap_or_else(|| panic!(
"A coin ({}) spender is present on the collision manager that shouldn't be there.",
utxo_id
));
}
assert!(
coins_spenders.is_empty(),
"Some coin senders are missing from the collision manager: {:?}",
coins_spenders
);
for contract_id in self.contracts_creators.keys() {
contracts_creators.remove(contract_id).unwrap_or_else(|| panic!(
"A contract ({}) creator is present on the collision manager that shouldn't be there.",
contract_id
));
}
assert!(
contracts_creators.is_empty(),
"Some contract creators are missing from the collision manager: {:?}",
contracts_creators
);
}
}
impl<StorageIndex> Default for BasicCollisionManager<StorageIndex> {
fn default() -> Self {
Self::new()
}
}
impl<StorageIndex> CollisionManager for BasicCollisionManager<StorageIndex>
where
StorageIndex: Copy + Debug + Hash + PartialEq + Eq,
{
type StorageIndex = StorageIndex;
fn get_coins_spenders(&self, tx_creator_id: &TxId) -> Vec<Self::StorageIndex> {
self.coins_spenders
.range(UtxoId::new(*tx_creator_id, 0)..UtxoId::new(*tx_creator_id, u16::MAX))
.map(|(_, storage_id)| *storage_id)
.collect()
}
fn find_collisions(
&self,
transaction: &PoolTransaction,
) -> Result<Collisions<Self::StorageIndex>, Error> {
let mut collisions = HashMap::new();
if let PoolTransaction::Blob(checked_tx, _) = &transaction {
let blob_id = checked_tx.transaction().blob_id();
if let Some(state) = self.blobs_users.get(blob_id) {
collisions.insert(*state, vec![CollisionReason::Blob(*blob_id)]);
}
}
for input in transaction.inputs() {
match input {
Input::CoinSigned(CoinSigned { utxo_id, .. })
| Input::CoinPredicate(CoinPredicate { utxo_id, .. }) => {
if let Some(storage_id) = self.coins_spenders.get(utxo_id) {
let entry = collisions.entry(*storage_id).or_default();
entry.push(CollisionReason::Utxo(*utxo_id));
}
}
Input::MessageCoinSigned(MessageCoinSigned { nonce, .. })
| Input::MessageCoinPredicate(MessageCoinPredicate { nonce, .. })
| Input::MessageDataSigned(MessageDataSigned { nonce, .. })
| Input::MessageDataPredicate(MessageDataPredicate { nonce, .. }) => {
if let Some(storage_id) = self.messages_spenders.get(nonce) {
let entry = collisions.entry(*storage_id).or_default();
entry.push(CollisionReason::Message(*nonce));
}
}
_ => {}
}
}
for (i, output) in transaction.outputs().iter().enumerate() {
match output {
Output::ContractCreated { contract_id, .. } => {
if let Some(storage_id) = self.contracts_creators.get(contract_id) {
let entry = collisions.entry(*storage_id).or_default();
entry.push(CollisionReason::ContractCreation(*contract_id));
}
}
Output::Coin { .. } | Output::Change { .. } | Output::Variable { .. } => {
let utxo_id = UtxoId::new(
transaction.id(),
u16::try_from(i)
.expect("`Checked` transaction has less than 2^16 outputs"),
);
if self.coins_spenders.contains_key(&utxo_id) {
return Err(Error::InputValidation(
InputValidationError::DuplicateTxId(transaction.id()),
));
}
}
Output::Contract(_) => {}
}
}
Ok(collisions)
}
fn on_stored_transaction(
&mut self,
storage_id: StorageIndex,
store_entry: &StorageData,
) {
if let PoolTransaction::Blob(checked_tx, _) = store_entry.transaction.as_ref() {
let blob_id = checked_tx.transaction().blob_id();
self.blobs_users.insert(*blob_id, storage_id);
}
for input in store_entry.transaction.inputs() {
match input {
Input::CoinSigned(CoinSigned { utxo_id, .. })
| Input::CoinPredicate(CoinPredicate { utxo_id, .. }) => {
self.coins_spenders.insert(*utxo_id, storage_id);
}
Input::MessageCoinSigned(MessageCoinSigned { nonce, .. })
| Input::MessageCoinPredicate(MessageCoinPredicate { nonce, .. })
| Input::MessageDataSigned(MessageDataSigned { nonce, .. })
| Input::MessageDataPredicate(MessageDataPredicate { nonce, .. }) => {
self.messages_spenders.insert(*nonce, storage_id);
}
_ => {}
}
}
for output in store_entry.transaction.outputs().iter() {
match output {
Output::Coin { .. }
| Output::Change { .. }
| Output::Variable { .. }
| Output::Contract(_) => {}
Output::ContractCreated { contract_id, .. } => {
self.contracts_creators.insert(*contract_id, storage_id);
}
};
}
}
fn on_removed_transaction(&mut self, transaction: &PoolTransaction) {
if let PoolTransaction::Blob(checked_tx, _) = transaction {
let blob_id = checked_tx.transaction().blob_id();
self.blobs_users.remove(blob_id);
}
for input in transaction.inputs() {
match input {
Input::CoinSigned(CoinSigned { utxo_id, .. })
| Input::CoinPredicate(CoinPredicate { utxo_id, .. }) => {
self.coins_spenders.remove(utxo_id);
}
Input::MessageCoinSigned(MessageCoinSigned { nonce, .. })
| Input::MessageCoinPredicate(MessageCoinPredicate { nonce, .. })
| Input::MessageDataSigned(MessageDataSigned { nonce, .. })
| Input::MessageDataPredicate(MessageDataPredicate { nonce, .. }) => {
self.messages_spenders.remove(nonce);
}
_ => {}
}
}
for output in transaction.outputs().iter() {
match output {
Output::Coin { .. }
| Output::Change { .. }
| Output::Variable { .. }
| Output::Contract(_) => {}
Output::ContractCreated { contract_id, .. } => {
self.contracts_creators.remove(contract_id);
}
};
}
}
}