Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add stub for Bitcoin headers endpoint #297

Merged
merged 9 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
20 changes: 18 additions & 2 deletions canister/candid.did
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ type address = text;

type block_hash = blob;

type block_header = blob;

type block_height = nat32;

type outpoint = record {
txid : blob;
vout : nat32;
Expand All @@ -18,7 +22,7 @@ type outpoint = record {
type utxo = record {
outpoint : outpoint;
value : satoshi;
height : nat32;
height : block_height;
};

type flag = variant {
Expand Down Expand Up @@ -68,7 +72,7 @@ type get_utxos_request = record {
type get_utxos_response = record {
utxos : vec utxo;
tip_block_hash : block_hash;
tip_height : nat32;
tip_height : block_height;
next_page : opt blob;
};

Expand All @@ -93,6 +97,16 @@ type set_config_request = record {
burn_cycles : opt flag;
};

type get_block_headers_request = record {
start_height : block_height;
end_height : opt block_height;
};

type get_block_headers_response = record {
tip_height : block_height;
block_headers : vec block_header;
};

service bitcoin : (config) -> {
bitcoin_get_balance : (get_balance_request) -> (satoshi);

Expand All @@ -104,6 +118,8 @@ service bitcoin : (config) -> {

bitcoin_get_current_fee_percentiles : (get_current_fee_percentiles_request) -> (vec millisatoshi_per_byte);

bitcoin_get_block_headers : (get_block_headers_request) -> (get_block_headers_response);

bitcoin_send_transaction : (send_transaction_request) -> ();

get_config : () -> (config) query;
Expand Down
2 changes: 2 additions & 0 deletions canister/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
mod fee_percentiles;
mod get_balance;
mod get_block_headers;
mod get_utxos;
mod metrics;
mod send_transaction;
mod set_config;
pub use fee_percentiles::get_current_fee_percentiles;
pub use get_balance::get_balance;
pub use get_balance::get_balance_query;
pub use get_block_headers::get_block_headers;
pub use get_utxos::get_utxos;
pub use get_utxos::get_utxos_query;
pub use metrics::get_metrics;
Expand Down
75 changes: 75 additions & 0 deletions canister/src/api/get_block_headers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use ic_btc_interface::{GetBlockHeadersError, GetBlockHeadersRequest, GetBlockHeadersResponse};

// Various profiling stats for tracking the performance of `get_block_headers`.
#[derive(Default, Debug)]
struct Stats {
dragoljub-duric marked this conversation as resolved.
Show resolved Hide resolved
// The total number of instructions used to process the request.
_ins_total: u64,

// The number of instructions used to build the block headers vec.
_ins_build_block_headers_vec: u64,
}

fn verify_get_block_headers_request(
request: GetBlockHeadersRequest,
) -> Result<(), GetBlockHeadersError> {
if let Some(end_height) = request.end_height {
if end_height < request.start_height {
return Err(GetBlockHeadersError::StartHeightLagerThanEndHeight {
start_height: request.start_height,
end_height,
});
}
}
Ok(())
}

/// Given a start height and an optional end height from request,
/// the function returns the block headers in the provided range.
/// The range is inclusive, i.e., the block headers at the start
/// and end heights are returned as well.

/// If no end height is specified, all blocks until the tip height,
/// i.e., the largest available height, are returned. However, if
/// the range from the start height to the end height or the tip
/// height is large, only a prefix of the requested block headers
/// may be returned in order to bound the size of the response.
pub fn get_block_headers(
request: GetBlockHeadersRequest,
) -> Result<GetBlockHeadersResponse, GetBlockHeadersError> {
verify_get_block_headers_request(request)?;
unimplemented!("get_block_headers is not implemented")
}

#[cfg(test)]
mod test {
use ic_btc_interface::{Config, GetBlockHeadersError, GetBlockHeadersRequest, Network};

use crate::api::get_block_headers;

#[test]
fn get_block_headers_malformed_heights() {
ielashi marked this conversation as resolved.
Show resolved Hide resolved
crate::init(Config {
stability_threshold: 1,
network: Network::Mainnet,
..Default::default()
});

let start_height = 3;
let end_height = 2;

let err = get_block_headers(GetBlockHeadersRequest {
start_height,
end_height: Some(end_height),
})
.unwrap_err();

assert_eq!(
err,
GetBlockHeadersError::StartHeightLagerThanEndHeight {
start_height,
end_height,
}
);
}
}
29 changes: 27 additions & 2 deletions canister/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ pub use api::send_transaction;
pub use api::set_config;
pub use heartbeat::heartbeat;
use ic_btc_interface::{
Config, Flag, GetBalanceError, GetBalanceRequest, GetCurrentFeePercentilesRequest,
GetUtxosError, GetUtxosRequest, GetUtxosResponse, MillisatoshiPerByte, Network, Satoshi,
Config, Flag, GetBalanceError, GetBalanceRequest, GetBlockHeadersError, GetBlockHeadersRequest,
GetBlockHeadersResponse, GetCurrentFeePercentilesRequest, GetUtxosError, GetUtxosRequest,
GetUtxosResponse, MillisatoshiPerByte, Network, Satoshi,
};
use ic_btc_types::Block;
use ic_stable_structures::Memory;
Expand Down Expand Up @@ -134,6 +135,14 @@ pub fn get_utxos_query(request: GetUtxosRequest) -> Result<GetUtxosResponse, Get
api::get_utxos_query(request.into())
}

pub fn get_block_headers(
request: GetBlockHeadersRequest,
) -> Result<GetBlockHeadersResponse, GetBlockHeadersError> {
verify_api_access();
verify_synced();
api::get_block_headers(request)
}

pub fn get_config() -> Config {
with_state(|s| Config {
stability_threshold: s.unstable_blocks.stability_threshold() as u128,
Expand Down Expand Up @@ -484,6 +493,22 @@ mod test {
.unwrap();
}

#[test]
#[should_panic(expected = "Bitcoin API is disabled")]
fn get_block_headers_access_disabled() {
init(Config {
stability_threshold: 0,
network: Network::Mainnet,
api_access: Flag::Disabled,
..Default::default()
});
get_block_headers(GetBlockHeadersRequest {
start_height: 3,
end_height: None,
})
.unwrap();
}

#[test]
#[should_panic(expected = "Bitcoin API is disabled")]
fn get_utxos_query_access_disabled() {
Expand Down
12 changes: 10 additions & 2 deletions canister/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use ic_btc_canister::types::{HttpRequest, HttpResponse};
use ic_btc_interface::{
Config, GetBalanceRequest, GetCurrentFeePercentilesRequest, GetUtxosRequest,
MillisatoshiPerByte, SendTransactionRequest, SetConfigRequest,
Config, GetBalanceRequest, GetBlockHeadersRequest, GetCurrentFeePercentilesRequest,
GetUtxosRequest, MillisatoshiPerByte, SendTransactionRequest, SetConfigRequest,
};
use ic_cdk::api::call::{reject, reply};
use ic_cdk_macros::{heartbeat, init, inspect_message, post_upgrade, pre_upgrade, query, update};
Expand Down Expand Up @@ -76,6 +76,14 @@ pub fn bitcoin_get_utxos_query(request: GetUtxosRequest) {
};
}

#[update(manual_reply = true)]
pub fn bitcoin_get_block_headers(request: GetBlockHeadersRequest) {
ielashi marked this conversation as resolved.
Show resolved Hide resolved
match ic_btc_canister::get_block_headers(request) {
Ok(response) => reply((response,)),
Err(e) => reject(format!("get_block_headers failed: {:?}", e).as_str()),
};
}

#[update(manual_reply = true)]
async fn bitcoin_send_transaction(request: SendTransactionRequest) {
match ic_btc_canister::send_transaction(request).await {
Expand Down
12 changes: 12 additions & 0 deletions e2e-tests/disable-api-if-not-fully-synced-flag.sh
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,18 @@ if ! [[ $MSG = *"Canister state is not fully synced."* ]]; then
exit 1
fi

# bitcoin_get_block_headers should panic.
set +e
MSG=$(dfx canister call bitcoin bitcoin_get_block_headers '(record {
start_height = 0;
})' 2>&1);
set -e

if ! [[ $MSG = *"Canister state is not fully synced."* ]]; then
echo "FAIL"
exit 1
fi

# bitcoin_get_utxos_query should panic.
set +e
MSG=$(dfx canister call --query bitcoin bitcoin_get_utxos_query '(record {
Expand Down
71 changes: 69 additions & 2 deletions interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub type MillisatoshiPerByte = u64;
pub type BlockHash = Vec<u8>;
pub type Height = u32;
pub type Page = ByteBuf;
pub type BlockHeader = Vec<u8>;

#[derive(CandidType, Clone, Copy, Deserialize, Debug, Eq, PartialEq, Serialize, Hash)]
pub enum Network {
Expand Down Expand Up @@ -272,7 +273,7 @@ pub struct OutPoint {
pub struct Utxo {
pub outpoint: OutPoint,
pub value: Satoshi,
pub height: u32,
pub height: Height,
}

impl std::cmp::PartialOrd for Utxo {
Expand Down Expand Up @@ -333,7 +334,7 @@ pub struct GetUtxosRequest {
pub struct GetUtxosResponse {
pub utxos: Vec<Utxo>,
pub tip_block_hash: BlockHash,
pub tip_height: u32,
pub tip_height: Height,
pub next_page: Option<Page>,
}

Expand All @@ -346,6 +347,72 @@ pub enum GetUtxosError {
MalformedPage { err: String },
}

/// A request for getting the block headers from a given height.
#[derive(CandidType, Debug, Deserialize, PartialEq, Eq)]
pub struct GetBlockHeadersRequest {
pub start_height: Height,
pub end_height: Option<Height>,
}

/// The response returned for a request for getting the block headers from a given height.
#[derive(CandidType, Debug, Deserialize, PartialEq, Eq, Clone)]
pub struct GetBlockHeadersResponse {
pub tip_height: Height,
pub block_headers: Vec<BlockHeader>,
}

/// Errors when processing a `get_block_headers` request.
#[derive(CandidType, Debug, Deserialize, PartialEq, Eq, Clone)]
pub enum GetBlockHeadersError {
ielashi marked this conversation as resolved.
Show resolved Hide resolved
StartHeightDoesNotExist {
requested: Height,
chain_height: Height,
},
EndHeightDoesNotExist {
requested: Height,
chain_height: Height,
},
StartHeightLagerThanEndHeight {
start_height: Height,
end_height: Height,
},
}

impl fmt::Display for GetBlockHeadersError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::StartHeightDoesNotExist {
requested,
chain_height,
} => {
write!(
f,
"The requested start_height is larger than the height of the chain. Requested: {}, height of chain: {}",
requested, chain_height
)
}
Self::EndHeightDoesNotExist {
requested,
chain_height,
} => {
write!(
f,
"The requested start_height is larger than the height of the chain. Requested: {}, height of chain: {}",
requested, chain_height
)
}
Self::StartHeightLagerThanEndHeight {
start_height,
end_height,
} => {
write!(
f,
"The requested start_height is larger than the requested end_height. start_height: {}, end_height: {}", start_height, end_height)
}
}
}
}

/// A request for getting the current fee percentiles.
#[derive(CandidType, Debug, Deserialize, PartialEq, Eq)]
pub struct GetCurrentFeePercentilesRequest {
Expand Down
Loading