Skip to content

Commit

Permalink
Use serde to deserialize program arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
m-kus committed Sep 5, 2024
1 parent 511857c commit 4c25966
Show file tree
Hide file tree
Showing 11 changed files with 79 additions and 34 deletions.
15 changes: 10 additions & 5 deletions scripts/data/format_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def serialize(obj):
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 -> []
list -> tuple(len(list), *list)
dict -> tuple(dict.values)
"""
if isinstance(obj, bool):
Expand All @@ -23,7 +23,11 @@ def serialize(obj):
assert(obj >= 0 and obj < 2 ** 252)
return obj
elif isinstance(obj, str):
if obj.isdigit():
if obj == "0" * 64:
# special case - zero hash
return (0, 0, 0, 0, 0, 0, 0, 0)
elif obj.isdigit():
# TODO: there might still be collisions with hashes
# Try to cast to int and then to low/high parts
num = int(obj)
assert(num >= 0 and num < 2 ** 256)
Expand All @@ -39,14 +43,15 @@ def serialize(obj):
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)
return tuple([len(main)] + 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))
arr = list(map(serialize, obj))
return tuple([len(arr)] + arr)
elif isinstance(obj, dict):
return tuple(map(serialize, obj.values()))
elif isinstance(obj, tuple):
Expand Down Expand Up @@ -105,7 +110,7 @@ def format_args():
raise TypeError("Expected single argument")
args = json.loads(Path(sys.argv[1]).read_text())
res = flatten_tuples(serialize(args))
print(res)
print([res])


if __name__ == '__main__':
Expand Down
4 changes: 2 additions & 2 deletions scripts/data/generate_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,9 @@ def resolve_transaction(transaction: dict):
"version": transaction['version'],
# Skip the first 4 bytes (version) and take the next 4 bytes (marker + flag)
"is_segwit": transaction["hex"][8:12] == "0001",
"lock_time": transaction['locktime'],
"inputs": [resolve_input(input) for input in transaction['vin']],
"outputs": [format_output(output) for output in transaction['vout']],
"lock_time": transaction['locktime'],
}


Expand All @@ -151,8 +151,8 @@ def resolve_input(input: dict):
return {
"script": f'0x{input["scriptSig"]["hex"]}',
"sequence": input['sequence'],
"witness": [f'0x{item}' for item in input.get('txinwitness', [])],
"previous_output": resolve_outpoint(input),
"witness": [f'0x{item}' for item in input.get('txinwitness', [])],
}


Expand Down
1 change: 0 additions & 1 deletion scripts/data/integration_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ test_files="tests/data"/*
ignored_files=(
"tests/data/light_481823.json"
"tests/data/light_709631.json"
"tests/data/full_169.json"
)
ignored="${ignored_files[@]}"

Expand Down
16 changes: 14 additions & 2 deletions src/main.cairo
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
use crate::types::block::Block;
use crate::types::chain_state::{ChainState, BlockValidator};

/// Raito program arguments.
#[derive(Serde)]
struct Args {
/// Current (initial) chain state
chain_state: ChainState,
/// Batch of blocks that have to be applied to the current chain state
blocks: Array<Block>,
}

/// Raito program entrypoint.
///
/// 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<Block>) -> ChainState {
while let Option::Some(block) = blocks.pop_front() {
fn main(mut arguments: Span<felt252>) -> ChainState {
let Args { mut chain_state, blocks, } = Serde::<Args>::deserialize(ref arguments)
.expect('Failed to deserialize');

for block in blocks {
chain_state = chain_state.validate_and_apply(block).expect('Validation failed');
};
chain_state
Expand Down
29 changes: 23 additions & 6 deletions src/test.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,31 @@ use crate::types::block::Block;
use crate::types::chain_state::{ChainState, BlockValidator};
use core::testing::get_available_gas;

/// Integration testing program arguments.
#[derive(Serde)]
struct Args {
/// Current (initial) chain state
chain_state: ChainState,
/// Batch of blocks that have to be applied to the current chain state
blocks: Array<Block>,
/// Expected chain state (that we want to compare the result with)
expected_chain_state: ChainState,
}

/// 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<Block>, mut expected_chain_state: ChainState
) {
/// Receives arguments in a serialized format (Cairo serde).
/// Panics in case of a validation error or chain state mismatch.
/// Prints result to the stdout.
fn test(mut arguments: Span<felt252>) {
let Args { mut chain_state, blocks, expected_chain_state } = Serde::<
Args
>::deserialize(ref arguments)
.expect('Failed to deserialize');

let mut gas_before = get_available_gas();
while let Option::Some(block) = blocks.pop_front() {

for block in blocks {
let height = chain_state.block_height + 1;
match chain_state.validate_and_apply(block) {
Result::Ok(new_chain_state) => {
Expand All @@ -28,6 +44,7 @@ fn test(
}
}
};

if chain_state != expected_chain_state {
println!(
"FAIL: block={} error='expected state {:?}, actual {:?}'",
Expand Down
6 changes: 3 additions & 3 deletions src/types/block.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::utils::numeric::u32_byte_reverse;
use super::transaction::Transaction;

/// Represents a block in the blockchain.
#[derive(Drop, Copy, Debug, PartialEq, Default)]
#[derive(Drop, Copy, Debug, PartialEq, Default, Serde)]
pub struct Block {
/// Block header.
pub header: Header,
Expand All @@ -17,7 +17,7 @@ pub struct Block {
}

/// Represents block contents.
#[derive(Drop, Copy, Debug, PartialEq)]
#[derive(Drop, Copy, Debug, PartialEq, Serde)]
pub enum TransactionData {
/// Merkle root of all transactions in the block.
/// This variant is used for header-only validation mode (light client).
Expand All @@ -37,7 +37,7 @@ pub enum TransactionData {
/// In order to do the calculation we just need data about the block that is strictly necessary,
/// but not the data we can calculate like merkle root or data that we already have
/// like previous_block_hash (in the previous chain state).
#[derive(Drop, Copy, Debug, PartialEq, Default)]
#[derive(Drop, Copy, Debug, PartialEq, Default, Serde)]
pub struct Header {
/// The version of the block.
pub version: u32,
Expand Down
2 changes: 1 addition & 1 deletion src/types/chain_state.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::validation::{
use super::block::{BlockHash, Block, TransactionData};

/// Represents the state of the blockchain.
#[derive(Drop, Copy, Debug, PartialEq)]
#[derive(Drop, Copy, Debug, PartialEq, Serde)]
pub struct ChainState {
/// Height of the current block.
pub block_height: u32,
Expand Down
21 changes: 17 additions & 4 deletions src/types/transaction.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::codec::{Encode, TransactionCodec};

/// Represents a transaction.
/// https://learnmeabitcoin.com/technical/transaction/
#[derive(Drop, Copy, Debug, PartialEq)]
#[derive(Drop, Copy, Debug, PartialEq, Serde)]
pub struct Transaction {
/// The version of the transaction.
pub version: u32,
Expand All @@ -29,7 +29,7 @@ pub struct Transaction {

/// Input of a transaction.
/// https://learnmeabitcoin.com/technical/transaction/input/
#[derive(Drop, Copy, Debug, PartialEq)]
#[derive(Drop, Copy, Debug, PartialEq, Serde)]
pub struct TxIn {
/// The signature script which satisfies the conditions placed in the txo pubkey script
/// or coinbase script that contains block height (since 227,836) and miner nonce (optional).
Expand Down Expand Up @@ -77,7 +77,7 @@ pub struct TxIn {
/// one by one, first inputs then outputs. Output validation might put something to the
/// cache while input validation might remove an item, thus it's important to maintain
/// the order.
#[derive(Drop, Copy, Debug, PartialEq)]
#[derive(Drop, Copy, Debug, PartialEq, Serde)]
pub struct OutPoint {
/// The hash of the referenced transaction.
pub txid: Hash,
Expand Down Expand Up @@ -112,7 +112,7 @@ pub struct OutPoint {
/// - Do nothing in case of a provably unspendable output
///
/// Read more: https://en.bitcoin.it/wiki/Script#Provably_Unspendable/Prunable_Outputs
#[derive(Drop, Copy, Debug, PartialEq)]
#[derive(Drop, Copy, Debug, PartialEq, Serde)]
pub struct TxOut {
/// The value of the output in satoshis.
/// Can be in range [0, 21_000_000] BTC (including both ends).
Expand All @@ -125,6 +125,19 @@ pub struct TxOut {
pub cached: bool,
}

impl ByteArraySnapSerde of Serde<@ByteArray> {
fn serialize(self: @@ByteArray, ref output: Array<felt252>) {
(*self).serialize(ref output);
}

fn deserialize(ref serialized: Span<felt252>) -> Option<@ByteArray> {
match Serde::deserialize(ref serialized) {
Option::Some(res) => Option::Some(@res),
Option::None => Option::None,
}
}
}

impl TxOutDefault of Default<TxOut> {
fn default() -> TxOut {
TxOut { value: 0, pk_script: @"", cached: false, }
Expand Down
2 changes: 1 addition & 1 deletion src/utils/hash.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use super::bit_shifts::{shl, shr};

/// 256-bit hash digest.
/// Represented as an array of 4-byte words.
#[derive(Copy, Drop, Debug, Default)]
#[derive(Copy, Drop, Debug, Default, Serde)]
pub struct Hash {
pub value: [u32; 8]
}
Expand Down
1 change: 0 additions & 1 deletion src/utils/numeric.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ pub fn u32_byte_reverse(word: u32) -> u32 {
return byte0 + byte1 + byte2 + byte3;
}


#[cfg(test)]
mod tests {
use super::u32_byte_reverse;
Expand Down
16 changes: 8 additions & 8 deletions tests/data/full_169.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,10 @@
{
"version": 1,
"is_segwit": false,
"lock_time": 0,
"inputs": [
{
"script": "0x04ffff001d0102",
"sequence": 4294967295,
"witness": [],
"previous_output": {
"txid": "0000000000000000000000000000000000000000000000000000000000000000",
"vout": 4294967295,
Expand All @@ -50,7 +48,8 @@
"block_height": 0,
"block_time": 0,
"is_coinbase": false
}
},
"witness": []
}
],
"outputs": [
Expand All @@ -59,17 +58,16 @@
"pk_script": "0x4104d46c4968bde02899d2aa0963367c7a6ce34eec332b32e42e5f3407e052d64ac625da6f0718e7b302140434bd725706957c092db53805b821a85b23a7ac61725bac",
"cached": false
}
]
],
"lock_time": 0
},
{
"version": 1,
"is_segwit": false,
"lock_time": 0,
"inputs": [
{
"script": "0x47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901",
"sequence": 4294967295,
"witness": [],
"previous_output": {
"txid": "0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9",
"vout": 0,
Expand All @@ -81,7 +79,8 @@
"block_height": 9,
"block_time": 1231473279,
"is_coinbase": true
}
},
"witness": []
}
],
"outputs": [
Expand All @@ -95,7 +94,8 @@
"pk_script": "0x410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac",
"cached": false
}
]
],
"lock_time": 0
}
]
}
Expand Down

0 comments on commit 4c25966

Please sign in to comment.