Skip to content

Commit

Permalink
Add the happy path for the new
Browse files Browse the repository at this point in the history
breed of integration tests.
  • Loading branch information
djordon committed Sep 27, 2024
1 parent 8ad6474 commit 9e235c5
Showing 1 changed file with 164 additions and 19 deletions.
183 changes: 164 additions & 19 deletions signer/tests/integration/complete_deposit.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
use std::sync::atomic::Ordering;

use bitcoin::consensus::Encodable as _;
use bitcoin::hashes::Hash as _;
use bitcoin::AddressType;
use bitcoin::BlockHash;
use bitcoin::OutPoint;
use bitcoincore_rpc::Client;
use bitcoincore_rpc::RpcApi as _;
use blockstack_lib::types::chainstate::StacksAddress;

use clarity::vm::types::PrincipalData;
use rand::rngs::OsRng;
use rand::SeedableRng;

use sbtc::testing::regtest;
use sbtc::testing::regtest::Faucet;
use sbtc::testing::regtest::Recipient;
use signer::bitcoin::utxo;
Expand All @@ -23,6 +27,7 @@ use signer::stacks::contracts::DepositErrorMsg;
use signer::stacks::contracts::ReqContext;
use signer::storage::model;
use signer::storage::model::BitcoinBlock;
use signer::storage::model::BitcoinBlockRef;
use signer::storage::model::BitcoinTxRef;
use signer::storage::model::StacksPrincipal;
use signer::storage::postgres::PgStore;
Expand All @@ -31,21 +36,20 @@ use signer::storage::DbWrite as _;
use signer::testing;
use signer::testing::dummy::SweepTxConfig;
use signer::testing::storage::model::TestData;
use signer::testing::TestSignerContext;

use fake::Fake;
use rand::SeedableRng;
use signer::testing::TestSignerContext;

use crate::utxo_construction::make_deposit_request;
use crate::DATABASE_NUM;

