-
Notifications
You must be signed in to change notification settings - Fork 83
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Tests] Include BTC-Relay integration tests See merge request interlay/btc-parachain!118
- Loading branch information
Showing
10 changed files
with
1,754 additions
and
36 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
use serde::Deserialize; | ||
use std::fs; | ||
use std::path::PathBuf; | ||
|
||
const ERR_FILE_NOT_FOUND: &'static str = "Testdata not found. Please run the python script under the parachain/scripts folder to obtain bitcoin blocks and transactions."; | ||
const ERR_JSON_FORMAT: &'static str = "JSON was not well-formatted"; | ||
|
||
#[derive(Clone, Debug, Deserialize)] | ||
pub struct Block { | ||
pub height: u32, | ||
pub hash: String, | ||
pub raw_header: String, | ||
pub test_txs: Vec<Transaction>, | ||
} | ||
|
||
#[derive(Clone, Debug, Deserialize)] | ||
pub struct Transaction { | ||
pub txid: String, | ||
pub raw_merkle_proof: String, | ||
} | ||
|
||
pub fn get_bitcoin_testdata() -> Vec<Block> { | ||
let path_str = String::from("./tests/data/bitcoin-testdata.json"); | ||
let path = PathBuf::from(&path_str); | ||
let abs_path = fs::canonicalize(&path).unwrap(); | ||
let debug_help = abs_path.as_path().to_str().unwrap(); | ||
|
||
let error_message = "\n".to_owned() + ERR_FILE_NOT_FOUND + "\n" + debug_help; | ||
|
||
let data = fs::read_to_string(&path_str).expect(&error_message); | ||
|
||
let test_data: Vec<Block> = serde_json::from_str(&data).expect(ERR_JSON_FORMAT); | ||
|
||
test_data | ||
} |
1,502 changes: 1,502 additions & 0 deletions
1,502
parachain/runtime/tests/data/bitcoin-testdata.json
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
mod bitcoin_data; | ||
mod mock; | ||
|
||
use bitcoin_data::get_bitcoin_testdata; | ||
use mock::*; | ||
|
||
#[test] | ||
fn integration_test_submit_block_headers_and_verify_transaction_inclusion() { | ||
ExtBuilder::build().execute_with(|| { | ||
// load blocks with transactions | ||
let test_data = get_bitcoin_testdata(); | ||
|
||
let mut init = false; | ||
// store all block headers | ||
for block in test_data.iter() { | ||
let raw_header = RawBlockHeader::from_hex(&block.raw_header).unwrap(); | ||
if init == false { | ||
assert_ok!(BTCRelayCall::initialize(raw_header, block.height) | ||
.dispatch(origin_of(account_of(ALICE)))); | ||
init = true; | ||
} else { | ||
assert_ok!(BTCRelayCall::store_block_header(raw_header) | ||
.dispatch(origin_of(account_of(ALICE)))); | ||
|
||
assert_store_main_chain_header_event( | ||
block.height, | ||
H256Le::from_hex_be(&block.hash), | ||
); | ||
} | ||
} | ||
// verify all transaction that have enough confirmations | ||
let current_height = btc_relay::Module::<Runtime>::get_best_block_height(); | ||
for block in test_data.iter() { | ||
if block.height < current_height - CONFIRMATIONS { | ||
for tx in &block.test_txs { | ||
let txid = H256Le::from_hex_be(&tx.txid); | ||
let raw_merkle_proof = | ||
hex::decode(&tx.raw_merkle_proof).expect("Error parsing merkle proof"); | ||
assert_ok!(BTCRelayCall::verify_transaction_inclusion( | ||
txid, | ||
block.height, | ||
raw_merkle_proof, | ||
CONFIRMATIONS, | ||
false | ||
) | ||
.dispatch(origin_of(account_of(ALICE)))); | ||
} | ||
} | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import requests | ||
import json | ||
import random | ||
import os | ||
|
||
DIRNAME = os.path.dirname(__file__) | ||
TESTDATA_DIR = os.path.join(DIRNAME, "..", "runtime", "tests", "data") | ||
TESTDATA_FILE = os.path.join(TESTDATA_DIR, "bitcoin-testdata.json") | ||
BASE_URL = "https://blockstream.info/api" | ||
|
||
def query(uri): | ||
url = BASE_URL + uri | ||
response = requests.get(url) | ||
|
||
if (response.ok): | ||
return response | ||
else: | ||
response.raise_for_status() | ||
|
||
def query_text(uri): | ||
response = query(uri) | ||
return response.text | ||
|
||
def query_json(uri): | ||
response = query(uri) | ||
return response.json() | ||
|
||
def query_binary(uri): | ||
url = BASE_URL + uri | ||
|
||
with requests.get(url, stream=True) as response: | ||
if (response.ok): | ||
# hacky way to get only the 80 block header bytes | ||
# the raw block heade endpoint gives the block header | ||
# plus the number of txs and the raw txs | ||
# see https://github.com/Blockstream/esplora/issues/171 | ||
if '/block/' in url: | ||
raw_header = response.raw.read(80) | ||
assert(len(raw_header) == 80) | ||
return raw_header.hex() | ||
else: | ||
return response.content.decode('utf-8') | ||
else: | ||
response.raise_for_status() | ||
|
||
def get_tip_height(): | ||
uri = "/blocks/tip/height" | ||
return query_json(uri) | ||
|
||
def get_raw_header(blockhash): | ||
uri = "/block/{}/raw".format(blockhash) | ||
return query_binary(uri) | ||
|
||
def get_block_hash(height): | ||
uri = "/block-height/{}".format(height) | ||
return query_text(uri) | ||
|
||
def get_block_txids(blockhash): | ||
uri = "/block/{}/txids".format(blockhash) | ||
return query_json(uri) | ||
|
||
def get_raw_merkle_proof(txid): | ||
uri = "/tx/{}/merkleblock-proof".format(txid) | ||
return query_binary(uri) | ||
|
||
def get_testdata(number, tip_height): | ||
# query number of blocks | ||
blocks = [] | ||
for i in range(tip_height - number, tip_height): | ||
blockhash = get_block_hash(i) | ||
print("Getting block at height {} with hash {}".format(i, blockhash)) | ||
raw_header = get_raw_header(blockhash) | ||
# get the txids in the block | ||
txids = get_block_txids(blockhash) | ||
# select two txids randomly for testing | ||
test_txids = random.sample(txids, 2) | ||
test_txs = [] | ||
# get the tx merkle proof | ||
for txid in test_txids: | ||
raw_merkle_proof = get_raw_merkle_proof(txid) | ||
tx = { | ||
'txid': txid, | ||
'raw_merkle_proof': raw_merkle_proof, | ||
} | ||
test_txs.append(tx) | ||
|
||
block = { | ||
'height': i, | ||
'hash': blockhash, | ||
'raw_header': raw_header, | ||
'test_txs': test_txs, | ||
} | ||
blocks.append(block) | ||
return blocks | ||
|
||
def overwrite_testdata(blocks): | ||
with open(TESTDATA_FILE, 'w', encoding='utf-8') as f: | ||
json.dump(blocks, f, ensure_ascii=False, indent=4) | ||
|
||
def read_testdata(): | ||
blocks = [] | ||
try: | ||
with open(TESTDATA_FILE) as data: | ||
blocks = json.load(data) | ||
except: | ||
print("No existing testdata found") | ||
return blocks | ||
|
||
def main(): | ||
max_num_blocks = 100 | ||
number_blocks = max_num_blocks | ||
# get current tip of Bitcoin blockchain | ||
tip_height = get_tip_height() | ||
blocks = read_testdata() | ||
if blocks: | ||
if blocks[-1]['height'] == tip_height: | ||
print("Latest blocks already downloaded") | ||
return | ||
else: | ||
## download new blocks | ||
delta = tip_height - blocks[-1]["height"] | ||
number_blocks = delta if delta <= max_num_blocks else max_num_blocks | ||
|
||
new_blocks = get_testdata(number_blocks, tip_height) | ||
blocks = blocks + new_blocks | ||
# only store max_num_blocks | ||
blocks = blocks[-max_num_blocks:] | ||
overwrite_testdata(blocks) | ||
|
||
if __name__ == "__main__": | ||
main() |