Skip to content

Commit

Permalink
Merge pull request #505 from sander2/spv-fix
Browse files Browse the repository at this point in the history
fix: provide coinbase proof
  • Loading branch information
gregdhill authored Jul 27, 2023
2 parents e727ac0 + 9b916a5 commit f9c1578
Show file tree
Hide file tree
Showing 24 changed files with 284 additions and 256 deletions.
187 changes: 76 additions & 111 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 1 addition & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,4 @@ sp-tracing = { git = "https://github.com/paritytech//substrate", branch = "polka
sp-trie = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" }
sp-version = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" }
sp-wasm-interface = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" }
sp-weights = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" }

[patch."https://github.com/paritytech/polkadot"]
xcm = { git = "https://github.com/paritytech//polkadot", branch = "release-v0.9.42" }
sp-weights = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" }
3 changes: 3 additions & 0 deletions bitcoin/src/electrs/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ pub enum Error {
TryFromIntError(#[from] TryFromIntError),
#[error("ParseIntError: {0}")]
ParseIntError(#[from] ParseIntError),

#[error("No txids in block")]
EmptyBlock,
}

impl Error {
Expand Down
8 changes: 8 additions & 0 deletions bitcoin/src/electrs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,14 @@ impl ElectrsClient {
Ok(txs)
}

pub(crate) async fn get_coinbase_txid(&self, block_hash: &BlockHash) -> Result<Txid, Error> {
self.get_and_decode::<Vec<String>>(&format!("/block/{block_hash}/txids"))
.await?
.first()
.ok_or(Error::EmptyBlock)
.and_then(|raw_txid| Ok(Txid::from_str(raw_txid)?))
}

pub(crate) async fn get_block(&self, hash: &BlockHash) -> Result<Block, Error> {
let (header, txdata) = try_join(self.get_block_header(hash), self.get_transactions_in_block(hash)).await?;
Ok(Block { header, txdata })
Expand Down
2 changes: 2 additions & 0 deletions bitcoin/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ pub enum Error {
FailedToConstructWalletName,
#[error("AddressError: {0}")]
AddressError(#[from] AddressError),
#[error("Failed to fetch coinbase tx")]
CoinbaseFetchingFailure,
}

impl Error {
Expand Down
2 changes: 1 addition & 1 deletion bitcoin/src/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ mod tests {
Block {
txdata: transactions.into_iter().map(dummy_tx).collect(),
header: BlockHeader {
version: Version::from_consensus(4),
version: Version::from_consensus(2),
bits: CompactTarget::from_consensus(0),
nonce: 0,
time: 0,
Expand Down
35 changes: 26 additions & 9 deletions bitcoin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,21 @@ fn get_exponential_backoff() -> ExponentialBackoff {
}
}

#[derive(PartialEq, Eq, PartialOrd, Clone, Debug)]
pub struct RawTransactionProof {
pub user_tx_proof: Vec<u8>,
pub raw_user_tx: Vec<u8>,
pub coinbase_tx_proof: Vec<u8>,
pub raw_coinbase_tx: Vec<u8>,
}

#[derive(PartialEq, Eq, PartialOrd, Clone, Copy, Debug)]
pub struct SatPerVbyte(pub u64);

#[derive(Debug, Clone)]
pub struct TransactionMetadata {
pub txid: Txid,
pub proof: Vec<u8>,
pub raw_tx: Vec<u8>,
pub proof: RawTransactionProof,
pub block_height: u32,
pub block_hash: BlockHash,
pub fee: Option<SignedAmount>,
Expand Down Expand Up @@ -885,19 +892,29 @@ impl BitcoinCoreApi for BitcoinCore {
.await?;

let proof = retry(get_exponential_backoff(), || async {
Ok(self.get_proof(txid, &block_hash).await?)
})
.await?;

let raw_tx = retry(get_exponential_backoff(), || async {
Ok(self.get_raw_tx(&txid, &block_hash).await?)
// fetch coinbase info..
let block = self.get_block(&block_hash).await?;
let coinbase_tx = block.coinbase().ok_or(Error::CoinbaseFetchingFailure)?;
let coinbase_txid = coinbase_tx.txid();
let coinbase_tx_proof = self.get_proof(coinbase_txid, &block_hash).await?;
let raw_coinbase_tx = self.get_raw_tx(&coinbase_txid, &block_hash).await?;

// fetch user tx info..
let raw_user_tx = self.get_raw_tx(&txid, &block_hash).await?;
let user_tx_proof = self.get_proof(txid, &block_hash).await?;

Ok(RawTransactionProof {
raw_coinbase_tx,
coinbase_tx_proof,
raw_user_tx,
user_tx_proof,
})
})
.await?;

Ok(TransactionMetadata {
txid,
proof,
raw_tx,
block_height,
block_hash,
fee,
Expand Down
25 changes: 21 additions & 4 deletions bitcoin/src/light/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub use error::Error;

use async_trait::async_trait;
use backoff::future::retry;
use futures::future::{join_all, try_join, try_join_all};
use futures::future::{join_all, try_join, try_join4, try_join_all};
use std::{convert::TryFrom, sync::Arc, time::Duration};
use tokio::{sync::Mutex, time::sleep};

Expand Down Expand Up @@ -82,6 +82,10 @@ impl BitcoinLight {
let txid = self.electrs.send_transaction(transaction.transaction).await?;
Ok(txid)
}

async fn get_coinbase_txid(&self, block_hash: &BlockHash) -> Result<Txid, BitcoinError> {
Ok(self.electrs.get_coinbase_txid(&block_hash).await?)
}
}

#[async_trait]
Expand Down Expand Up @@ -236,15 +240,28 @@ impl BitcoinCoreApi for BitcoinLight {
})
.await?;

let (proof, raw_tx) = retry(get_exponential_backoff(), || async {
Ok(try_join(self.get_proof(txid, &block_hash), self.get_raw_tx(&txid, &block_hash)).await?)
let proof = retry(get_exponential_backoff(), || async {
let coinbase_txid = self.get_coinbase_txid(&block_hash).await?;

let (coinbase_tx_proof, raw_coinbase_tx, user_tx_proof, raw_user_tx) = try_join4(
self.get_proof(coinbase_txid, &block_hash),
self.get_raw_tx(&coinbase_txid, &block_hash),
self.get_proof(txid, &block_hash),
self.get_raw_tx(&txid, &block_hash),
)
.await?;
Ok(RawTransactionProof {
coinbase_tx_proof,
raw_coinbase_tx,
user_tx_proof,
raw_user_tx,
})
})
.await?;

Ok(TransactionMetadata {
txid,
proof,
raw_tx,
block_height,
block_hash,
fee: Some(fee),
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: "3.8"
services:
interbtc:
image: "interlayhq/interbtc:1.25.0-rc1"
image: "interlayhq/interbtc:1.25.0-rc4"
command:
- --rpc-external
- --ws-external
Expand Down
9 changes: 5 additions & 4 deletions runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,23 @@ rand = { version = "0.7", optional = true }

[dependencies.primitives]
git = "https://github.com/interlay/interbtc"
rev = "2ec5b959a547f28d7bab7799568321c9cff40343"
rev = "1b90616692dcce55749616b4c60bdab228279909"
package = "interbtc-primitives"

[dependencies.module-bitcoin]
git = "https://github.com/interlay/interbtc"
rev = "2ec5b959a547f28d7bab7799568321c9cff40343"
rev = "1b90616692dcce55749616b4c60bdab228279909"
package = "bitcoin"
features = ["parser"]

[dependencies.module-btc-relay]
git = "https://github.com/interlay/interbtc"
rev = "2ec5b959a547f28d7bab7799568321c9cff40343"
rev = "1b90616692dcce55749616b4c60bdab228279909"
package = "btc-relay"

[dependencies.module-oracle-rpc-runtime-api]
git = "https://github.com/interlay/interbtc"
rev = "2ec5b959a547f28d7bab7799568321c9cff40343"
rev = "1b90616692dcce55749616b4c60bdab228279909"
package = "oracle-rpc-runtime-api"

[dev-dependencies]
Expand Down
Binary file modified runtime/metadata-parachain-interlay.scale
Binary file not shown.
Binary file modified runtime/metadata-parachain-kintsugi.scale
Binary file not shown.
49 changes: 40 additions & 9 deletions runtime/src/integration/bitcoin_simulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use bitcoin::{
},
secp256k1::{self, constants::SECRET_KEY_SIZE, Secp256k1, SecretKey},
serialize, Address, Amount, BitcoinCoreApi, Block, BlockHash, BlockHeader, Error as BitcoinError, GetBlockResult,
Hash, Network, OutPoint, PartialMerkleTree, PrivateKey, PublicKey, SatPerVbyte, Script, Transaction,
TransactionExt, TransactionMetadata, TxIn, TxMerkleNode, TxOut, Txid, PUBLIC_KEY_SIZE,
Hash, Network, OutPoint, PartialMerkleTree, PrivateKey, PublicKey, RawTransactionProof, SatPerVbyte, Script,
Transaction, TransactionExt, TransactionMetadata, TxIn, TxMerkleNode, TxOut, Txid, PUBLIC_KEY_SIZE,
};
use rand::{thread_rng, Rng};
use std::{convert::TryInto, sync::Arc, time::Duration};
Expand Down Expand Up @@ -54,7 +54,7 @@ impl MockBitcoinCore {
ret.parachain_rpc
.initialize_btc_relay(RawBlockHeader(raw_block_header), 0)
.await
.unwrap();
.expect("failed to initialize relay");

// submit blocks in order to prevent the WaitingForRelayerInitialization error in request_issue
let headers = futures::future::join_all((0..7u32).map(|_| ret.generate_block_with_transaction(&dummy_tx)))
Expand Down Expand Up @@ -192,13 +192,28 @@ impl MockBitcoinCore {
fn generate_coinbase_transaction(address: &BtcAddress, reward: u64, height: u32) -> Transaction {
let address = ScriptBuf::from(address.to_script_pub_key().as_bytes().to_vec());

// construct height: see https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki
// first byte is number of bytes in the number (will be 0x03 on main net for the next
// 150 or so years with 223-1 blocks), following bytes are little-endian representation
// of the number (including a sign bit)
let mut height_bytes = height.to_le_bytes().to_vec();
for i in (1..4).rev() {
// remove trailing zeroes, but always keep first byte even if it's zero
if height_bytes[i] == 0 {
height_bytes.remove(i);
} else {
break;
}
}
height_bytes.insert(0, height_bytes.len() as u8);

// note that we set lock_time to height, otherwise we might generate blocks with
// identical block hashes
Transaction {
input: vec![TxIn {
previous_output: OutPoint::null(), // coinbase
witness: Witness::from_slice::<&[u8]>(&[]),
script_sig: Default::default(),
script_sig: ScriptBuf::from(height_bytes),
sequence: Sequence(u32::max_value()),
}],
output: vec![TxOut {
Expand Down Expand Up @@ -360,7 +375,16 @@ impl BitcoinCoreApi for MockBitcoinCore {

// part two: info about the transactions (we assume the txid is at index 1)
let txids = block.txdata.iter().map(|x| x.txid()).collect::<Vec<_>>();
let partial_merkle_tree = PartialMerkleTree::from_txids(&txids, &[false, true]);
assert_eq!(txids.len(), 2); // expect coinbase and user tx

let partial_merkle_tree = if txids[0] == txid {
PartialMerkleTree::from_txids(&txids, &[true, false])
} else if txids[1] == txid {
PartialMerkleTree::from_txids(&txids, &[false, true])
} else {
panic!("txid not in block")
};

proof.append(&mut serialize(&partial_merkle_tree));

Ok(proof)
Expand Down Expand Up @@ -441,13 +465,20 @@ impl BitcoinCoreApi for MockBitcoinCore {
tokio::time::sleep(Duration::from_secs(1)).await;
};
let block_hash = block.block_hash();
let proof = self.get_proof(txid, &block_hash).await.unwrap();
let raw_tx = self.get_raw_tx(&txid, &block_hash).await.unwrap();
let coinbase_txid = block.coinbase().unwrap().txid();
let coinbase_tx_proof = self.get_proof(coinbase_txid, &block_hash).await.unwrap();
let raw_coinbase_tx = self.get_raw_tx(&coinbase_txid, &block_hash).await.unwrap();
let user_tx_proof = self.get_proof(txid, &block_hash).await.unwrap();
let raw_user_tx = self.get_raw_tx(&txid, &block_hash).await.unwrap();

Ok(TransactionMetadata {
block_hash,
proof,
raw_tx,
proof: RawTransactionProof {
user_tx_proof,
raw_user_tx,
coinbase_tx_proof,
raw_coinbase_tx,
},
txid,
block_height: block_height as u32,
fee: None,
Expand Down
3 changes: 2 additions & 1 deletion runtime/src/integration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub use bitcoin_simulator::MockBitcoinCore;
pub async fn default_root_provider(key: AccountKeyring) -> (InterBtcParachain, TempDir) {
let tmp = TempDir::new("btc-parachain-").expect("failed to create tempdir");
let root_provider = setup_provider(key).await;

try_join(
root_provider.set_bitcoin_confirmations(1),
root_provider.set_parachain_confirmations(1),
Expand Down Expand Up @@ -81,7 +82,7 @@ pub async fn assert_issue(
.unwrap();

parachain_rpc
.execute_issue(*issue.issue_id, &metadata.proof, &metadata.raw_tx)
.execute_issue(*issue.issue_id, &metadata.proof)
.await
.unwrap();
}
Expand Down
12 changes: 8 additions & 4 deletions runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ pub const DISABLE_DIFFICULTY_CHECK: &str = "DisableDifficultyCheck";
),
derive_for_type(path = "interbtc_primitives::VaultCurrencyPair", derive = "Eq, PartialEq"),
derive_for_type(path = "interbtc_primitives::VaultId", derive = "Eq, PartialEq"),
derive_for_type(path = "security::types::ErrorCode", derive = "Eq, PartialEq, Ord, PartialOrd"),
derive_for_type(path = "security::types::StatusCode", derive = "Eq, PartialEq"),
substitute_type(path = "primitive_types::H256", with = "::subxt::utils::Static<crate::H256>"),
substitute_type(path = "primitive_types::U256", with = "::subxt::utils::Static<crate::U256>"),
substitute_type(path = "primitive_types::H160", with = "::subxt::utils::Static<crate::H160>"),
Expand Down Expand Up @@ -106,6 +104,10 @@ pub const DISABLE_DIFFICULTY_CHECK: &str = "DisableDifficultyCheck";
path = "bitcoin::types::Transaction",
with = "::subxt::utils::Static<::module_bitcoin::types::Transaction>"
),
substitute_type(
path = "bitcoin::types::FullTransactionProof",
with = "::subxt::utils::Static<::module_bitcoin::types::FullTransactionProof>"
),
)
)]
#[cfg_attr(
Expand All @@ -123,8 +125,6 @@ pub const DISABLE_DIFFICULTY_CHECK: &str = "DisableDifficultyCheck";
),
derive_for_type(path = "interbtc_primitives::VaultCurrencyPair", derive = "Eq, PartialEq"),
derive_for_type(path = "interbtc_primitives::VaultId", derive = "Eq, PartialEq"),
derive_for_type(path = "security::types::ErrorCode", derive = "Eq, PartialEq, Ord, PartialOrd"),
derive_for_type(path = "security::types::StatusCode", derive = "Eq, PartialEq"),
substitute_type(path = "primitive_types::H256", with = "::subxt::utils::Static<crate::H256>"),
substitute_type(path = "primitive_types::U256", with = "::subxt::utils::Static<crate::U256>"),
substitute_type(path = "primitive_types::H160", with = "::subxt::utils::Static<crate::H160>"),
Expand Down Expand Up @@ -158,6 +158,10 @@ pub const DISABLE_DIFFICULTY_CHECK: &str = "DisableDifficultyCheck";
path = "bitcoin::types::Transaction",
with = "::subxt::utils::Static<::module_bitcoin::types::Transaction>"
),
substitute_type(
path = "bitcoin::types::FullTransactionProof",
with = "::subxt::utils::Static<::module_bitcoin::types::FullTransactionProof>"
),
)
)]

Expand Down
Loading

0 comments on commit f9c1578

Please sign in to comment.