fn as_deposit(value: utxo::DepositRequest, recipient: StacksPrincipal) -> model::DepositRequest {
fn as_deposit(value: utxo::DepositRequest, recipient: PrincipalData) -> model::DepositRequest {
model::DepositRequest {
txid: value.outpoint.txid.into(),
output_index: value.outpoint.vout,
spend_script: value.deposit_script.into(),
reclaim_script: value.reclaim_script.into(),
recipient,
recipient: recipient.into(),
amount: value.amount,
max_fee: value.max_fee,
sender_script_pub_keys: Vec::new(),
Expand Down Expand Up @@ -104,6 +108,59 @@ fn make_complete_deposit(
(complete_deposit_tx, req_ctx)
}

/// Create a "proper" [`CompleteDepositV1`] object and context with the
/// given information. If the information here is correct then the returned
/// [`CompleteDepositV1`] object will pass validation with the given
/// context.
fn make_complete_deposit2(data: &TestDepositInfo) -> (CompleteDepositV1, ReqContext) {
// Okay now we get ready to create the transaction using the
// `CompleteDepositV1` type.
let complete_deposit_tx = CompleteDepositV1 {
// This OutPoint points to the deposit UTXO.
outpoint: data.request.outpoint,
// This amount must not exceed the amount in the deposit request.
amount: data.request.amount - data.tx_fee,
// The recipient must match what was indicated in the deposit
// request.
recipient: data.recipient.clone(),
// The deployer must match what is in the signers' context.
deployer: StacksAddress::burn_address(false),
// The sweep transaction ID must point to a transaction on
// the canonical bitcoin blockchain.
sweep_txid: data.sweep_tx.compute_txid().into(),
// The block hash of the block that includes the above sweep
// transaction. It must be on the canonical bitcoin blockchain.
sweep_block_hash: data.sweep_block_hash.into(),
// This must be the height of the above block.
sweep_block_height: data.sweep_block_height,
};

// This is what the current signer things of the state of things.
let req_ctx = ReqContext {
chain_tip: BitcoinBlockRef {
block_hash: data.sweep_block_hash.into(),
block_height: data.sweep_block_height,
},
// This value means that the signer will go back 10 blocks when
// looking for pending and accepted deposit requests.
context_window: 10,
// The value here doesn't matter.
origin: fake::Faker.fake_with_rng(&mut OsRng),
// When checking whether the transaction is from the signer, we
// check that the first "prevout" has a `scriptPubKey` that the
// signers control.
aggregate_key: data.signer.keypair.public_key().into(),
// This value affects how many deposit transactions are consider
// accepted.
signatures_required: 2,
// This is who the current signer thinks deployed the sBTC
// contracts.
deployer: StacksAddress::burn_address(false),
};

(complete_deposit_tx, req_ctx)
}

/// Generate a signer set, deposit requests and store them into the
/// database.
async fn deposit_setup<R>(rng: &mut R, db: &PgStore) -> Vec<PublicKey>
Expand Down Expand Up @@ -134,9 +191,9 @@ where
/// sweep transaction was signed with the `signer` field's public key.
struct TestDepositInfo {
deposit_block_hash: bitcoin::BlockHash,
deposit_block_height: u64,
request: model::DepositRequest,
deposit: bitcoin::Transaction,
recipient: PrincipalData,
request: utxo::DepositRequest,
deposit_tx: bitcoin::Transaction,
tx_fee: u64,
sweep_tx: bitcoin::Transaction,
sweep_block_hash: bitcoin::BlockHash,
Expand Down Expand Up @@ -196,31 +253,43 @@ fn setup_sweep_tx(rpc: &Client, faucet: &Faucet, amount: u64) -> TestDepositInfo
// Add the signature and/or other required information to the witness data.
signer::testing::set_witness_data(&mut unsigned, signer.keypair);
rpc.send_raw_transaction(&unsigned.tx).unwrap();
(unsigned.tx.clone(), unsigned.tx_fee)
(unsigned.tx, unsigned.tx_fee)
};

// Let's sweep in the transaction
let sweep_block_hash = faucet.generate_blocks(1).pop().unwrap();

let recipient = PrincipalData::from(StacksAddress::burn_address(false)).into();
let deposit_request = requests.deposits.pop().unwrap();
let sweep_block_height = rpc.get_block_header_info(&sweep_block_hash).unwrap().height as u64;

TestDepositInfo {
deposit_block_height: rpc
.get_block_header_info(&deposit_block_hash)
.unwrap()
.height as u64,
deposit_block_hash,
request: as_deposit(deposit_request, recipient),
deposit: deposit_tx,
request: requests.deposits.pop().unwrap(),
recipient: PrincipalData::from(StacksAddress::burn_address(false)),
deposit_tx,
sweep_tx,
sweep_block_height: rpc.get_block_header_info(&sweep_block_hash).unwrap().height as u64,
sweep_block_height,
sweep_block_hash,
tx_fee,
signer,
}
}

async fn backfill_bitcoin_blocks(db: &PgStore, rpc: &Client, chain_tip: &BlockHash) {
let mut block_header = rpc.get_block_header_info(&chain_tip).unwrap();
while block_header.height > 100 {
let parent_header_hash = block_header.previous_block_hash.unwrap();
let bitcoin_block = model::BitcoinBlock {
block_hash: block_header.hash.into(),
block_height: block_header.height as u64,
parent_hash: parent_header_hash.into(),
confirms: Vec::new(),
};

db.write_bitcoin_block(&bitcoin_block).await.unwrap();
block_header = rpc.get_block_header_info(&parent_header_hash).unwrap();
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
}
}

/// Get the full block
async fn get_bitcoin_canonical_chain_tip_block(db: &PgStore) -> BitcoinBlock {
sqlx::query_as::<_, BitcoinBlock>(
Expand Down Expand Up @@ -262,6 +331,82 @@ async fn get_pending_accepted_deposit_requests(
.unwrap()
}

#[cfg_attr(not(feature = "integration-tests"), ignore)]
#[tokio::test]
async fn complete_deposit_validation_happy_path2() {
let db_num = DATABASE_NUM.fetch_add(1, Ordering::SeqCst);
let db = testing::storage::new_test_database(db_num, true).await;
let mut rng = rand::rngs::StdRng::seed_from_u64(51);

let (rpc, faucet) = regtest::initialize_blockchain();

let case = setup_sweep_tx(&rpc, &faucet, 1_000_000);

backfill_bitcoin_blocks(&db, rpc, &case.sweep_block_hash).await;

let mut tx = Vec::new();
case.sweep_tx.consensus_encode(&mut tx).unwrap();

let sweep_tx = model::Transaction {
tx,
txid: case.sweep_tx.compute_txid().to_byte_array(),
tx_type: model::TransactionType::SbtcTransaction,
block_hash: case.sweep_block_hash.to_byte_array(),
};

let bitcoin_tx_ref = BitcoinTxRef {
txid: sweep_tx.txid.into(),
block_hash: sweep_tx.block_hash.into(),
};

db.write_transaction(&sweep_tx).await.unwrap();
db.write_bitcoin_transaction(&bitcoin_tx_ref).await.unwrap();

let mut tx = Vec::new();
case.deposit_tx.consensus_encode(&mut tx).unwrap();

let deposit_tx = model::Transaction {
tx,
txid: case.deposit_tx.compute_txid().to_byte_array(),
tx_type: model::TransactionType::SbtcTransaction,
block_hash: case.deposit_block_hash.to_byte_array(),
};

let bitcoin_tx_ref = BitcoinTxRef {
txid: deposit_tx.txid.into(),
block_hash: deposit_tx.block_hash.into(),
};

db.write_transaction(&deposit_tx).await.unwrap();
db.write_bitcoin_transaction(&bitcoin_tx_ref).await.unwrap();

let signer_keys = testing::wallet::create_signers_keys(&mut rng, &case.signer, 7);
let deposit_signers: Vec<model::DepositSigner> = signer_keys
.iter()
.copied()
.map(|signer_pub_key| model::DepositSigner {
txid: case.request.outpoint.txid.into(),
output_index: case.request.outpoint.vout,
signer_pub_key,
is_accepted: true,
})
.collect();

let deposit_request = as_deposit(case.request.clone(), case.recipient.clone());
db.write_deposit_request(&deposit_request).await.unwrap();
for decision in deposit_signers {
db.write_deposit_signer_decision(&decision).await.unwrap();
}

let (complete_deposit_tx, req_ctx) = make_complete_deposit2(&case);

let ctx = TestSignerContext::from_db(db.clone());

complete_deposit_tx.validate(&ctx, &req_ctx).await.unwrap();

testing::storage::drop_db(db).await;
}

/// For this test we check that the `CompleteDepositV1::validate` function
/// returns okay when everything matches the way that it is supposed to.
#[cfg_attr(not(feature = "integration-tests"), ignore)]
Expand Down

0 comments on commit 9e235c5

Please sign in to comment.