From 34cc917fd10e4c5d8104579786ec59f67bb85ffe Mon Sep 17 00:00:00 2001 From: Michael Zaikin Date: Mon, 2 Sep 2024 20:14:00 +0100 Subject: [PATCH] Light client pipeline --- .gitignore | 2 + README.md | 37 ++-- Scarb.toml | 7 +- scripts/data/felt_writer.py | 75 -------- scripts/data/format_args.py | 109 ++++++++++++ scripts/data/generate_data.py | 222 ++++++++++++++++++++++++ scripts/data/get_block.py | 1 - scripts/data/get_blocks.sh | 43 ----- scripts/data/integration_tests.sh | 59 +++++++ scripts/data/light_client.sh | 18 +- scripts/data/light_client_args_170.json | 34 ---- scripts/data/regenerate_tests.sh | 34 ++++ scripts/data/requirements.txt | 12 +- src/lib.cairo | 5 + src/main.cairo | 25 ++- src/test.cairo | 40 +++++ tests/blocks.cairo | 1 - tests/blocks/block_170.cairo | 97 ----------- tests/data/light_150012.json | 56 ++++++ tests/data/light_169.json | 56 ++++++ tests/data/light_209999.json | 56 ++++++ tests/data/light_24834.json | 56 ++++++ tests/data/light_478557.json | 56 ++++++ tests/data/light_481823.json | 56 ++++++ tests/data/light_491406.json | 56 ++++++ tests/data/light_57042.json | 56 ++++++ tests/data/light_629999.json | 56 ++++++ tests/data/light_709631.json | 56 ++++++ tests/data/light_757738.json | 56 ++++++ tests/data/light_757752.json | 56 ++++++ tests/data/light_774627.json | 56 ++++++ tests/data/light_839999.json | 56 ++++++ tests/lib.cairo | 3 - tests/light_client.cairo | 3 - tests/light_client/block_170.cairo | 75 -------- tests/tests.cairo | 131 -------------- 36 files changed, 1311 insertions(+), 506 deletions(-) delete mode 100644 scripts/data/felt_writer.py create mode 100644 scripts/data/format_args.py create mode 100755 scripts/data/generate_data.py delete mode 100755 scripts/data/get_blocks.sh create mode 100755 scripts/data/integration_tests.sh mode change 100755 => 100644 scripts/data/light_client.sh delete mode 100644 scripts/data/light_client_args_170.json create mode 100755 scripts/data/regenerate_tests.sh create mode 100644 src/test.cairo delete mode 100644 tests/blocks.cairo delete mode 100644 tests/blocks/block_170.cairo create mode 100644 tests/data/light_150012.json create mode 100644 tests/data/light_169.json create mode 100644 tests/data/light_209999.json create mode 100644 tests/data/light_24834.json create mode 100644 tests/data/light_478557.json create mode 100644 tests/data/light_481823.json create mode 100644 tests/data/light_491406.json create mode 100644 tests/data/light_57042.json create mode 100644 tests/data/light_629999.json create mode 100644 tests/data/light_709631.json create mode 100644 tests/data/light_757738.json create mode 100644 tests/data/light_757752.json create mode 100644 tests/data/light_774627.json create mode 100644 tests/data/light_839999.json delete mode 100644 tests/lib.cairo delete mode 100644 tests/light_client.cairo delete mode 100644 tests/light_client/block_170.cairo delete mode 100644 tests/tests.cairo diff --git a/.gitignore b/.gitignore index 345c585e..42efa844 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ Cargo.lock .venv .python-version __pycache__ + +.light_client \ No newline at end of file diff --git a/README.md b/README.md index 910808c7..c70fd175 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,8 @@ Tasks: * [x] block time validation/computation * [x] block difficulty adjustment * [x] script for fetching arbitrary block data -* [ ] script for preparing program arguments -* [ ] script for running the program e2e for multiple block +* [x] script for preparing program arguments +* [x] script for running the program e2e for multiple blocks ### Milestone 2 - Partial transaction validation @@ -71,7 +71,7 @@ Extend light client with partial transaction validation, but without UTXO checks Tasks: * [ ] reassess validation check list (analyze Bitcoin core codebase) -* [ ] generate & run integration tests e2e instead of Cairo codegen +* [x] generate & run integration tests e2e instead of Cairo codegen * [x] transaction ID calculation * [x] transaction root computation * [x] validate transaction fee @@ -135,29 +135,42 @@ This will compile all the components: ```bash scarb build - ``` -This will run the test-suite: +This will run unit and integration tests: ```bash scarb test ``` -Re-generate test data: +For integration tests ony: + +```bash +scarb run integration_tests +``` + +Run for specific test file(s): + +```bash +scarb run integration_tests tests/data/light_481823.json +``` + +Re-generate integration test data: ```base -scarb run get_blocks -scarb run get_block -scarb run get_block_py +scarb run regenerate_tests ``` -* File will be created in [tests/blocks/](https://github.com/keep-starknet-strange/raito/blob/main/tests/blocks)block_\.cairo -* If you want to modify the blockHash for get_blocks change [scripts/data/get_blocks.sh](https://github.com/keep-starknet-strange/raito/blob/main/scripts/data/get_blocks.sh#L11) +* Files will be created in [tests/data/](https://github.com/keep-starknet-strange/raito/blob/main/tests/data) +* If you want to add a new test case, edit [scripts/data/regenerate_tests.sh](https://github.com/keep-starknet-strange/raito/blob/main/scripts/data/regenerate_tests.sh) ## Build dependencies -* ```get_blocks``` and ```get_block_py``` need [that Python dependencies](https://github.com/keep-starknet-strange/raito/tree/main/scripts/data/requirements.txt) +Install necessary packages required by Python scripts: + +```bash +pip install -r scripts/data/requirements.txt +``` ## References diff --git a/Scarb.toml b/Scarb.toml index e1457801..498f2c43 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -4,9 +4,10 @@ version = "0.1.0" edition = "2024_07" [scripts] -get_block= "python3 ./scripts/data/get_block.py" -get_blocks= "./scripts/data/get_blocks.sh" -light_client= "./scripts/data/light_client.sh" +regenerate_tests= "./scripts/data/regenerate_tests.sh" +integration_tests = "scarb build && ./scripts/data/integration_tests.sh" +light_client= "scarb build && ./scripts/data/light_client.sh" +test = "scarb cairo-test && scarb run integration_tests" [dependencies] diff --git a/scripts/data/felt_writer.py b/scripts/data/felt_writer.py deleted file mode 100644 index 5fb79b44..00000000 --- a/scripts/data/felt_writer.py +++ /dev/null @@ -1,75 +0,0 @@ -# This module provides helpers for serializing Python objects into Cairo runner arguments (felts and arrays). -# -# Supported types: -# int (unsigned) -> felt252 -# [0-9] str -> u256 which is (felt252, felt252) -# 0x str (reversed) -> Hash which is [u32; 8] which is a tuple of felt252 -# bytes -> ByteArray which is (Array, felt252, felt252) -# list -> Array -# dict -> (V, V, ...) -import sys -import os -import json -from pathlib import Path - -def serialize(obj): - if isinstance(obj, int): - # This covers u8, u16, u32, u64, u128, felt252 - assert(obj >= 0 and obj < 2 ** 252) - return obj - elif isinstance(obj, str): - if obj.startswith('0x') and len(obj) == 66: - # Hex string into 4-byte words then into BE u32 - rev = list(reversed(bytes.fromhex(obj[2:]))) - return tuple(int.from_bytes(rev[i:i+4], 'big') for i in range(0, 32, 4)) - else: - # Try to cast to int and then to a pair of felts - num = int(obj) - assert(num >= 0 and num < 2 ** 256) - lo = num % 2 ** 128 - hi = num // 2 ** 128 - return (lo, hi) - elif isinstance(obj, bytes): - # Split into 31-byte chunks and save the remainder - num_chunks = len(obj) // 31 - main_len = num_chunks * 31 - rem_len = len(obj) - main_len - main = [int.from_bytes(obj[i:i+31], 'big') for i in range(0, main_len, 31)] - # TODO: check if this is correct - rem = int.from_bytes(obj[main_len:].rjust(31, b'\x00'), 'big') - return (main, rem, rem_len) - elif isinstance(obj, list): - return list(map(serialize, obj)) - elif isinstance(obj, dict): - return tuple(map(serialize, obj.values())) - elif isinstance(obj, tuple): - return obj - else: - raise NotImplementedError(obj) - -def flatten_tuples(src): - res = [] - def iter_obj(obj): - if isinstance(obj, int): - res.append(obj) - elif isinstance(obj, list): - res.append(obj) - elif isinstance(obj, tuple): - for item in obj: - iter_obj(item) - else: - raise NotImplementedError(obj) - iter_obj(src) - return res - -def main(): - try: - if (len(sys.argv) != 2): - raise TypeError("Error: bad arguments") - args = json.loads(Path(sys.argv[1]).read_text()) - res = flatten_tuples(serialize(args)) - print(res) - except Exception as error: - print(error) - -main() diff --git a/scripts/data/format_args.py b/scripts/data/format_args.py new file mode 100644 index 00000000..3369a480 --- /dev/null +++ b/scripts/data/format_args.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python + +import sys +import json +from pathlib import Path + + +def serialize(obj): + """Serializes Cairo data in JSON format to a Python object with reduced types. + Supports the following conversions: + integer -> int # felt252 + dec string (0-9) -> (int, int) -> u256 = { lo: felt252, hi: felt252 } + hex string (0-F), 64 len -> (int, int, int, int, int, int, int, int) -> Hash !reversed! + hex string 0x prefixed -> ([int, ...], int, int) -> ByteArray + list -> [] + dict -> tuple(dict.values) + """ + if isinstance(obj, int): + # This covers u8, u16, u32, u64, u128, felt252 + assert(obj >= 0 and obj < 2 ** 252) + return obj + elif isinstance(obj, str): + if obj.isdigit(): + # Try to cast to int and then to low/high parts + num = int(obj) + assert(num >= 0 and num < 2 ** 256) + lo = num % 2 ** 128 + hi = num // 2 ** 128 + return (lo, hi) + elif obj.startswith('0x'): + # Split into 31-byte chunks and save the remainder + src = bytes.fromhex(obj[2:]) + num_chunks = len(src) // 31 + main_len = num_chunks * 31 + rem_len = len(src) - main_len + main = [int.from_bytes(src[i:i+31], 'big') for i in range(0, main_len, 31)] + # TODO: check if this is how byte31 is implemented + rem = int.from_bytes(src[main_len:].rjust(31, b'\x00'), 'big') + return (main, rem, rem_len) + else: + # Reversed hex string into 4-byte words then into BE u32 + assert(len(obj) == 64) + rev = list(reversed(bytes.fromhex(obj))) + return tuple(int.from_bytes(rev[i:i+4], 'big') for i in range(0, 32, 4)) + elif isinstance(obj, list): + return list(map(serialize, obj)) + elif isinstance(obj, dict): + return tuple(map(serialize, obj.values())) + elif isinstance(obj, tuple): + return obj + else: + raise NotImplementedError(obj) + + +def flatten_tuples(src): + """Recursively flattens tuples. + Example: (0, (1, 2), [(3, 4, [5, 6])]) -> [0, 1, 2, [3, 4, [5, 6]]] + + :param src: an object that can be int|list|tuple or their nested combination. + :return: an object that can only contain integers and lists, top-level tuple converts to a list. + """ + res = [] + def append_obj(obj, to): + if isinstance(obj, int): + to.append(obj) + elif isinstance(obj, list): + inner = [] + for item in obj: + append_obj(item, inner) + to.append(inner) + elif isinstance(obj, tuple): + for item in obj: + append_obj(item, to) + else: + raise NotImplementedError(obj) + append_obj(src, res) + return res + + +def format_cairo1_run(args: list) -> str: + """Formats arguments for usage with cairo1-run. + Example: [0, 1, [2, 3, 4]] -> "0 1 [2 3 4]" + + :param args: Python object containing already processed arguments. + :return: Removes outer array brackets [] and commas, returns string. + """ + def format_item(item, root=False): + if isinstance(item, list): + arr = " ".join(map(format_item, item)) + return arr if root else f'[{arr}]' + else: + return str(item) + return format_item(args, root=True) + + +def format_args(): + """Reads arguments from JSON file and prints formatted result. + Expects a single CLI argument containing file path. + Output is compatible with the Scarb runner arguments format. + """ + if (len(sys.argv) != 2): + raise TypeError("Expected single argument") + args = json.loads(Path(sys.argv[1]).read_text()) + res = flatten_tuples(serialize(args)) + print(res) + + +if __name__ == '__main__': + format_args() diff --git a/scripts/data/generate_data.py b/scripts/data/generate_data.py new file mode 100755 index 00000000..968ccd95 --- /dev/null +++ b/scripts/data/generate_data.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python + +import sys +import os +import json +import requests +from pathlib import Path + +BITCOIN_RPC = os.getenv("BITCOIN_RPC") +USERPWD = os.getenv("USERPWD") +DEFAULT_URL = "https://bitcoin-mainnet.public.blastapi.io" + + +def request_rpc(method: str, params: list): + """Makes a JSON-RPC call to a Bitcoin API endpoint. + Uses environment variables BITCOIN_RPC and USERPWD + or the default public endpoint if those variables are not set. + + :return: parsed JSON result as Python object + """ + url = BITCOIN_RPC or DEFAULT_URL + auth = USERPWD.split(":") if USERPWD else None + headers = {'content-type': 'application/json'} + payload = { + "jsonrpc": "2.0", + "method": method, + "params": params, + "id": 0, + } + data = requests.post(url, auth=auth, headers=headers, json=payload).json() + if data['result'] is None: + raise ConnectionError("RPC response is null") + return data['result'] + + +def fetch_chain_state(block_height: int): + """Fetches chain state at the end of a specific block with given height. + Chain state is a just a block header extended with extra fields: + - prev_timestamps + - epoch_start_time + """ + # Chain state at height H is the state after applying block H + block_hash = request_rpc("getblockhash", [block_height]) + head = request_rpc("getblockheader", [block_hash]) + + # In order to init prev_timestamps we need to query 10 previous headers + prev_header = head + prev_timestamps = [head['time']] + for _ in range(10): + if prev_header['height'] == 0: + prev_timestamps.insert(0, 0) + else: + prev_header = request_rpc("getblockheader", [prev_header['previousblockhash']]) + prev_timestamps.insert(0, prev_header['time']) + head['prev_timestamps'] = prev_timestamps + + # In order to init epoch start we need to query block header at epoch start + if block_height < 2016: + head['epoch_start_time'] = 1231006505 + else: + head['epoch_start_time'] = get_epoch_start_time(block_height) + + return head + + +def next_chain_state(head: dict, blocks: list): + """Computes resulting chain state given the initial chain state + and all blocks that were applied to it. + """ + block_height = head['height'] + len(blocks) + next_head = blocks[-1] + + # We need to recalculate the prev_timestamps field given the previous chain state + # and all the blocks we applied to it + prev_timestamps = head['prev_timestamps'] + list(map(lambda x: x['time'], blocks)) + next_head['prev_timestamps'] = prev_timestamps[-11:] + + # Update epoch start time if neccesary + if head['height'] // 2016 != block_height // 2016: + next_head['epoch_start_time'] = get_epoch_start_time(block_height) + else: + next_head['epoch_start_time'] = head['epoch_start_time'] + return next_head + + +def get_epoch_start_time(block_height: int) -> int: + """Computes the corresponding epoch start time given the current block height. + """ + epoch_start_block_height = (block_height // 2016) * 2016 + epoch_start_block_hash = request_rpc("getblockhash", [epoch_start_block_height]) + epoch_start_header = request_rpc("getblockheader", [epoch_start_block_hash]) + return epoch_start_header['time'] + + +def format_chain_state(head: dict): + """Formats chain state according to the respective Cairo type. + """ + return { + "block_height": head['height'], + "total_work": str(int.from_bytes(bytes.fromhex(head['chainwork']), 'big')), + "best_block_hash": head['hash'], + "current_target": str(bits_to_target(head['bits'])), + "epoch_start_time": head['epoch_start_time'], + "prev_timestamps": head['prev_timestamps'], + } + + +def bits_to_target(bits: str) -> int: + """Convert difficulty bits (compact target representation) to target. + + :param bits: bits as a hex string (without 0x prefix) + :return: target as integer + """ + exponent = int.from_bytes(bytes.fromhex(bits[:2]), 'big') + mantissa = int.from_bytes(bytes.fromhex(bits[2:]), 'big') + if exponent == 0: + return mantissa + elif exponent <=3: + return mantissa >> (8 * (3 - exponent)) + else: + return mantissa << (8 * (exponent - 3)) + + +def fetch_block(block_hash: str): + """Downloads block with transactions (and referred UTXOs) from RPC given the block hash. + """ + # TODO + pass + + +def format_block_with_transactions(block: dict): + """Formats block with transactions according to the respective Cairo type. + """ + # TODO + pass + + +def fetch_block_header(block_hash: str): + """Downloads block header (without trasnasction) from RPC given the block hash. + """ + return request_rpc("getblockheader", [block_hash]) + + +def format_block(header: dict): + """Formats block (without transactions) according to the respective Cairo type. + Note that transaction data uses a verbose format to include information + about the particular enum variant. + + :param header: block header obtained from RPC + """ + return { + "header": format_header(header), + "data": { + "variant_id": 0, + "merkle_root": header['merkleroot'] + } + } + + +def format_header(header: dict): + """Formats header according to the respective Cairo type. + + :param header: block header obtained from RPC + """ + return { + "version": header['version'], + "time": header['time'], + "bits": int.from_bytes(bytes.fromhex(header['bits']), 'big'), + "nonce": header['nonce'] + } + + +def generate_data(mode: str, initial_height: int, num_blocks: int, include_expected: bool): + """Generates arguments for Raito program in a human readable form and the expected result. + + :param mode: Validation mode: + "light" — generate block headers with Merkle root only + "full" — generate full blocks with transactions (and referenced UTXOs) + :param initial_height: The block height of the initial chain state (0 means the state after genesis) + :param num_blocks: The number of blocks to apply on top of it (has to be at least 1) + :return: tuple (arguments, expected output) + """ + chain_state = fetch_chain_state(initial_height) + next_block_hash = chain_state['nextblockhash'] + blocks = [] + + for _ in range(num_blocks): + if mode == 'light': + block = fetch_block_header(next_block_hash) + elif mode == 'full': + block = fetch_block(next_block_hash) + else: + raise NotImplementedError(mode) + next_block_hash = block['nextblockhash'] + blocks.append(block) + + block_formatter = format_block if mode == 'light' else format_block_with_transactions + result = { + "chain_state": format_chain_state(chain_state), + "blocks": list(map(block_formatter, blocks)), + } + + if include_expected: + result['expected'] = format_chain_state(next_chain_state(chain_state, blocks)) + + return result + + +# Usage: generate_data.py MODE INITIAL_HEIGHT NUM_BLOCKS INCLUDE_EXPECTED OUTPUT_FILE +# Example: generate_data.py 'light' 0 10 false light_0_10.json +if __name__ == '__main__': + if (len(sys.argv) != 6): + raise TypeError("Expected five arguments") + + data = generate_data( + mode=sys.argv[1], + initial_height=int(sys.argv[2]), + num_blocks=int(sys.argv[3]), + include_expected=sys.argv[4].lower() == "true", + ) + + Path(sys.argv[5]).write_text(json.dumps(data, indent=2)) diff --git a/scripts/data/get_block.py b/scripts/data/get_block.py index dcb508f2..37a6b23b 100755 --- a/scripts/data/get_block.py +++ b/scripts/data/get_block.py @@ -123,7 +123,6 @@ def check_segwit(tx): return "true" return "false" - def txs_process(txsraw, ntx): payload = '' bar = progressbar.ProgressBar(max_value=ntx) diff --git a/scripts/data/get_blocks.sh b/scripts/data/get_blocks.sh deleted file mode 100755 index 6e12b0ea..00000000 --- a/scripts/data/get_blocks.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash -set -e; -set -o pipefail; - -if [ -f .env ] -then - export $(cat .env | xargs) -fi - -get_single_block() { - local block_hash=$1 - python3 ./scripts/data/get_block.py "$block_hash" -} - -main() { - local block_hashes=( - "00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee" # Block containing first P2P tx to Hal Finney (170) - "00000000132fbe8314fc571c0be60b31ccd461c9ee85f42bde8c6d160a9dacc0" # Block containing first off ramp tx from Martti Malmi (24835) - "00000000152340ca42227603908689183edc47355204e7aca59383b0aaac1fd8" # Block containing pizza tx (57043) - "000000000000011f9791dcfdfe0e402b79a165a3b781bafcc918b6f2166d577c" # Small Block (150013) - "000000000000048b95347e83192f69cf0366076336c639f9b7228e9ba171342e" # First halving block (210000) - # "000000000000000002cce816c0ab2c5c269cb081896b7dcb34b8422d6b74ffa1" # Second halving block (420000) - # "0000000000000000011865af4122fe3b144e2cbeea86142e8ff2fb4107352d43" # Bitcoin Cash hard fork block (478558) - # "0000000000000000001c8018d9cb3b742ef25114f27563e3fc4a1902167f9893" # Segwit soft fork block (481824) - # "000000000000000000e5438564434edaf41e63829a637521a96235adf4653e1b" # Bitcoin Gold hard fork block (491407) - # "000000000000000000024bead8df69990852c202db0e0097c1a12ea637d7e96d" # Third halving block (630000) - # "0000000000000000000687bca986194dc2c1f949318629b44bb54ec0a94d8244" # Taproot soft fort block (709632) - "00000000000000000002601c74946371bd1bf00ad3154f011c20abad1cabd0ea" # Block with witness (757739) - "000000000000000000032c781dbe11459fba50312acfca3cd96fa2bc4367d5b1" # Block with witnesses, 81txs (757753) - # "0000000000000000000515e202c8ae73c8155fc472422d7593af87aa74f2cf3d" # Biggest block in Bitcoin history - Taproot Wizards (774628) - # "0000000000000000000320283a032748cef8227873ff4872689bf23f1cda83a5" # Fourth halving block (840000) - ) - - # Loop through the block hashes and call get_block.sh for each - for block_hash in "${block_hashes[@]}"; do - echo "Getting block: $block_hash" - get_single_block "$block_hash" - done - - echo "All blocks retrieved successfully." -} - -main diff --git a/scripts/data/integration_tests.sh b/scripts/data/integration_tests.sh new file mode 100755 index 00000000..4feba3f2 --- /dev/null +++ b/scripts/data/integration_tests.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +set -e; +set -o pipefail; + +GREEN='\033[0;32m' +RED='\033[1;31m' +RESET='\033[0m' # No Color + +num_ok=0 +num_fail=0 +num_ignored=0 +failures=() +test_files="tests/data"/* + +# TODO: fix bugs +ignored_files=( + "tests/data/light_481823.json" + "tests/data/light_709631.json" +) +ignored="${ignored_files[@]}" + +if [ $# -gt 0 ]; then + args=("$@") + test_files="${args[@]}" +fi + +for test_file in $test_files; do + if [ -f "$test_file" ]; then + echo -n "test e2e:$test_file ..." + + if [[ "$ignored" =~ "$test_file" ]]; then + echo " ignored" + num_ignored=$((num_ignored + 1)) + else + arguments=$(python scripts/data/format_args.py ${test_file}) + output=$(scarb cairo-run --no-build --function test "$arguments") + gas_spent=$(echo $output | grep -o 'gas_spent=[0-9]*' | sed 's/gas_spent=//') + + if [[ "$output" == *"OK"* ]]; then + echo -e "${GREEN} ok ${RESET}(gas usage est.: $gas_spent)" + num_ok=$((num_ok + 1)) + else + echo -e "${RED} fail ${RESET}(gas usage est.: $gas_spent)" + num_fail=$((num_fail + 1)) + error=$(echo $output | grep -o "error='[^']*'" | sed "s/error=//") + failures+="\te2e:$test_file — Panicked with $error\n" + fi + fi + fi +done + +if [[ $num_fail == 0 ]]; then + echo -e "test result: ${GREEN}ok${RESET}. ${num_ok} passed; 0 failed; ${num_ignored} ignored" +else + echo -e "failures:\n$failures" + echo -e "test result: ${RED}FAILED${RESET}. ${num_ok} passed; ${num_fail} failed; ${num_ignored} ignored" + false +fi \ No newline at end of file diff --git a/scripts/data/light_client.sh b/scripts/data/light_client.sh old mode 100755 new mode 100644 index 7a116a32..cf547574 --- a/scripts/data/light_client.sh +++ b/scripts/data/light_client.sh @@ -1,8 +1,20 @@ #!/usr/bin/env bash + set -e; set -o pipefail; -INPUT_FILE="./scripts/data/light_client_args_170.json" -ARGUMENTS=$(python ./scripts/data/felt_writer.py "$INPUT_FILE") +batch_size=50 + +mkdir .light_client || true -scarb cairo-run "$ARGUMENTS" \ No newline at end of file +for (( height=0; height<=1000; height+=batch_size )); do + echo "Validating blocks $height — $((height+batch_size))" + batch_file=.light_client/${height}_${batch_size}.json + if [ ! -f "$batch_file" ]; then + echo "Downloading blocks" + python scripts/data/generate_data.py "light" $height $batch_size false $batch_file + fi + echo "Running client" + arguments=$(python scripts/data/format_args.py $batch_file) + scarb cairo-run --no-build "$arguments" +done diff --git a/scripts/data/light_client_args_170.json b/scripts/data/light_client_args_170.json deleted file mode 100644 index 41737c5c..00000000 --- a/scripts/data/light_client_args_170.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "chain_state": { - "block_height": 169, - "total_work": "730155581610", - "best_block_hash": "0x000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55", - "current_target": "26959535291011309493156476344723991336010898738574164086137773096960", - "epoch_start_time": 1231006505, - "prev_timestamps": [ - 1231702618, - 1231703466, - 1231704197, - 1231704464, - 1231714917, - 1231715347, - 1231716245, - 1231716329, - 1231716711, - 1231717181, - 1231730523 - ] - }, - "block": { - "header": { - "version": 1, - "time": 1231731025, - "bits": 486604799, - "nonce": 1889418792 - }, - "data": { - "variant_id": 0, - "merkle_root": "0x7dac2c5666815c17a3b36427de37bb9d2e2c5ccec3f8633eb91a4205cb4c10ff" - } - } -} \ No newline at end of file diff --git a/scripts/data/regenerate_tests.sh b/scripts/data/regenerate_tests.sh new file mode 100755 index 00000000..2cf13b38 --- /dev/null +++ b/scripts/data/regenerate_tests.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +set -e; +set -o pipefail; + +if [ -f .env ] +then + export $(cat .env | xargs) +fi + +test_cases=( + 169 # Block containing first P2P tx to Hal Finney (170) + 24834 # Block containing first off ramp tx from Martti Malmi (24835) + 57042 # Block containing pizza tx (57043) + 150012 # Small Block (150013) + 209999 # First halving block (210000) + 478557 # Bitcoin Cash hard fork block (478558) + 481823 # Segwit soft fork block (481824) + 491406 # Bitcoin Gold hard fork block (491407) + 629999 # Third halving block (630000) + 709631 # Taproot soft fort block (709632) + 757738 # Block with witness (757739) + 757752 # Block with witnesses, 81txs (757753) + 774627 # Biggest block in Bitcoin history - Taproot Wizards (774628) + 839999 # Fourth halving block (840000) +) + +mkdir tests/data || true + +# Loop through the test cases and generate data +for test_case in "${test_cases[@]}"; do + echo "Generating test data: light mode, chain state @ $test_case, single block" + python scripts/data/generate_data.py "light" $test_case 1 true tests/data/light_${test_case}.json +done diff --git a/scripts/data/requirements.txt b/scripts/data/requirements.txt index f95bd23f..ef487e06 100644 --- a/scripts/data/requirements.txt +++ b/scripts/data/requirements.txt @@ -1,11 +1 @@ -certifi==2024.7.4 -charset-normalizer==3.3.2 -idna==3.7 -importjson==0.2.3 -progressbar2==4.4.2 -python-utils==3.8.2 -requests==2.32.3 -six==1.16.0 -templatelite==0.2.1.1 -typing_extensions==4.12.2 -urllib3==2.2.2 +requests==2.32.3 \ No newline at end of file diff --git a/src/lib.cairo b/src/lib.cairo index 51eaf5f8..0915c59a 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -26,3 +26,8 @@ pub mod types { } mod main; + +// TODO: move this module to a separate package +// Scarb does not support features when using cairo-run +// neither it allows to run function from the "tests" folder +mod test; diff --git a/src/main.cairo b/src/main.cairo index 8ab5dd73..fd4cf98e 100644 --- a/src/main.cairo +++ b/src/main.cairo @@ -1,17 +1,14 @@ -use raito::types::block::Block; -use raito::types::chain_state::{ChainState, BlockValidator}; +use crate::types::block::Block; +use crate::types::chain_state::{ChainState, BlockValidator}; -/// Raito light client program entrypoint. +/// Raito program entrypoint. /// -/// Receives current chain state and a pending block header. -/// Returns (true, new chain state) if validation was successfull. -/// Returns (false, default) otherwise. -fn main(chain_state: ChainState, block: Block) -> (bool, ChainState) { - match chain_state.validate_and_apply(block) { - Result::Ok(res) => (true, res), - Result::Err(err) => { - println!("{}", err); - (false, Default::default()) - } - } +/// Receives current chain state and pending blocks, +/// then validates and applies them one by one. +/// Returns new chain state in case of succes, otherwise raises an error. +fn main(mut chain_state: ChainState, mut blocks: Array) -> ChainState { + while let Option::Some(block) = blocks.pop_front() { + chain_state = chain_state.validate_and_apply(block).expect('Validation failed'); + }; + chain_state } diff --git a/src/test.cairo b/src/test.cairo new file mode 100644 index 00000000..46bc53c7 --- /dev/null +++ b/src/test.cairo @@ -0,0 +1,40 @@ +use crate::types::block::Block; +use crate::types::chain_state::{ChainState, BlockValidator}; +use core::testing::get_available_gas; + +/// Integration testing program entrypoint. +/// +/// Receives current chain state, pending blocks, and expected result. +/// Validates and applies blocks one by one, exits on first failure. +fn test( + mut chain_state: ChainState, mut blocks: Array, mut expected_chain_state: ChainState +) { + let mut gas_before = get_available_gas(); + while let Option::Some(block) = blocks.pop_front() { + let height = chain_state.block_height + 1; + match chain_state.validate_and_apply(block) { + Result::Ok(new_chain_state) => { + chain_state = new_chain_state; + let gas_after = get_available_gas(); + println!("OK: block={} gas_spent={}", height, gas_before - gas_after); + gas_before = gas_after; + }, + Result::Err(err) => { + let gas_after = get_available_gas(); + println!( + "FAIL: block={} gas_spent={} error='{}'", height, gas_before - gas_after, err + ); + panic!(); + } + } + }; + if chain_state != expected_chain_state { + println!( + "FAIL: block={} error='expected state {:?}, actual {:?}'", + chain_state.block_height, + expected_chain_state, + chain_state + ); + panic!(); + } +} diff --git a/tests/blocks.cairo b/tests/blocks.cairo deleted file mode 100644 index 3845d94f..00000000 --- a/tests/blocks.cairo +++ /dev/null @@ -1 +0,0 @@ -pub mod block_170; diff --git a/tests/blocks/block_170.cairo b/tests/blocks/block_170.cairo deleted file mode 100644 index 2d09a61b..00000000 --- a/tests/blocks/block_170.cairo +++ /dev/null @@ -1,97 +0,0 @@ -// THIS CODE IS GENERATED BY SCRIPT, DO NOT EDIT IT MANUALLY - -use raito::types::transaction::{Transaction, TxIn, TxOut, OutPoint}; -use raito::types::block::{Block, Header, TransactionData}; -use raito::utils::hex::from_hex; - -// block_hash: 00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee -pub fn block_170() -> Block { - Block { - header: Header { - version: 1_u32, time: 1231731025_u32, bits: 486604799_u32, nonce: 1889418792_u32, - }, - data: TransactionData::Transactions( - array![ - Transaction { - version: 1, - is_segwit: false, - lock_time: 0, - inputs: array![ - TxIn { - script: @from_hex("04ffff001d0102"), - sequence: 4294967295, - witness: array![].span(), - previous_output: OutPoint { - txid: 0_u256.into(), - vout: 0xffffffff_u32, - data: Default::default(), - block_height: Default::default(), - block_time: Default::default(), - is_coinbase: false, - }, - } - ] - .span(), - outputs: array![ - TxOut { - value: 5000000000_u64, - pk_script: @from_hex( - "4104d46c4968bde02899d2aa0963367c7a6ce34eec332b32e42e5f3407e052d64ac625da6f0718e7b302140434bd725706957c092db53805b821a85b23a7ac61725bac" - ), - cached: false, - }, - ] - .span(), - }, - Transaction { - version: 1, - is_segwit: false, - lock_time: 0, - inputs: array![ - TxIn { - script: @from_hex( - "47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901" - ), - sequence: 4294967295, - witness: array![].span(), - previous_output: OutPoint { - txid: 0x0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9_u256 - .into(), - vout: 0_u32, - data: TxOut { - value: 5000000000_u64, - pk_script: @from_hex( - "410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac" - ), - cached: false, - }, - block_height: 9_u32, - block_time: 1231473279_u32, - is_coinbase: true - }, - }, - ] - .span(), - outputs: array![ - TxOut { - value: 1000000000_u64, - pk_script: @from_hex( - "4104ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84cac" - ), - cached: false, - }, - TxOut { - value: 4000000000_u64, - pk_script: @from_hex( - "410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac" - ), - cached: false, - }, - ] - .span(), - }, - ] - .span() - ), - } -} diff --git a/tests/data/light_150012.json b/tests/data/light_150012.json new file mode 100644 index 00000000..784b2ff8 --- /dev/null +++ b/tests/data/light_150012.json @@ -0,0 +1,56 @@ +{ + "chain_state": { + "block_height": 150012, + "total_work": "148037117366232969946", + "best_block_hash": "00000000000006d414b6c22492b1cc5fb56d42645616efe049f3aad7fa589de4", + "current_target": "18362361570655080300849714079315004638119732162003921272832000", + "epoch_start_time": 1318556675, + "prev_timestamps": [ + 1319118976, + 1319120780, + 1319121961, + 1319122014, + 1319124571, + 1319124771, + 1319125096, + 1319125853, + 1319126850, + 1319127325, + 1319127367 + ] + }, + "blocks": [ + { + "header": { + "version": 1, + "time": 1319128988, + "bits": 436956491, + "nonce": 3634786348 + }, + "data": { + "variant_id": 0, + "merkle_root": "a25a937478ca5f18e77aef1cdb9e69e347288248411253faefcd04d90b4c9380" + } + } + ], + "expected": { + "block_height": 150013, + "total_work": "148043423313798143900", + "best_block_hash": "000000000000011f9791dcfdfe0e402b79a165a3b781bafcc918b6f2166d577c", + "current_target": "18362361570655080300849714079315004638119732162003921272832000", + "epoch_start_time": 1318556675, + "prev_timestamps": [ + 1319120780, + 1319121961, + 1319122014, + 1319124571, + 1319124771, + 1319125096, + 1319125853, + 1319126850, + 1319127325, + 1319127367, + 1319128988 + ] + } +} \ No newline at end of file diff --git a/tests/data/light_169.json b/tests/data/light_169.json new file mode 100644 index 00000000..01fc67f1 --- /dev/null +++ b/tests/data/light_169.json @@ -0,0 +1,56 @@ +{ + "chain_state": { + "block_height": 169, + "total_work": "730155581610", + "best_block_hash": "000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55", + "current_target": "26959535291011309493156476344723991336010898738574164086137773096960", + "epoch_start_time": 1231006505, + "prev_timestamps": [ + 1231702618, + 1231703466, + 1231704197, + 1231704464, + 1231714917, + 1231715347, + 1231716245, + 1231716329, + 1231716711, + 1231717181, + 1231730523 + ] + }, + "blocks": [ + { + "header": { + "version": 1, + "time": 1231731025, + "bits": 486604799, + "nonce": 1889418792 + }, + "data": { + "variant_id": 0, + "merkle_root": "7dac2c5666815c17a3b36427de37bb9d2e2c5ccec3f8633eb91a4205cb4c10ff" + } + } + ], + "expected": { + "block_height": 170, + "total_work": "734450614443", + "best_block_hash": "00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee", + "current_target": "26959535291011309493156476344723991336010898738574164086137773096960", + "epoch_start_time": 1231006505, + "prev_timestamps": [ + 1231703466, + 1231704197, + 1231704464, + 1231714917, + 1231715347, + 1231716245, + 1231716329, + 1231716711, + 1231717181, + 1231730523, + 1231731025 + ] + } +} \ No newline at end of file diff --git a/tests/data/light_209999.json b/tests/data/light_209999.json new file mode 100644 index 00000000..27b819dc --- /dev/null +++ b/tests/data/light_209999.json @@ -0,0 +1,56 @@ +{ + "chain_state": { + "block_height": 209999, + "total_work": "628948977548807410656", + "best_block_hash": "00000000000000f3819164645360294b5dee7f2e846001ac9f41a70b7a9a3de1", + "current_target": "7839560629067579481152758851432818444879208153964570478641152", + "epoch_start_time": 1353928229, + "prev_timestamps": [ + 1354109259, + 1354109550, + 1354109928, + 1354110304, + 1354111480, + 1354111543, + 1354112757, + 1354112978, + 1354113407, + 1354114799, + 1354114900 + ] + }, + "blocks": [ + { + "header": { + "version": 2, + "time": 1354116278, + "bits": 436527338, + "nonce": 4069828196 + }, + "data": { + "variant_id": 0, + "merkle_root": "3cdd40a60823b1c7356d0987078e9426724c5b3ab439c2d80ad2bdd620e603d8" + } + } + ], + "expected": { + "block_height": 210000, + "total_work": "628963747775700992096", + "best_block_hash": "000000000000048b95347e83192f69cf0366076336c639f9b7228e9ba171342e", + "current_target": "7839560629067579481152758851432818444879208153964570478641152", + "epoch_start_time": 1353928229, + "prev_timestamps": [ + 1354109550, + 1354109928, + 1354110304, + 1354111480, + 1354111543, + 1354112757, + 1354112978, + 1354113407, + 1354114799, + 1354114900, + 1354116278 + ] + } +} \ No newline at end of file diff --git a/tests/data/light_24834.json b/tests/data/light_24834.json new file mode 100644 index 00000000..1d841132 --- /dev/null +++ b/tests/data/light_24834.json @@ -0,0 +1,56 @@ +{ + "chain_state": { + "block_height": 24834, + "total_work": "106667140407555", + "best_block_hash": "00000000ab8f089817e23c63c914e98860974a5a320ff3b5e695358b5cda5ae7", + "current_target": "26959535291011309493156476344723991336010898738574164086137773096960", + "epoch_start_time": 1254454028, + "prev_timestamps": [ + 1255309188, + 1255309684, + 1255310160, + 1255311281, + 1255314142, + 1255314878, + 1255315309, + 1255316033, + 1255316675, + 1255316753, + 1255319802 + ] + }, + "blocks": [ + { + "header": { + "version": 1, + "time": 1255321278, + "bits": 486604799, + "nonce": 2784354844 + }, + "data": { + "variant_id": 0, + "merkle_root": "06b9ebfc02a22fa14367fe8d4233466f76244f080fb67c7e705feb64bc677b74" + } + } + ], + "expected": { + "block_height": 24835, + "total_work": "106671435440388", + "best_block_hash": "00000000132fbe8314fc571c0be60b31ccd461c9ee85f42bde8c6d160a9dacc0", + "current_target": "26959535291011309493156476344723991336010898738574164086137773096960", + "epoch_start_time": 1254454028, + "prev_timestamps": [ + 1255309684, + 1255310160, + 1255311281, + 1255314142, + 1255314878, + 1255315309, + 1255316033, + 1255316675, + 1255316753, + 1255319802, + 1255321278 + ] + } +} \ No newline at end of file diff --git a/tests/data/light_478557.json b/tests/data/light_478557.json new file mode 100644 index 00000000..317a3518 --- /dev/null +++ b/tests/data/light_478557.json @@ -0,0 +1,56 @@ +{ + "chain_state": { + "block_height": 478557, + "total_work": "140570947507683834800942596", + "best_block_hash": "000000000000000000eb9bc1f9557dc9e2cfe576f57a52f6be94720b338029e4", + "current_target": "31340207270661909233492904963194738468218672502370467840", + "epoch_start_time": 1501153434, + "prev_timestamps": [ + 1501585012, + 1501585575, + 1501586246, + 1501587141, + 1501587349, + 1501589769, + 1501591048, + 1501592847, + 1501592914, + 1501592981, + 1501593084 + ] + }, + "blocks": [ + { + "header": { + "version": 536870914, + "time": 1501593374, + "bits": 402736949, + "nonce": 1968823574 + }, + "data": { + "variant_id": 0, + "merkle_root": "5b65144f6518bf4795abd428acd0c3fb2527e4e5c94b0f5a7366f4826001884a" + } + } + ], + "expected": { + "block_height": 478558, + "total_work": "140574642189350656803792530", + "best_block_hash": "0000000000000000011865af4122fe3b144e2cbeea86142e8ff2fb4107352d43", + "current_target": "31340207270661909233492904963194738468218672502370467840", + "epoch_start_time": 1501153434, + "prev_timestamps": [ + 1501585575, + 1501586246, + 1501587141, + 1501587349, + 1501589769, + 1501591048, + 1501592847, + 1501592914, + 1501592981, + 1501593084, + 1501593374 + ] + } +} \ No newline at end of file diff --git a/tests/data/light_481823.json b/tests/data/light_481823.json new file mode 100644 index 00000000..3e0432ef --- /dev/null +++ b/tests/data/light_481823.json @@ -0,0 +1,56 @@ +{ + "chain_state": { + "block_height": 481823, + "total_work": "153183377335674126116398464", + "best_block_hash": "000000000000000000cbeff0b533f8e1189cf09dfbebf57a8ebe349362811b80", + "current_target": "29201223626342991605750065618903157022235193117232857088", + "epoch_start_time": 1502282210, + "prev_timestamps": [ + 1503532486, + 1503533722, + 1503534323, + 1503534333, + 1503535125, + 1503536364, + 1503536701, + 1503537770, + 1503538455, + 1503538877, + 1503539571 + ] + }, + "blocks": [ + { + "header": { + "version": 536870914, + "time": 1503539857, + "bits": 402734313, + "nonce": 575995682 + }, + "data": { + "variant_id": 0, + "merkle_root": "6438250cad442b982801ae6994edb8a9ec63c0a0ba117779fbe7ef7f07cad140" + } + } + ], + "expected": { + "block_height": 481824, + "total_work": "153187192062958097878268137", + "best_block_hash": "0000000000000000001c8018d9cb3b742ef25114f27563e3fc4a1902167f9893", + "current_target": "30353962581764818649842367179120467226026534727449575424", + "epoch_start_time": 1503539857, + "prev_timestamps": [ + 1503533722, + 1503534323, + 1503534333, + 1503535125, + 1503536364, + 1503536701, + 1503537770, + 1503538455, + 1503538877, + 1503539571, + 1503539857 + ] + } +} \ No newline at end of file diff --git a/tests/data/light_491406.json b/tests/data/light_491406.json new file mode 100644 index 00000000..810d92bf --- /dev/null +++ b/tests/data/light_491406.json @@ -0,0 +1,56 @@ +{ + "chain_state": { + "block_height": 491406, + "total_work": "195957008972212060987909551", + "best_block_hash": "000000000000000000b23dfce1c518814ec15cc1416f5e72af1844c17d470244", + "current_target": "22526487188587264742197108840494583820145762956159746048", + "epoch_start_time": 1508040302, + "prev_timestamps": [ + 1508801963, + 1508802620, + 1508803336, + 1508803890, + 1508804388, + 1508804637, + 1508806096, + 1508806419, + 1508807074, + 1508807422, + 1508807855 + ] + }, + "blocks": [ + { + "header": { + "version": 536870912, + "time": 1508808039, + "bits": 402713392, + "nonce": 700943001 + }, + "data": { + "variant_id": 0, + "merkle_root": "3336de14c9bbb6d51c65a68b55289156c115ba8117e7c5d4f0c463fca12d4a94" + } + } + ], + "expected": { + "block_height": 491407, + "total_work": "195962149236127509849667536", + "best_block_hash": "000000000000000000e5438564434edaf41e63829a637521a96235adf4653e1b", + "current_target": "22526487188587264742197108840494583820145762956159746048", + "epoch_start_time": 1508040302, + "prev_timestamps": [ + 1508802620, + 1508803336, + 1508803890, + 1508804388, + 1508804637, + 1508806096, + 1508806419, + 1508807074, + 1508807422, + 1508807855, + 1508808039 + ] + } +} \ No newline at end of file diff --git a/tests/data/light_57042.json b/tests/data/light_57042.json new file mode 100644 index 00000000..ed7710ac --- /dev/null +++ b/tests/data/light_57042.json @@ -0,0 +1,56 @@ +{ + "chain_state": { + "block_height": 57042, + "total_work": "682061703958502", + "best_block_hash": "0000000013e7e85518dac94d012d73253d3fdac5c30c4143b177f3086f129580", + "current_target": "2275790652544821279950241890112140030244814501479017131553197129728", + "epoch_start_time": 1274278435, + "prev_timestamps": [ + 1274546733, + 1274546901, + 1274547666, + 1274548679, + 1274548759, + 1274548815, + 1274548928, + 1274549700, + 1274549833, + 1274551103, + 1274551384 + ] + }, + "blocks": [ + { + "header": { + "version": 1, + "time": 1274552191, + "bits": 471178276, + "nonce": 188133155 + }, + "data": { + "variant_id": 0, + "merkle_root": "5c1d2211f598cd6498f42b269fe3ce4a6fdb40eaa638f86a0579c4e63a721b5a" + } + } + ], + "expected": { + "block_height": 57043, + "total_work": "682112583897352", + "best_block_hash": "00000000152340ca42227603908689183edc47355204e7aca59383b0aaac1fd8", + "current_target": "2275790652544821279950241890112140030244814501479017131553197129728", + "epoch_start_time": 1274278435, + "prev_timestamps": [ + 1274546901, + 1274547666, + 1274548679, + 1274548759, + 1274548815, + 1274548928, + 1274549700, + 1274549833, + 1274551103, + 1274551384, + 1274552191 + ] + } +} \ No newline at end of file diff --git a/tests/data/light_629999.json b/tests/data/light_629999.json new file mode 100644 index 00000000..2732e284 --- /dev/null +++ b/tests/data/light_629999.json @@ -0,0 +1,56 @@ +{ + "chain_state": { + "block_height": 629999, + "total_work": "4760696354555745652367069856", + "best_block_hash": "0000000000000000000d656be18bb095db1b23bd797266b0ac3ba720b1962b1e", + "current_target": "1674005436900453533413418811078063286996924790657253376", + "epoch_start_time": 1588651521, + "prev_timestamps": [ + 1589216781, + 1589217134, + 1589217149, + 1589219592, + 1589219803, + 1589221014, + 1589221719, + 1589222377, + 1589223061, + 1589224354, + 1589225003 + ] + }, + "blocks": [ + { + "header": { + "version": 536870912, + "time": 1589225023, + "bits": 387021369, + "nonce": 2302182970 + }, + "data": { + "variant_id": 0, + "merkle_root": "b191f5f973b9040e81c4f75f99c7e43c92010ba8654718e3dd1a4800851d300d" + } + } + ], + "expected": { + "block_height": 630000, + "total_work": "4760765525232665145485718734", + "best_block_hash": "000000000000000000024bead8df69990852c202db0e0097c1a12ea637d7e96d", + "current_target": "1674005436900453533413418811078063286996924790657253376", + "epoch_start_time": 1588651521, + "prev_timestamps": [ + 1589217134, + 1589217149, + 1589219592, + 1589219803, + 1589221014, + 1589221719, + 1589222377, + 1589223061, + 1589224354, + 1589225003, + 1589225023 + ] + } +} \ No newline at end of file diff --git a/tests/data/light_709631.json b/tests/data/light_709631.json new file mode 100644 index 00000000..0b0852b4 --- /dev/null +++ b/tests/data/light_709631.json @@ -0,0 +1,56 @@ +{ + "chain_state": { + "block_height": 709631, + "total_work": "11167921669044838326494243616", + "best_block_hash": "000000000000000000013712fc242ee6dd28476d0e9c931c75f83e6974c6bccc", + "current_target": "1244706868954148772026104835685647745369230477348569088", + "epoch_start_time": 1635710370, + "prev_timestamps": [ + 1636858675, + 1636858836, + 1636859029, + 1636859026, + 1636859129, + 1636860027, + 1636861723, + 1636861759, + 1636863983, + 1636865627, + 1636865834 + ] + }, + "blocks": [ + { + "header": { + "version": 538968068, + "time": 1636866927, + "bits": 386689514, + "nonce": 1410298626 + }, + "data": { + "variant_id": 0, + "merkle_root": "6ada3b10082068de09f7e819b65113d3c58969fd857aab2980c65f374714ec77" + } + } + ], + "expected": { + "block_height": 709632, + "total_work": "11168019055255961316881503271", + "best_block_hash": "0000000000000000000687bca986194dc2c1f949318629b44bb54ec0a94d8244", + "current_target": "1188998811044006745492934980917001185509005296607952896", + "epoch_start_time": 1636866927, + "prev_timestamps": [ + 1636858836, + 1636859029, + 1636859026, + 1636859129, + 1636860027, + 1636861723, + 1636861759, + 1636863983, + 1636865627, + 1636865834, + 1636866927 + ] + } +} \ No newline at end of file diff --git a/tests/data/light_757738.json b/tests/data/light_757738.json new file mode 100644 index 00000000..713fdd08 --- /dev/null +++ b/tests/data/light_757738.json @@ -0,0 +1,56 @@ +{ + "chain_state": { + "block_height": 757738, + "total_work": "16927818163357589032909741096", + "best_block_hash": "00000000000000000001c4ffa847267d4f7a6842421cba3f1ec195062ea1057e", + "current_target": "859664032087861081904916640712713969859737457373741056", + "epoch_start_time": 1664333794, + "prev_timestamps": [ + 1665245428, + 1665245458, + 1665245937, + 1665247316, + 1665248544, + 1665248564, + 1665248868, + 1665249416, + 1665249581, + 1665249850, + 1665249935 + ] + }, + "blocks": [ + { + "header": { + "version": 607625220, + "time": 1665249955, + "bits": 386464174, + "nonce": 1249847859 + }, + "data": { + "variant_id": 0, + "merkle_root": "669d79f554365d340f5aadf1c2491589e31529c11b101e5be7ed29e33c1c2cd0" + } + } + ], + "expected": { + "block_height": 757739, + "total_work": "16927952857941653568697532352", + "best_block_hash": "00000000000000000002601c74946371bd1bf00ad3154f011c20abad1cabd0ea", + "current_target": "859664032087861081904916640712713969859737457373741056", + "epoch_start_time": 1664333794, + "prev_timestamps": [ + 1665245458, + 1665245937, + 1665247316, + 1665248544, + 1665248564, + 1665248868, + 1665249416, + 1665249581, + 1665249850, + 1665249935, + 1665249955 + ] + } +} \ No newline at end of file diff --git a/tests/data/light_757752.json b/tests/data/light_757752.json new file mode 100644 index 00000000..9466aeb4 --- /dev/null +++ b/tests/data/light_757752.json @@ -0,0 +1,56 @@ +{ + "chain_state": { + "block_height": 757752, + "total_work": "16929703887534492533938818680", + "best_block_hash": "0000000000000000000620ebe7e1f82eeff8ebf085f80ac036e76016d84f567a", + "current_target": "859664032087861081904916640712713969859737457373741056", + "epoch_start_time": 1664333794, + "prev_timestamps": [ + 1665254120, + 1665254763, + 1665255018, + 1665255425, + 1665256214, + 1665256267, + 1665256547, + 1665256670, + 1665257132, + 1665258043, + 1665258313 + ] + }, + "blocks": [ + { + "header": { + "version": 549453828, + "time": 1665258359, + "bits": 386464174, + "nonce": 1554058904 + }, + "data": { + "variant_id": 0, + "merkle_root": "3b1c35571ae9a734619cf96011975b7697f718e7db65fae74fd63f835df33a19" + } + } + ], + "expected": { + "block_height": 757753, + "total_work": "16929838582118557069726609936", + "best_block_hash": "000000000000000000032c781dbe11459fba50312acfca3cd96fa2bc4367d5b1", + "current_target": "859664032087861081904916640712713969859737457373741056", + "epoch_start_time": 1664333794, + "prev_timestamps": [ + 1665254763, + 1665255018, + 1665255425, + 1665256214, + 1665256267, + 1665256547, + 1665256670, + 1665257132, + 1665258043, + 1665258313, + 1665258359 + ] + } +} \ No newline at end of file diff --git a/tests/data/light_774627.json b/tests/data/light_774627.json new file mode 100644 index 00000000..0c48d921 --- /dev/null +++ b/tests/data/light_774627.json @@ -0,0 +1,56 @@ +{ + "chain_state": { + "block_height": 774627, + "total_work": "19535913008274828339499138992", + "best_block_hash": "0000000000000000000560268dbd186dbecdd347e6dad829c10c0fd3cffb2b1a", + "current_target": "685105199528332699160504931662746558558072186305773568", + "epoch_start_time": 1674972641, + "prev_timestamps": [ + 1675279224, + 1675279256, + 1675279647, + 1675280604, + 1675280829, + 1675281795, + 1675281807, + 1675281911, + 1675282912, + 1675283660, + 1675283776 + ] + }, + "blocks": [ + { + "header": { + "version": 680919040, + "time": 1675283913, + "bits": 386344736, + "nonce": 3494801492 + }, + "data": { + "variant_id": 0, + "merkle_root": "498902de19bf91644236aee19cd5cba1d9c4d8902e63508a820e4e3006b4605c" + } + } + ], + "expected": { + "block_height": 774628, + "total_work": "19536082021864736917596606276", + "best_block_hash": "0000000000000000000515e202c8ae73c8155fc472422d7593af87aa74f2cf3d", + "current_target": "685105199528332699160504931662746558558072186305773568", + "epoch_start_time": 1674972641, + "prev_timestamps": [ + 1675279256, + 1675279647, + 1675280604, + 1675280829, + 1675281795, + 1675281807, + 1675281911, + 1675282912, + 1675283660, + 1675283776, + 1675283913 + ] + } +} \ No newline at end of file diff --git a/tests/data/light_839999.json b/tests/data/light_839999.json new file mode 100644 index 00000000..99b73bda --- /dev/null +++ b/tests/data/light_839999.json @@ -0,0 +1,56 @@ +{ + "chain_state": { + "block_height": 839999, + "total_work": "36281734469479373899698561888", + "best_block_hash": "0000000000000000000172014ba58d66455762add0512355ad651207918494ab", + "current_target": "312072983117630369221114618645075196904111619969122304", + "epoch_start_time": 1712783853, + "prev_timestamps": [ + 1713565365, + 1713565582, + 1713566049, + 1713567412, + 1713568092, + 1713568594, + 1713570208, + 1713570326, + 1713571084, + 1713571122, + 1713571533 + ] + }, + "blocks": [ + { + "header": { + "version": 710926336, + "time": 1713571767, + "bits": 386089497, + "nonce": 3932395645 + }, + "data": { + "variant_id": 0, + "merkle_root": "031b417c3a1828ddf3d6527fc210daafcc9218e81f98257f88d4d43bd7a5894f" + } + } + ], + "expected": { + "block_height": 840000, + "total_work": "36282105511176353065702212651", + "best_block_hash": "0000000000000000000320283a032748cef8227873ff4872689bf23f1cda83a5", + "current_target": "312072983117630369221114618645075196904111619969122304", + "epoch_start_time": 1712783853, + "prev_timestamps": [ + 1713565582, + 1713566049, + 1713567412, + 1713568092, + 1713568594, + 1713570208, + 1713570326, + 1713571084, + 1713571122, + 1713571533, + 1713571767 + ] + } +} \ No newline at end of file diff --git a/tests/lib.cairo b/tests/lib.cairo deleted file mode 100644 index 609108a3..00000000 --- a/tests/lib.cairo +++ /dev/null @@ -1,3 +0,0 @@ -mod blocks; -mod tests; -mod light_client; diff --git a/tests/light_client.cairo b/tests/light_client.cairo deleted file mode 100644 index 0bf91338..00000000 --- a/tests/light_client.cairo +++ /dev/null @@ -1,3 +0,0 @@ -/// Regression tests for [BlockHeader] validation. - -mod block_170; diff --git a/tests/light_client/block_170.cairo b/tests/light_client/block_170.cairo deleted file mode 100644 index 9fc75708..00000000 --- a/tests/light_client/block_170.cairo +++ /dev/null @@ -1,75 +0,0 @@ -// THIS FILE IS GENERATED BY A SCRIPT, DO NOT EDIT IT MANUALLY! - -use raito::types::chain_state::{ChainState, BlockValidator}; -use raito::types::block::{Block, Header, TransactionData}; - -/// Chain state at the beginning of the test -fn chain_state_169() -> ChainState { - ChainState { - block_height: 169, - total_work: 730155581610_u256, - best_block_hash: 0x000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55_u256 - .into(), - current_target: 26959535291011309493156476344723991336010898738574164086137773096960, - epoch_start_time: 1231006505, - prev_timestamps: [ - 1231702618, - 1231703466, - 1231704197, - 1231704464, - 1231714917, - 1231715347, - 1231716245, - 1231716329, - 1231716711, - 1231717181, - 1231730523 - ].span(), - } -} - -/// Block that is being validated and applied -fn block_170() -> Block { - Block { - header: Header { - version: 1_u32, time: 1231731025_u32, bits: 486604799_u32, nonce: 1889418792_u32, - }, - data: TransactionData::MerkleRoot( - 0x7dac2c5666815c17a3b36427de37bb9d2e2c5ccec3f8633eb91a4205cb4c10ff_u256.into() - ), - } -} - -/// Expected chain state at the end of the test -fn chain_state_170() -> ChainState { - ChainState { - block_height: 170, - total_work: 734450614443_u256, - best_block_hash: 0x00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee_u256 - .into(), - current_target: 26959535291011309493156476344723991336010898738574164086137773096960, - epoch_start_time: 1231006505, - prev_timestamps: [ - 1231703466, - 1231704197, - 1231704464, - 1231714917, - 1231715347, - 1231716245, - 1231716329, - 1231716711, - 1231717181, - 1231730523, - 1231731025, - ].span(), - } -} - -#[test] -fn test_block_header_170() { - let chain_state = chain_state_169(); - let block = block_170(); - let new_chain_state = chain_state.validate_and_apply(block).unwrap(); - let expected = chain_state_170(); - assert_eq!(expected, new_chain_state); -} diff --git a/tests/tests.cairo b/tests/tests.cairo deleted file mode 100644 index cb265e0b..00000000 --- a/tests/tests.cairo +++ /dev/null @@ -1,131 +0,0 @@ -use raito::utils::hex::from_hex; -use raito::types::{ - chain_state::{ChainState, BlockValidator}, block::{Block, Header, TransactionData}, - transaction::{Transaction, TxIn, TxOut, OutPoint}, -}; -use super::blocks::{block_170::block_170}; - -#[test] -fn test_block1() { - let block = Block { - header: Header { - version: 1_u32, time: 1231469665_u32, bits: 486604799_u32, nonce: 2573394689_u32, - }, - data: TransactionData::Transactions( - array![ - Transaction { - version: 1, - is_segwit: false, - lock_time: 0, - inputs: array![ - TxIn { - script: @from_hex("04ffff001d0104"), - sequence: 0xffffffff, - witness: array![].span(), - previous_output: OutPoint { - txid: 0_u256.into(), - vout: 4294967295_u32, - data: Default::default(), - block_height: Default::default(), - block_time: Default::default(), - is_coinbase: false, - }, - } - ] - .span(), - outputs: array![ - TxOut { - value: 5000000000_u64, - pk_script: @from_hex( - "410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac" - ), - cached: false, - }, - ] - .span(), - }, - ] - .span() - ) - }; - let prev_chain_state: ChainState = Default::default(); - - let next_chain_state = prev_chain_state.validate_and_apply(block); - assert!(next_chain_state.is_ok(), "Error: {:?}", next_chain_state.err()); - - let result = next_chain_state.unwrap(); - assert_eq!(result.block_height, 1); - assert_eq!(result.total_work, 8590065666); - assert_eq!(result.prev_timestamps, [0, 0, 0, 0, 0, 0, 0, 0, 0, 1231006505, 1231469665].span()); - assert_eq!( - result.current_target, - 0x00000000ffff0000000000000000000000000000000000000000000000000000_u256 - ); - assert_eq!(result.epoch_start_time, 1231006505); - assert_eq!( - result.best_block_hash, - 0x00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048_u256.into() - ); - //to impl -// assert_eq!(result.utreexo_state.roots, [...]); - -} - -#[test] -fn test_block170() { - let block170 = block_170(); - let prev_chain_state_block169 = ChainState { - block_height: 169, - total_work: 734450614443_u256, - best_block_hash: 0x000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55_u256 - .into(), - current_target: 26959535291011309493156476344723991336010898738574164086137773096960, - epoch_start_time: 1231006505, - prev_timestamps: [ - 1231702618, - 1231703466, - 1231704197, - 1231704464, - 1231714917, - 1231715347, - 1231716245, - 1231716329, - 1231716711, - 1231717181, - 1231730523 - ].span(), - }; - - let next_chain_state = prev_chain_state_block169.validate_and_apply(block170); - assert!(next_chain_state.is_ok(), "Error: {:?}", next_chain_state.err()); - - let result = next_chain_state.unwrap(); - assert_eq!(result.block_height, 170); - // assert_eq!(result.total_work, ?); - assert_eq!( - result.prev_timestamps, - [ - 1231703466, - 1231704197, - 1231704464, - 1231714917, - 1231715347, - 1231716245, - 1231716329, - 1231716711, - 1231717181, - 1231730523, - 1231731025 - ].span() - ); - assert_eq!( - result.current_target, 0x00000000ffff0000000000000000000000000000000000000000000000000000 - ); - assert_eq!(result.epoch_start_time, 1231006505); - assert_eq!( - result.best_block_hash, - 0x00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee_u256.into() - ); - //to impl -// assert_eq!(result.utreexo_state.roots, [...]); -}