Skip to content

Commit

Permalink
feature/PRO-1712/reject-tx (#5332)
Browse files Browse the repository at this point in the history
* feat: Handling of tainted transactions

- Extended DepositChannelDetails with owner field
- Added extrinsic to mark transaction as tainted
- Handling deposit and save details for a refund if tx is tainted

* tests: Added tests

- Added tests to verify that tainted transactions can get detected for all possible swap types
- Added tests  to check that txs marked by other brokers are getting ignored

* chore: Extended LpRegistration trait + Added to Ingress/Egress config

* feat: Getting LP refund address

* feature: ensure by lp and broker + added more tests

* refactor: Don't error if tx is tainted

* refactor: Using DoubleMap instead of Map

* refactor: Using BadOrigin + Added unit test

* refactor: Inline code + Add Deposit Witness to struct

* refactor: Extended lp deposit with refund address

- Taking refund address if we open a deposit channel as lp
- Extended ChannelAction to take an optional refund address
- Removed all dependencies regarding the LpRegistration trait (added last commit)
- Refactored tests, benchmarks, etc

* feature: Added benchmark

* chore: Changes to benchmark

* chore: generated mock weights

* chore: Added POC for BTC swap rejecting.

* chore: only allow mark transactions for BTC

* feature: expire tainted transaction

* test: refactored tests

* chore: Extended RejectCall with DepositWitness

* Revert "chore: Extended RejectCall with DepositWitness"

This reverts commit 67873ff.

* chore: added draft for eth refund implementation

* chore: Removed unused events

* chore: Ensure only by broker

* chore: removed broker from tainted tx struct

* chore: Only clone owner

* chore: Moved tx tainted check

* feature: Added migration for DepositChannelLookup

* refactor: Changed data structure + fixed migrations

* chore: Handle LP refund address as requirement

* chore: Made clippy happy 🙂

* chore: don't manipulate storage in place in iteration 🙅‍♂️

* test: Added migration test 🧪

* chore: changed pallet storage version 📀

* chore: bumped pallet storage version (again)

* chore: using ScheduledTxForReject for refunding

* refactor: Changed accounting of expired transactions

* refactor: using translate for migration

* refactor: using append, refactored test

* feature: Added handling of boost channels

* feature: Marking txs when prewitness and reject when we process the depo

* feat: pre-witnessed rejection handling

* chore: Fixed logic + added tests

* tests: Refactor/Rearranged tests

* chore: Using SECONDS_PER_BLOCK instead of static block seconds

* chore: Addressed comments

* chore: Fixed clippy in CI

* chore: update comments

* fix: don't allow report overwrite

* chore: Renamed event

* feat: improvements:

- mark boosted transactions as boosted instead of using channel status
- allow pallet config instead of relying on chain
- add event for tx reports
- only allow reporting of unseen transactions
- add doc comments
- renaming of types/events
- remove unused error

* chore: removed not needed RefundParameters, added test

* refactor: Rejecting

- Changed trait
- Refactor AllBatch build
- LogOrPanic if reject failed and safe details

* chore: fix compiler errors

* chore: subtract fees

* fix: using deposit address

* fix: addressed problems

- Introduced NoChangeTransfer ApiCall
- Fire event when tx was broadcasted
- Handle on_broadcast_ready with no-op

* chore: small improvements

- Removed not needed derives
- Clippy

* wip: change depositDetails for btc to utxo

* Revert "wip: change depositDetails for btc to utxo"

This reverts commit 55de221.

* refactor: added current status of tx_id refactor

* tests: Moved screening feature tests to own file and test against btc

* refactor:

- Fire event if we can not reject tx
- Split amount and fees in trait
- Fix UTXO construct

* chore: rearranged imports

* refactors:

- use Utxo and remove BtcDepositDetails
- use saturating_sub for egress fee subtraction
- use ChainAccount instead of ForeignChainAddress in RejectCall
- simplify definitions of RejectCall using default method

---------

Co-authored-by: Daniel <daniel@chainflip.io>
Co-authored-by: Maxim Shishmarev <maxim@chainflip.io>
  • Loading branch information
3 people committed Oct 31, 2024
1 parent d905b17 commit e7b8336
Show file tree
Hide file tree
Showing 28 changed files with 949 additions and 676 deletions.
351 changes: 183 additions & 168 deletions Cargo.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ impl From<DepositInfo<Bitcoin>> for WitnessInformation {
amount: value.amount.into(),
asset: value.asset.into(),
deposit_details: Some(DepositDetails::Bitcoin {
tx_id: value.deposit_details.tx_id,
vout: value.deposit_details.vout,
tx_id: value.deposit_details.id.tx_id,
vout: value.deposit_details.id.vout,
}),
}
}
Expand Down
6 changes: 3 additions & 3 deletions engine/src/witness/btc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,15 +211,15 @@ mod tests {
let txs = vec![
fake_transaction(vec![], Some(Amount::from_sat(FEE_0))),
fake_transaction(
fake_verbose_vouts(vec![(2324, vec![0, 32, 121, 9])]),
fake_verbose_vouts(vec![(2324, &DepositAddress::new([0; 32], 0))]),
Some(Amount::from_sat(FEE_1)),
),
fake_transaction(
fake_verbose_vouts(vec![(232232, vec![32, 32, 121, 9])]),
fake_verbose_vouts(vec![(232232, &DepositAddress::new([32; 32], 0))]),
Some(Amount::from_sat(FEE_2)),
),
fake_transaction(
fake_verbose_vouts(vec![(232232, vec![32, 32, 121, 9])]),
fake_verbose_vouts(vec![(232232, &DepositAddress::new([32; 32], 0))]),
Some(Amount::from_sat(FEE_3)),
),
];
Expand Down
167 changes: 87 additions & 80 deletions engine/src/witness/btc/deposits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::{
use bitcoin::{hashes::Hash, BlockHash};
use cf_chains::{
assets::btc,
btc::{ScriptPubkey, UtxoId},
btc::{deposit_address::DepositAddress, Utxo, UtxoId},
Bitcoin,
};

Expand Down Expand Up @@ -56,11 +56,11 @@ impl<Inner: ChunkedByVault> ChunkedByVaultBuilder<Inner> {
let process_call = process_call.clone();
async move {
// TODO: Make addresses a Map of some kind?
let (((), txs), addresses) = header.data;
let (((), txs), deposit_channels) = header.data;

let script_addresses = script_addresses(addresses);
let deposit_addresses = map_script_addresses(deposit_channels);

let deposit_witnesses = deposit_witnesses(&txs, &script_addresses);
let deposit_witnesses = deposit_witnesses(&txs, &deposit_addresses);

// Submit all deposit witnesses for the block.
if !deposit_witnesses.is_empty() {
Expand All @@ -82,19 +82,24 @@ impl<Inner: ChunkedByVault> ChunkedByVaultBuilder<Inner> {

fn deposit_witnesses(
txs: &[VerboseTransaction],
script_addresses: &HashMap<Vec<u8>, ScriptPubkey>,
deposit_addresses: &HashMap<Vec<u8>, DepositAddress>,
) -> Vec<DepositWitness<Bitcoin>> {
txs.iter()
.flat_map(|tx| {
Iterator::zip(0.., &tx.vout)
.filter(|(_vout, tx_out)| tx_out.value.to_sat() > 0)
.filter_map(|(vout, tx_out)| {
script_addresses.get(tx_out.script_pubkey.as_bytes()).map(|bitcoin_script| {
deposit_addresses.get(tx_out.script_pubkey.as_bytes()).map(|deposit_address| {
let amount = tx_out.value.to_sat();
DepositWitness::<Bitcoin> {
deposit_address: bitcoin_script.clone(),
deposit_address: deposit_address.script_pubkey(),
asset: btc::Asset::Btc,
amount: tx_out.value.to_sat(),
deposit_details: UtxoId { tx_id: tx.txid.to_byte_array().into(), vout },
amount,
deposit_details: Utxo {
id: UtxoId { tx_id: tx.txid.to_byte_array().into(), vout },
amount,
deposit_address: deposit_address.clone(),
},
}
})
})
Expand All @@ -114,15 +119,17 @@ fn deposit_witnesses(
.collect()
}

fn script_addresses(
addresses: Vec<DepositChannelDetails<state_chain_runtime::Runtime, BitcoinInstance>>,
) -> HashMap<Vec<u8>, ScriptPubkey> {
addresses
fn map_script_addresses(
deposit_channels: Vec<DepositChannelDetails<state_chain_runtime::Runtime, BitcoinInstance>>,
) -> HashMap<Vec<u8>, DepositAddress> {
deposit_channels
.into_iter()
.map(|channel| {
assert_eq!(channel.deposit_channel.asset, btc::Asset::Btc);
let deposit_address = channel.deposit_channel.state;
let script_pubkey = channel.deposit_channel.address;
(script_pubkey.bytes(), script_pubkey)

(script_pubkey.bytes(), deposit_address)
})
.collect()
}
Expand All @@ -137,10 +144,7 @@ pub mod tests {
absolute::{Height, LockTime},
Amount, ScriptBuf, Txid,
};
use cf_chains::{
btc::{deposit_address::DepositAddress, ScriptPubkey},
DepositChannel,
};
use cf_chains::{btc::deposit_address::DepositAddress, DepositChannel};
use pallet_cf_ingress_egress::{BoostStatus, ChannelAction};
use rand::{seq::SliceRandom, Rng, SeedableRng};
use sp_runtime::AccountId32;
Expand All @@ -164,7 +168,7 @@ pub mod tests {
}

fn fake_details(
address: ScriptPubkey,
deposit_address: DepositAddress,
) -> DepositChannelDetails<state_chain_runtime::Runtime, BitcoinInstance> {
use cf_chains::{btc::ScriptPubkey, ForeignChainAddress};
DepositChannelDetails::<_, BitcoinInstance> {
Expand All @@ -173,9 +177,9 @@ pub mod tests {
expires_at: 10,
deposit_channel: DepositChannel {
channel_id: 1,
address,
address: deposit_address.script_pubkey(),
asset: btc::Asset::Btc,
state: DepositAddress::new([0; 32], 1),
state: deposit_address,
},
action: ChannelAction::<AccountId32>::LiquidityProvision {
lp_account: AccountId32::new([0xab; 32]),
Expand All @@ -186,14 +190,16 @@ pub mod tests {
}
}

pub fn fake_verbose_vouts(vals_and_scripts: Vec<(u64, Vec<u8>)>) -> Vec<VerboseTxOut> {
vals_and_scripts
pub fn fake_verbose_vouts(
amounts_and_addresses: Vec<(u64, &DepositAddress)>,
) -> Vec<VerboseTxOut> {
amounts_and_addresses
.into_iter()
.enumerate()
.map(|(n, (value, script_bytes))| VerboseTxOut {
value: Amount::from_sat(value),
.map(|(n, (amount, address))| VerboseTxOut {
value: Amount::from_sat(amount),
n: n as u64,
script_pubkey: ScriptBuf::from(script_bytes),
script_pubkey: ScriptBuf::from(address.script_pubkey().bytes()),
})
.collect()
}
Expand All @@ -207,19 +213,16 @@ pub mod tests {

#[test]
fn filter_out_value_0() {
let btc_deposit_script: ScriptPubkey = DepositAddress::new([0; 32], 9).script_pubkey();
let deposit_address = DepositAddress::new([0; 32], 9);

const UTXO_WITNESSED_1: u64 = 2324;
let txs = vec![fake_transaction(
fake_verbose_vouts(vec![
(2324, btc_deposit_script.bytes()),
(0, btc_deposit_script.bytes()),
]),
fake_verbose_vouts(vec![(2324, &deposit_address), (0, &deposit_address)]),
None,
)];

let deposit_witnesses =
deposit_witnesses(&txs, &script_addresses(vec![(fake_details(btc_deposit_script))]));
deposit_witnesses(&txs, &map_script_addresses(vec![(fake_details(deposit_address))]));
assert_eq!(deposit_witnesses.len(), 1);
assert_eq!(deposit_witnesses[0].amount, UTXO_WITNESSED_1);
}
Expand All @@ -230,23 +233,23 @@ pub mod tests {
const UTXO_TO_DEPOSIT_2: u64 = 1234;
const UTXO_TO_DEPOSIT_3: u64 = 2000;

let btc_deposit_script: ScriptPubkey = DepositAddress::new([0; 32], 9).script_pubkey();
let deposit_address = DepositAddress::new([0; 32], 9);

let txs = vec![
fake_transaction(
fake_verbose_vouts(vec![
(UTXO_TO_DEPOSIT_2, btc_deposit_script.bytes()),
(12223, vec![0, 32, 121, 9]),
(LARGEST_UTXO_TO_DEPOSIT, btc_deposit_script.bytes()),
(UTXO_TO_DEPOSIT_3, btc_deposit_script.bytes()),
(UTXO_TO_DEPOSIT_2, &deposit_address),
(12223, &DepositAddress::new([0; 32], 10)),
(LARGEST_UTXO_TO_DEPOSIT, &deposit_address),
(UTXO_TO_DEPOSIT_3, &deposit_address),
]),
None,
),
fake_transaction(vec![], None),
];

let deposit_witnesses =
deposit_witnesses(&txs, &script_addresses(vec![fake_details(btc_deposit_script)]));
deposit_witnesses(&txs, &map_script_addresses(vec![fake_details(deposit_address)]));
assert_eq!(deposit_witnesses.len(), 1);
assert_eq!(deposit_witnesses[0].amount, LARGEST_UTXO_TO_DEPOSIT);
}
Expand All @@ -257,16 +260,16 @@ pub mod tests {
const UTXO_TO_DEPOSIT_2: u64 = 1234;
const UTXO_FOR_SECOND_DEPOSIT: u64 = 2000;

let btc_deposit_script_1: ScriptPubkey = DepositAddress::new([0; 32], 9).script_pubkey();
let btc_deposit_script_2: ScriptPubkey = DepositAddress::new([0; 32], 1232).script_pubkey();
let deposit_address_1 = DepositAddress::new([0; 32], 9);
let deposit_address_2 = DepositAddress::new([0; 32], 1232);

let txs = vec![
fake_transaction(
fake_verbose_vouts(vec![
(UTXO_TO_DEPOSIT_2, btc_deposit_script_1.bytes()),
(12223, vec![0, 32, 121, 9]),
(LARGEST_UTXO_TO_DEPOSIT, btc_deposit_script_1.bytes()),
(UTXO_FOR_SECOND_DEPOSIT, btc_deposit_script_2.bytes()),
(UTXO_TO_DEPOSIT_2, &deposit_address_1),
(12223, &DepositAddress::new([0; 32], 999)),
(LARGEST_UTXO_TO_DEPOSIT, &deposit_address_1),
(UTXO_FOR_SECOND_DEPOSIT, &deposit_address_2),
]),
None,
),
Expand All @@ -276,115 +279,119 @@ pub mod tests {
let deposit_witnesses = deposit_witnesses(
&txs,
// watching 2 addresses
&script_addresses(vec![
fake_details(btc_deposit_script_1.clone()),
fake_details(btc_deposit_script_2.clone()),
&map_script_addresses(vec![
fake_details(deposit_address_1.clone()),
fake_details(deposit_address_2.clone()),
]),
);

// We should have one deposit per address.
assert_eq!(deposit_witnesses.len(), 2);
assert_eq!(deposit_witnesses[0].amount, UTXO_FOR_SECOND_DEPOSIT);
assert_eq!(deposit_witnesses[0].deposit_address, btc_deposit_script_2);
assert_eq!(deposit_witnesses[0].deposit_address, deposit_address_2.script_pubkey());
assert_eq!(deposit_witnesses[1].amount, LARGEST_UTXO_TO_DEPOSIT);
assert_eq!(deposit_witnesses[1].deposit_address, btc_deposit_script_1);
assert_eq!(deposit_witnesses[1].deposit_address, deposit_address_1.script_pubkey());
}

#[test]
fn deposit_witnesses_ordering_is_consistent() {
let btc_deposit_script_1: ScriptPubkey = DepositAddress::new([0; 32], 9).script_pubkey();
let btc_deposit_script_2: ScriptPubkey = DepositAddress::new([0; 32], 1232).script_pubkey();
let address_1 = DepositAddress::new([0; 32], 9);
let address_2 = DepositAddress::new([0; 32], 1232);
DepositAddress::new([0; 32], 0);

let script_addresses = script_addresses(vec![
fake_details(btc_deposit_script_1.clone()),
fake_details(btc_deposit_script_2.clone()),
let addresses = map_script_addresses(vec![
fake_details(address_1.clone()),
fake_details(address_2.clone()),
]);

let txs: Vec<VerboseTransaction> = vec![
fake_transaction(
fake_verbose_vouts(vec![
(3, btc_deposit_script_1.bytes()),
(5, vec![3]),
(7, btc_deposit_script_1.bytes()),
(11, btc_deposit_script_2.bytes()),
(3, &address_1),
(5, &DepositAddress::new([3; 32], 0)),
(7, &address_1),
(11, &address_2),
]),
None,
),
fake_transaction(
fake_verbose_vouts(vec![
(13, btc_deposit_script_2.bytes()),
(17, btc_deposit_script_2.bytes()),
(19, vec![5]),
(23, btc_deposit_script_1.bytes()),
(13, &address_2),
(17, &address_2),
(19, &DepositAddress::new([5; 32], 0)),
(23, &address_1),
]),
None,
),
fake_transaction(
fake_verbose_vouts(vec![
(13, btc_deposit_script_2.bytes()),
(19, vec![7]),
(23, btc_deposit_script_1.bytes()),
(13, &address_2),
(19, &DepositAddress::new([7; 32], 0)),
(23, &address_1),
]),
None,
),
fake_transaction(
fake_verbose_vouts(vec![
(29, btc_deposit_script_1.bytes()),
(31, btc_deposit_script_2.bytes()),
(37, vec![11]),
(29, &address_1),
(31, &address_2),
(37, &DepositAddress::new([11; 32], 0)),
]),
None,
),
fake_transaction(
fake_verbose_vouts(vec![(41, btc_deposit_script_2.bytes()), (43, vec![17])]),
fake_verbose_vouts(vec![(41, &address_2), (43, &DepositAddress::new([17; 32], 0))]),
None,
),
fake_transaction(
fake_verbose_vouts(vec![(47, &address_1), (53, &DepositAddress::new([19; 32], 0))]),
None,
),
fake_transaction(
fake_verbose_vouts(vec![(47, btc_deposit_script_1.bytes()), (53, vec![19])]),
fake_verbose_vouts(vec![(61, &DepositAddress::new([23; 32], 0)), (59, &address_2)]),
None,
),
fake_transaction(
fake_verbose_vouts(vec![(61, vec![23]), (59, btc_deposit_script_2.bytes())]),
fake_verbose_vouts(vec![(67, &DepositAddress::new([29; 32], 0))]),
None,
),
fake_transaction(fake_verbose_vouts(vec![(67, vec![29])]), None),
];

let mut rng = rand::rngs::StdRng::from_seed([3; 32]);

for _i in 0..10 {
let n = rng.gen_range(0..txs.len());
let test_txs = txs.as_slice().choose_multiple(&mut rng, n).cloned().collect::<Vec<_>>();
assert!((0..10).map(|_| deposit_witnesses(&test_txs, &script_addresses)).all_equal());
assert!((0..10).map(|_| deposit_witnesses(&test_txs, &addresses)).all_equal());
}
}

#[test]
fn deposit_witnesses_several_diff_tx() {
let btc_deposit_script: ScriptPubkey = DepositAddress::new([0; 32], 9).script_pubkey();
let address = DepositAddress::new([0; 32], 9);

const UTXO_WITNESSED_1: u64 = 2324;
const UTXO_WITNESSED_2: u64 = 1234;
let txs = vec![
fake_transaction(
fake_verbose_vouts(vec![
(UTXO_WITNESSED_1, btc_deposit_script.bytes()),
(12223, vec![0, 32, 121, 9]),
(UTXO_WITNESSED_1 - 1, btc_deposit_script.bytes()),
(UTXO_WITNESSED_1, &address),
(12223, &DepositAddress::new([0; 32], 11)),
(UTXO_WITNESSED_1 - 1, &address),
]),
None,
),
fake_transaction(
fake_verbose_vouts(vec![
(UTXO_WITNESSED_2 - 10, btc_deposit_script.bytes()),
(UTXO_WITNESSED_2, btc_deposit_script.bytes()),
(UTXO_WITNESSED_2 - 10, &address),
(UTXO_WITNESSED_2, &address),
]),
None,
),
];

let deposit_witnesses =
deposit_witnesses(&txs, &script_addresses(vec![fake_details(btc_deposit_script)]));
deposit_witnesses(&txs, &map_script_addresses(vec![fake_details(address)]));
assert_eq!(deposit_witnesses.len(), 2);
assert_eq!(deposit_witnesses[0].amount, UTXO_WITNESSED_1);
assert_eq!(deposit_witnesses[1].amount, UTXO_WITNESSED_2);
Expand Down
Loading

0 comments on commit e7b8336

Please sign in to comment.