Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,13 @@ jobs:
target/debug/deps
target/debug/build
key: test-cache-${{ github.run_id }}-${{ github.run_number }}
# Not --all-features: that turns on ddk-manager's `fuzztarget`, which
# replaces the contract id and serial id generators with constants. A test
# that creates a second contract, as every splice does, then collides with
# the first. The features named here are the ones the integration tests
# actually want.
- id: set-matrix
run: cargo test --no-run --all-features && echo "matrix=$(testconfig/scripts/get_test_list.sh manager_execution manager_tests contract_updater stateless_execution)" >> "$GITHUB_OUTPUT"
run: cargo test --no-run --features ddk-manager/parallel,ddk-manager/use-serde && echo "matrix=$(testconfig/scripts/get_test_list.sh manager_execution manager_tests contract_updater stateless_execution)" >> "$GITHUB_OUTPUT"
integration_tests:
name: integration tests
needs: integration_tests_prepare
Expand All @@ -120,15 +125,8 @@ jobs:
target/debug/deps
target/debug/build
key: test-cache-${{ github.run_id }}-${{ github.run_number }}
# --exact, because a matrix entry names one test. Without it the name is
# a substring filter, and a job for `enum_single_oracle_test` would also
# run `splice_in_enum_single_oracle_test`.
- name: Run test
run: RUST_BACKTRACE=1 RUST_MIN_STACK=8388608 ${{ matrix.tests }} --ignored

test_splicing:
name: test splicing
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- name: Run test
run: RUST_MIN_STACK=8388608 cargo test -p ddk-manager splice -- --nocapture --ignored
run: RUST_BACKTRACE=1 ${{ matrix.tests }} --ignored --exact
36 changes: 28 additions & 8 deletions ddk-manager/src/contract_updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -910,23 +910,41 @@ where
// from the offer party is their half of the DLC input and we can build the valid redeem script.
if let Some(dlc_input) = &funding_input.dlc_input {
let dlc_input_info: DlcInputInfo = funding_input.into();

// The two keys are named for the roles in the contract being
// spliced, not for the roles in the splice. Either party may offer
// the splice, so resolve which key is ours before using the other
// one to verify.
let own_fund_pubkey = crate::dlc_input::get_fund_pubkey_for_dlc_input(
secp,
&dlc_input.contract_id,
storage,
signer_provider,
)
.await?;
let counter_party_fund_pubkey = if dlc_input.local_fund_pubkey == own_fund_pubkey {
dlc_input.remote_fund_pubkey
} else {
dlc_input.local_fund_pubkey
};

log_debug!(
logger,
"Verifying DLC input signature. contract_id={} input_index={} remote_fund_pubkey={} local_fund_pubkey={}",
"Verifying DLC input signature. contract_id={} input_index={} own_fund_pubkey={} counter_party_fund_pubkey={}",
accepted_contract.get_contract_id_string(),
input_index,
dlc_input.remote_fund_pubkey.to_string(),
dlc_input.local_fund_pubkey.to_string(),
own_fund_pubkey.to_string(),
counter_party_fund_pubkey.to_string(),
);

// Verify the signature from the offer party is valid for the DLC input.
// Verify the signature from the party that offered the splice.
ddk_dlc::dlc_input::verify_dlc_funding_input_signature(
secp,
fund_tx,
input_index,
&dlc_input_info,
funding_signatures.witness_elements[0].witness.clone(),
&dlc_input.local_fund_pubkey,
&counter_party_fund_pubkey,
)?;

log_debug!(
Expand All @@ -947,13 +965,15 @@ where
)
.await?;

// Build the redeem script for the DLC input.
// Build the redeem script for the DLC input. The witness orders the
// two signatures by public key, so both keys have to be the ones
// that actually produced them.
let completed_witness = ddk_dlc::dlc_input::combine_dlc_input_signatures(
&dlc_input_info,
&my_dlc_input_signature,
&funding_signatures.witness_elements[0].witness,
&dlc_input.remote_fund_pubkey,
&dlc_input.local_fund_pubkey,
&own_fund_pubkey,
&counter_party_fund_pubkey,
);

log_debug!(
Expand Down
36 changes: 36 additions & 0 deletions ddk-manager/src/dlc_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,42 @@ use crate::{
contract::Contract, error::Error, ContractId, ContractSigner, ContractSignerProvider, Storage,
};

/// The funding public key this node holds in the contract a DLC input spends.
///
/// A [`DlcInputInfo`] names the two keys of the 2-of-2 it spends in the order
/// the spliced contract had them: `local_fund_pubkey` belongs to whoever
/// offered that contract and `remote_fund_pubkey` to whoever accepted it.
/// Either party can offer the splice, so which of the two is ours has to be
/// resolved against our own key rather than assumed from the splice roles.
pub async fn get_fund_pubkey_for_dlc_input<S: Deref, X: ContractSigner, SP: Deref>(
secp: &Secp256k1<All>,
contract_id: &ContractId,
storage: &S,
signer_provider: &SP,
) -> Result<secp256k1_zkp::PublicKey, Error>
where
S::Target: Storage,
SP::Target: ContractSignerProvider<Signer = X>,
{
let contract = storage
.get_contract(contract_id)
.await?
.ok_or(Error::StorageError(
"Contract not found to resolve DLC input keys.".to_string(),
))?;

let keys_id = match contract {
Contract::Confirmed(c) => Ok(c.accepted_contract.offered_contract.keys_id),
_ => Err(Error::InvalidState(
"Contract must be confirmed to resolve DLC input keys.".to_string(),
)),
}?;

signer_provider
.derive_contract_signer(keys_id)?
.get_public_key(secp)
}

// todo: definitely test
/// Get the DlcInputInfo from FundingInputs
pub fn get_dlc_inputs_from_funding_inputs(funding_inputs: &[FundingInput]) -> Vec<DlcInputInfo> {
Expand Down
Loading
Loading