From f69c0a7b88ba58e17aef4727bb0386db1b31ee86 Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Fri, 15 Mar 2024 09:34:14 -0500 Subject: [PATCH 1/7] Inital scripts to request data commitment proof and relay from L1 to Starknet, also includes a wait script to hold until relay is done and in Herodotus Fact Registry on Starknet --- scripts/request-data-commitments.sh | 188 ++++++++++++++++++++++++++ scripts/wait-for-herodotus-fulfill.sh | 162 ++++++++++++++++++++++ 2 files changed, 350 insertions(+) create mode 100755 scripts/request-data-commitments.sh create mode 100755 scripts/wait-for-herodotus-fulfill.sh diff --git a/scripts/request-data-commitments.sh b/scripts/request-data-commitments.sh new file mode 100755 index 0000000..b9b2f14 --- /dev/null +++ b/scripts/request-data-commitments.sh @@ -0,0 +1,188 @@ +#!/bin/bash +# +# Request relay of the latest BlobstreamX data commitments from L1 to Starknet +# Uses the Herodotus API to submit a batch query for all data commitments +# up to the latest state_proofNonce + +# Constants +HERODOTUS_API_URL="https://api.herodotus.cloud/" +BLOBSTREAMX_SOURCE_CHAIN_ID="11155111" +BLOBSTREAMX_DESTINATION_CHAIN_ID="SN_SEPOLIA" +STATE_PROOF_NONCE_SLOT="0x00000000000000000000000000000000000000000000000000000000000000fc" +STATE_DATA_COMMITMENT_MAP_SLOT="0x00000000000000000000000000000000000000000000000000000000000000fe" + +# Optional arguments +# TODO: To public api? +L1_RPC_URL="https://sepolia.infura.io/v3/bed8a8401c894421bd7cd31050e7ced6" +STARKNET_RPC_URL="https://starknet-sepolia.infura.io/v3/bed8a8401c894421bd7cd31050e7ced6" +BLOBSTREAMX_L1_ADDRESS="0x48B257EC1610d04191cC2c528d0c940AdbE1E439" +#TODO +BLOBSTREAMX_STARKNET_ADDRESS="0x48B257EC1610d04191cC2c528d0c940AdbE1E439" + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +WORK_DIR=$SCRIPT_DIR/.. + +#TODO: w/o webhook, verbose, w/o header access +#TODO: resume from id + +display_help() { + echo "Usage: $0 [option...] {arguments...}" + echo + + echo " -h, --help display help" + echo " -r, --l1-rpc=URL URL of the L1 RPC server (default: $L1_RPC_URL)" + echo " -s, --starknet-rpc=URL URL of the Starknet RPC server (default: $STARKNET_RPC_URL)" + echo " -b, --blobstreamx-l1=ADDR BlobstreamX contract address on L1 (default: $BLOBSTREAMX_L1_ADDRESS)" + echo " -B, --blobstreamx-starknet=ADDR BlobstreamX contract address on Starknet (default: $BLOBSTREAMX_STARKNET_ADDRESS)" + + echo + echo "Example: $0" +} + +#TODO: Add support for optional arguments + +# Parse command line arguments +while getopts ":hr:-:" opt; do + case ${opt} in + - ) + case "${OPTARG}" in + help ) + display_help + exit 0 + ;; + * ) + echo "Invalid option: --$OPTARG" 1>&2 + display_help + exit 1 + ;; + esac + ;; + h ) + display_help + exit 0 + ;; + \? ) + echo "Invalid option: $OPTARG" 1>&2 + display_help + exit 1 + ;; + : ) + echo "Invalid option: $OPTARG requires an argument" 1>&2 + display_help + exit 1 + ;; + esac +done + +if [ -z "$HERODOTUS_API_KEY" ]; then + echo "HERODOTUS_API_KEY is not set. Get your API key from https://dashboard.herodotus.dev by signing up with your GitHub." + exit 1 +fi + +L1_BLOCK_NUM_RES=$(curl $L1_RPC_URL \ + -X POST \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' 2>/dev/null) +L1_BLOCK_NUM=$(echo $L1_BLOCK_NUM_RES | jq -r '.result') +echo "Latest L1 block number: $L1_BLOCK_NUM" + +# echo +# echo "Getting current Starknet block number..." +# curl $STARKNET_RPC_URL \ +# -X POST \ +# -H "Content-Type: application/json" \ +# -d '{"jsonrpc":"2.0","method":"starknet_blockNumber","params":[],"id":1}' + +LATEST_PROOF_NONCE_RES=$(curl $L1_RPC_URL \ + -X POST \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"eth_getStorageAt","params":["'"$BLOBSTREAMX_L1_ADDRESS"'","'"$STATE_PROOF_NONCE_SLOT"'","latest"],"id":1}' 2>/dev/null) +LATEST_PROOF_NONCE=$(echo $LATEST_PROOF_NONCE_RES | jq -r '.result') +echo "Latest L1 state_proofNonce: $LATEST_PROOF_NONCE" + +# TODO : This or latest option for just one proofNonce +#STARKNET_PROOF_NONCE_RES=$(curl $STARKNET_RPC_URL \ +# -X POST \ +# -H "Content-Type: application/json" \ +# -d '{"jsonrpc":"2.0","method":"starknet_getState","params":["'"$BLOBSTREAMX_STARKNET_ADDRESS"'",'"$LATEST_PROOF_NONCE"'],"id":1}' 2>/dev/null) +STARKNET_PROOF_NONCE_RES=$(echo '{"jsonrpc":"2.0","id":1,"result":"0x00000000000000000000000000000000000000000000000000000000000006a4"}') +STARKNET_PROOF_NONCE=$(echo $STARKNET_PROOF_NONCE_RES | jq -r '.result') +echo "Latest Starknet state_proofNonce: $STARKNET_PROOF_NONCE" + +DATA_COMMITMENT_SLOTS='' +# TODO: Think about edge cases +# Loop through each missing state_proofNonce to build the batch query slots +for ((i = STARKNET_PROOF_NONCE; i <= LATEST_PROOF_NONCE - 1; i++)); do + # Data commitment slot for each proofNonce is located at + # keccak256(abi.encode(proofNonce, STATE_DATA_COMMITMENT_MAP_SLOT)) + DATA_COMMITMENT_ENCODED_SLOT=$(printf "%064x%064x" $i $STATE_DATA_COMMITMENT_MAP_SLOT) + DATA_COMMITMENT_SLOT=$(echo $DATA_COMMITMENT_ENCODED_SLOT | keccak-256sum -x -l | awk '{print $1}') + + if [ -z "$DATA_COMMITMENT_SLOTS" ]; then + DATA_COMMITMENT_SLOTS='"0x'$DATA_COMMITMENT_SLOT'"' + else + DATA_COMMITMENT_SLOTS=$DATA_COMMITMENT_SLOTS',"0x'$DATA_COMMITMENT_SLOT'"' + fi +done + +if [ -z "$DATA_COMMITMENT_SLOTS" ]; then + echo "No missing data commitments found." + exit 0 +fi + +# Convert L1 Block number from hex to decimal +L1_BLOCK_NUM_DEC=$(printf "%d" $L1_BLOCK_NUM) +HERODOTUS_QUERY='{ + "destinationChainId": "'$BLOBSTREAMX_DESTINATION_CHAIN_ID'", + "fee": "0", + "data": { + "'$BLOBSTREAMX_SOURCE_CHAIN_ID'": { + "block:'$L1_BLOCK_NUM_DEC'": { + "header": [ + "PARENT_HASH" + ], + "accounts": { + "'$BLOBSTREAMX_L1_ADDRESS'": { + "slots": [ + "'$STATE_PROOF_NONCE_SLOT'", + '$DATA_COMMITMENT_SLOTS' + ], + "props": [] + } + } + } + } + }, + "webhook": { + "url": "https://webhook.site/1f3a9b5d-5c8c-4e2a-9d7e-6c3c5a0a0e2f", + "headers": { + "Content-Type": "application/json" + } + } +}' +# Clean up the query formatting for readability +HERODOTUS_QUERY=$(echo $HERODOTUS_QUERY | jq '.') +HERODOTUS_QUERY_URL=$(echo $HERODOTUS_API_URL | sed 's/\/$//')'/submit-batch-query?apiKey='$HERODOTUS_API_KEY + +echo +echo "Submitting batch query to Herodotus API..." +echo "Query: $HERODOTUS_QUERY" +curl -X 'POST' \ + $HERODOTUS_QUERY_URL \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d "$HERODOTUS_QUERY" + +#TODO: Wait option +# Wait for state proof nonce slot to be relayed +echo +echo "Waiting for state proof nonce slot $STATE_PROOF_NONCE_SLOT to be relayed..." +$SCRIPT_DIR/wait-for-herodotus-fulfill.sh -b $L1_BLOCK_NUM_DEC -a $BLOBSTREAMX_L1_ADDRESS -S $STATE_PROOF_NONCE_SLOT +# Loop thru each data commitment slot to wait for the data to be relayed (comma separated) +for slot in $(echo $DATA_COMMITMENT_SLOTS | tr ',' '\n' | tr -d '"'); do + echo + echo "Waiting for data commitment at slot $slot to be relayed..." + $SCRIPT_DIR/wait-for-herodotus-fulfill.sh -b $L1_BLOCK_NUM_DEC -a $BLOBSTREAMX_L1_ADDRESS -S $slot +done + +#TODO: Herodotus Fact registry mock, cheat code set values, do call to herodotus reg to update state_proofNonce & data commitments and latest l1 block?, PR for verifying attestations, use herodotus to get data commitment and verify attestation from DC diff --git a/scripts/wait-for-herodotus-fulfill.sh b/scripts/wait-for-herodotus-fulfill.sh new file mode 100755 index 0000000..3308528 --- /dev/null +++ b/scripts/wait-for-herodotus-fulfill.sh @@ -0,0 +1,162 @@ +#!/bin/bash +# +# Wait for the requested slot value relay to be fulfilled by Herodotus. +# Monitors the Herodotus Fact registry on Starknet for the requested slot values. + +# Constants + +# Optional arguments +STARKNET_RPC_URL="https://starknet-sepolia.infura.io/v3/bed8a8401c894421bd7cd31050e7ced6" +HERODOTUS_FACT_REGISTRY="0x07d3550237ecf2d6ddef9b78e59b38647ee511467fe000ce276f245a006b40bc" + +display_help() { + echo "Usage: $0 [option...] {arguments...}" + echo + + echo " -h, --help display help" + echo " -s, --starknet-rpc=URL URL of the Starknet RPC server (default: $STARKNET_RPC_URL)" + echo " -F, --herodotus-fact-registry=ADDRESS Address of the Herodotus Fact registry on Starknet (default: $HERODOTUS_FACT_REGISTRY)" + echo + echo " -b, --block-number=NUMBER Block number for the slot fact (required)" + echo " -a, --address=ADDRESS Address of the contract for the slot fact (required)" + echo " -S, --slot=NUMBER Slot number for the slot fact (required)" + + echo + echo "Example: $0" +} + +#TODO: Add support for optional arguments +#TODO: Add support for required arguments +#TODO: parse arg formats + +# Parse command line arguments +while getopts ":hb:a:S:-:" opt; do + case ${opt} in + - ) + case "${OPTARG}" in + help ) + display_help + exit 0 + ;; + block-number ) + BLOCK_NUMBER="${!OPTIND}" + OPTIND=$((OPTIND + 1)) + ;; + block-number=* ) + BLOCK_NUMBER="${OPTARG#*=}" + ;; + address ) + ADDRESS="${!OPTIND}" + OPTIND=$((OPTIND + 1)) + ;; + address=* ) + ADDRESS="${OPTARG#*=}" + ;; + slot ) + SLOT="${!OPTIND}" + OPTIND=$((OPTIND + 1)) + ;; + slot=* ) + SLOT="${OPTARG#*=}" + ;; + * ) + echo "Invalid option: --$OPTARG" 1>&2 + display_help + exit 1 + ;; + esac + ;; + h ) + display_help + exit 0 + ;; + b ) + BLOCK_NUMBER="${OPTARG}" + ;; + a ) + ADDRESS="${OPTARG}" + ;; + S ) + SLOT="${OPTARG}" + ;; + \? ) + echo "Invalid option: $OPTARG" 1>&2 + display_help + exit 1 + ;; + : ) + echo "Invalid option: $OPTARG requires an argument" 1>&2 + display_help + exit 1 + ;; + esac +done + +# Convert block number to 32-byte hex +BLOCK_NUMBER=$(printf "%064x" $BLOCK_NUMBER) +# Get the low and high 16 bytes +BLOCK_NUMBER_HIGH=0x${BLOCK_NUMBER:0:32} +BLOCK_NUMBER_LOW=0x${BLOCK_NUMBER:32:32} + +# TODO: allow slots of non hex values +# Left pad the slot value to 32 bytes +if [[ $SLOT == 0x* ]]; then + SLOT=${SLOT:2} +fi +while [ ${#SLOT} -lt 64 ]; do + SLOT="0$SLOT" +done +# Get the low and high 16 bytes +SLOT_HIGH=0x${SLOT:0:32} +SLOT_LOW=0x${SLOT:32:32} + +#TODO: Compute entry point selector from get_slot_value +HERODOTUS_FACT_QUERY='{ + "id": 1, + "jsonrpc": "2.0", + "method": "starknet_call", + "params": [ + { + "calldata": [ + "'$ADDRESS'", + "'$BLOCK_NUMBER_LOW'", + "'$BLOCK_NUMBER_HIGH'", + "'$SLOT_LOW'", + "'$SLOT_HIGH'" + ], + "entry_point_selector": "0x01d02b5043fe08831f4d75f1582080c274c6b4d5245fae933747a6990009adff", + "contract_address": "'$HERODOTUS_FACT_REGISTRY'" + }, + "pending" + ] +}' +echo "Query: $HERODOTUS_FACT_QUERY" + +echo +echo "Waiting for Herodotus to fulfill the slot fact..." +RES='{"jsonrpc":"2.0","id":1,"result":["0x1"]}' +while [ $(echo $RES | jq -r '.result[0]') == "0x1" ]; do + RES=$(curl $STARKNET_RPC_URL \ + -X 'POST' \ + -H "Content-Type: application/json" \ + -d "$HERODOTUS_FACT_QUERY" 2>/dev/null) + if [ $(echo $RES | jq -r '.result[0]') == "0x0" ]; then + VAL_LOW=$(echo $RES | jq -r '.result[1]') + VAL_HIGH=$(echo $RES | jq -r '.result[2]') + # Pad and remove 0x prefix + VAL_LOW=${VAL_LOW:2} + while [ ${#VAL_LOW} -lt 32 ]; do + VAL_LOW="0$VAL_LOW" + done + VAL_HIGH=${VAL_HIGH:2} + while [ ${#VAL_HIGH} -lt 32 ]; do + VAL_HIGH="0$VAL_HIGH" + done + VAL="0x$VAL_HIGH$VAL_LOW" + echo "Slot fact was fulfilled w/ value: $VAL" + exit 0 + fi + sleep 5 +done + +#TODO: verbose, ... From 423c085af6fe39188d5d57f87cb566f44ee74f2c Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Fri, 15 Mar 2024 13:31:14 -0500 Subject: [PATCH 2/7] Setup update_data_commitments_from_facts on BlobstreamX contract with tests and mock of evm_facts_registry --- scripts/request-data-commitments.sh | 3 +- src/blobstreamx.cairo | 53 +++++++++++++++++++++++++++++ src/interfaces.cairo | 7 ++++ src/lib.cairo | 1 + src/mocks/evm_facts_registry.cairo | 47 +++++++++++++++++++++++++ src/tests/common.cairo | 5 +++ src/tests/test_blobstreamx.cairo | 30 ++++++++++++++++ 7 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 src/mocks/evm_facts_registry.cairo diff --git a/scripts/request-data-commitments.sh b/scripts/request-data-commitments.sh index b9b2f14..5b08075 100755 --- a/scripts/request-data-commitments.sh +++ b/scripts/request-data-commitments.sh @@ -93,6 +93,7 @@ echo "Latest L1 block number: $L1_BLOCK_NUM" # -H "Content-Type: application/json" \ # -d '{"jsonrpc":"2.0","method":"starknet_blockNumber","params":[],"id":1}' +#TODO: latest -> block number LATEST_PROOF_NONCE_RES=$(curl $L1_RPC_URL \ -X POST \ -H "Content-Type: application/json" \ @@ -185,4 +186,4 @@ for slot in $(echo $DATA_COMMITMENT_SLOTS | tr ',' '\n' | tr -d '"'); do $SCRIPT_DIR/wait-for-herodotus-fulfill.sh -b $L1_BLOCK_NUM_DEC -a $BLOBSTREAMX_L1_ADDRESS -S $slot done -#TODO: Herodotus Fact registry mock, cheat code set values, do call to herodotus reg to update state_proofNonce & data commitments and latest l1 block?, PR for verifying attestations, use herodotus to get data commitment and verify attestation from DC +#TODO: do call to herodotus reg to update latest l1 block?, PR for verifying attestations, use herodotus to get data commitment and verify attestation from DC diff --git a/src/blobstreamx.cairo b/src/blobstreamx.cairo index 6327f55..b6c4fb4 100644 --- a/src/blobstreamx.cairo +++ b/src/blobstreamx.cairo @@ -4,6 +4,7 @@ mod blobstreamx { use blobstream_sn::interfaces::{ DataRoot, TendermintXErrors, IBlobstreamX, IDAOracle, ITendermintX }; + use blobstream_sn::mocks::evm_facts_registry::{IEVMFactsRegistryMockDispatcher, IEVMFactsRegistryMockDispatcherImpl}; use blobstream_sn::tree::binary::merkle_proof::BinaryMerkleProof; use core::starknet::event::EventEmitter; use core::traits::Into; @@ -32,6 +33,7 @@ mod blobstreamx { header_range_function_id: u256, next_header_function_id: u256, frozen: bool, + herodotus_facts_registry: ContractAddress, #[substorage(v0)] ownable: OwnableComponent::Storage, #[substorage(v0)] @@ -112,6 +114,7 @@ mod blobstreamx { header: u256, header_range_function_id: u256, next_header_function_id: u256, + herodotus_facts_registry: ContractAddress ) { self.data_commitment_max.write(1000); self.gateway.write(gateway); @@ -122,6 +125,7 @@ mod blobstreamx { self.header_range_function_id.write(header_range_function_id); self.next_header_function_id.write(next_header_function_id); self.frozen.write(false); + self.herodotus_facts_registry.write(herodotus_facts_registry); } #[abi(embed_v0)] @@ -184,6 +188,10 @@ mod blobstreamx { self.state_proof_nonce.read() } + fn get_state_data_commitment(self: @ContractState, state_nonce: u64) -> u256 { + self.state_data_commitments.read(state_nonce) + } + fn get_header_range_id(self: @ContractState) -> u256 { self.header_range_function_id.read() } @@ -209,6 +217,14 @@ mod blobstreamx { self.frozen.write(_frozen); } + fn get_herodotus_facts_registry(self: @ContractState) -> ContractAddress { + self.herodotus_facts_registry.read() + } + fn set_herodotus_facts_registry(ref self: ContractState, facts_registry: ContractAddress) { + self.ownable.assert_only_owner(); + self.herodotus_facts_registry.write(facts_registry); + } + /// Request a header_range proof for the next header hash and a data commitment for the block range [latest_block, _target_block). /// Used to skip from the latest block to the target block. /// @@ -376,5 +392,42 @@ mod blobstreamx { self.state_proof_nonce.write(proof_nonce + 1); self.latest_block.write(next_block); } + + fn update_data_commitments_from_facts(ref self: ContractState, l1_block: u256) { + // TODO: block_height_to_header_hash, latest_block, events + // TODO: Issue where this will only work if we are using the same data commitments from l1 from genesis + // Could be resolved if we only use the latest data commitments from l1 and check latest block + assert(!self.frozen.read(), Errors::ContractFrozen); + + let herodotus_facts_registry = IEVMFactsRegistryMockDispatcher { + contract_address: self.get_herodotus_facts_registry() + }; + + // TODO: use non-mock, and non-hardcoded values + let blobstreamx_l1_contract: felt252 = 0x48B257EC1610d04191cC2c528d0c940AdbE1E439; + + // Get the proof nonce for the new state data commitments + let blobstreamx_l1_proof_nonce_slot: u256 = 0xfc; + let new_state_proof_nonce = herodotus_facts_registry.get_slot_value(blobstreamx_l1_contract, l1_block, blobstreamx_l1_proof_nonce_slot); + assert!(new_state_proof_nonce.is_some(), "No proof nonce found for block {}", l1_block); + // TODO: error handling on try_into + let new_state_proof_nonce: u64 = new_state_proof_nonce.unwrap().try_into().unwrap(); + assert!(new_state_proof_nonce > self.get_state_proof_nonce(), "State proof nonce does not increase on block {}", l1_block); + + //// Loop though all the new state data commitments + let blobstreamx_l1_data_commitment_map_slot: u256 = 0xfe; + let mut current_dc_id = self.get_state_proof_nonce(); + while current_dc_id < new_state_proof_nonce { + let mut dc_slot_encoded: Bytes = BytesTrait::new_empty(); + dc_slot_encoded.append_u256(current_dc_id.into()); + dc_slot_encoded.append_u256(blobstreamx_l1_data_commitment_map_slot); + let dc_slot: u256 = dc_slot_encoded.keccak(); + let data_commitment = herodotus_facts_registry.get_slot_value(blobstreamx_l1_contract, l1_block, dc_slot); + assert!(data_commitment.is_some(), "No data commitment found for block {} and proof nonce {}", l1_block, current_dc_id); + self.state_data_commitments.write(current_dc_id, data_commitment.unwrap()); + current_dc_id += 1; + }; + self.state_proof_nonce.write(new_state_proof_nonce); + } } } diff --git a/src/interfaces.cairo b/src/interfaces.cairo index 38995ce..1c06be1 100644 --- a/src/interfaces.cairo +++ b/src/interfaces.cairo @@ -52,6 +52,8 @@ trait IBlobstreamX { fn get_gateway(self: @TContractState) -> ContractAddress; // Nonce for proof events. Must be incremented sequentially fn get_state_proof_nonce(self: @TContractState) -> u64; + // Data commitment for given nonce. + fn get_state_data_commitment(self: @TContractState, state_nonce: u64) -> u256; // Header range function id. fn get_header_range_id(self: @TContractState) -> u256; fn set_header_range_id(ref self: TContractState, _function_id: u256); @@ -61,6 +63,9 @@ trait IBlobstreamX { // Contract freezing state. fn get_frozen(self: @TContractState) -> bool; fn set_frozen(ref self: TContractState, _frozen: bool); + // Address to the Herodotus Fact Registry contract. + fn get_herodotus_facts_registry(self: @TContractState) -> ContractAddress; + fn set_herodotus_facts_registry(ref self: TContractState, facts_registry: ContractAddress); // Prove the validity of the header at the target block and a data commitment for the block range [latestBlock, _targetBlock). fn request_header_range(ref self: TContractState, _target_block: u64); // Commits the new header at targetBlock and the data commitment for the block range [trustedBlock, targetBlock). @@ -69,4 +74,6 @@ trait IBlobstreamX { fn request_next_header(ref self: TContractState); // Stores the new header for _trustedBlock + 1 and the data commitment for the block range [_trustedBlock, _trustedBlock + 1). fn commit_next_header(ref self: TContractState, _trusted_block: u64); + // Use the Herodotus Fact Registry to update data commitments from facts at the l1_block. + fn update_data_commitments_from_facts(ref self: TContractState, l1_block: u256); } diff --git a/src/lib.cairo b/src/lib.cairo index 1012d60..739b64d 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -2,6 +2,7 @@ pub mod blobstreamx; mod interfaces; mod mocks { + mod evm_facts_registry; mod function_verifier; mod upgradeable; } diff --git a/src/mocks/evm_facts_registry.cairo b/src/mocks/evm_facts_registry.cairo new file mode 100644 index 0000000..512d7ec --- /dev/null +++ b/src/mocks/evm_facts_registry.cairo @@ -0,0 +1,47 @@ +// Mock contract for the herodotus-on-starknet EVMFactsRegistry contract +// Only mocking the slot_values portion, the rest can be treated as a black box +// https://github.com/HerodotusDev/herodotus-on-starknet/blob/develop/src/core/evm_facts_registry.cairo + +#[starknet::interface] +trait IEVMFactsRegistryMock { + // @notice Returns a proven storage slot value + // @param account: The account to query + // @param block: The block number + // @param slot: The slot to query + // @return The value of the slot, if the slot is not proven, returns None + fn get_slot_value( + self: @TContractState, account: felt252, block: u256, slot: u256 + ) -> Option; + + // Testing only function, not safe nor included in the actual contract + fn set_slot_value( + ref self: TContractState, account: felt252, block: u256, slot: u256, value: u256 + ); +} + +#[starknet::contract] +mod EVMFactsRegistryMock { + #[storage] + struct Storage { + // (account_address, block_number, slot) => value + slot_values: LegacyMap::<(felt252, u256, u256), Option> + } + + #[constructor] + fn constructor(ref self: ContractState) {} + + #[abi(embed_v0)] + impl EVMFactsRegistryMockImpl of super::IEVMFactsRegistryMock { + fn get_slot_value( + self: @ContractState, account: felt252, block: u256, slot: u256 + ) -> Option { + self.slot_values.read((account, block, slot)) + } + + fn set_slot_value( + ref self: ContractState, account: felt252, block: u256, slot: u256, value: u256 + ) { + self.slot_values.write((account, block, slot), Option::Some(value)); + } + } +} diff --git a/src/tests/common.cairo b/src/tests/common.cairo index 4c43314..e8037ea 100644 --- a/src/tests/common.cairo +++ b/src/tests/common.cairo @@ -48,6 +48,10 @@ fn setup_base() -> ContractAddress { .deploy(@array![NEXT_HEADER_DIGEST.low.into(), NEXT_HEADER_DIGEST.high.into()]) .unwrap(); + // deploy the mock herodotus fact registry + let herodotus_registry_class = snf::declare('EVMFactsRegistryMock'); + let herodotus_facts_registry = herodotus_registry_class.deploy(@array![]).unwrap(); + // register verifier functions w/ gateway let header_range_func_id = gateway .register_function(OWNER(), header_range_verifier, 'HEADER_RANGE'); @@ -66,6 +70,7 @@ fn setup_base() -> ContractAddress { header_range_func_id.high.into(), next_header_func_id.low.into(), next_header_func_id.high.into(), + herodotus_facts_registry.into(), ]; blobstreamx_class.deploy(@calldata).unwrap() } diff --git a/src/tests/test_blobstreamx.cairo b/src/tests/test_blobstreamx.cairo index c525846..6a5a5e4 100644 --- a/src/tests/test_blobstreamx.cairo +++ b/src/tests/test_blobstreamx.cairo @@ -4,6 +4,7 @@ use blobstream_sn::interfaces::{ IBlobstreamXDispatcher, IBlobstreamXDispatcherTrait, Validator, ITendermintXDispatcher, ITendermintXDispatcherTrait }; +use blobstream_sn::mocks::evm_facts_registry::{IEVMFactsRegistryMockDispatcher, IEVMFactsRegistryMockDispatcherImpl}; use blobstream_sn::tests::common::{ setup_base, setup_spied, setup_succinct_gateway, TEST_START_BLOCK, TEST_END_BLOCK, TEST_HEADER, }; @@ -194,3 +195,32 @@ fn blobstreamx_frozen() { bsx.set_frozen(true); bsx.commit_header_range(0); } + +#[test] +fn blobstreamx_data_commitments_from_herodotus_facts() { + let bsx = setup_blobstreamx(); + + let hfr_addr = bsx.get_herodotus_facts_registry(); + let l1_addr: felt252 = 0x48B257EC1610d04191cC2c528d0c940AdbE1E439; + let l1_block_num: u256 = 0x100; + + let hfr_dispatcher = IEVMFactsRegistryMockDispatcher { contract_address: hfr_addr }; + hfr_dispatcher.set_slot_value(l1_addr, l1_block_num, 0xfc, 3.into()); // state_proofNonce at slot 0xfc + + // Slot pos = keccak256(abi.encode(map_key, state_dataCommitment_slot)) ie keccak256(abi.encode(map_key, 0xfe)) + let data_commitment1_slot: u256 = 0x457c8a48b4735f56b938837eb0a8a5f9c55f23c1a85767ce3b65c3e59d3d32b7; + let data_commitment1: u256 = 0xe1078369756a0b28e3b8cc1fa6e0133630ccdf9d2bd5bde1d40d197793c3c8b4; + hfr_dispatcher.set_slot_value(l1_addr, l1_block_num, data_commitment1_slot, data_commitment1); // state_dataCommitment[1] + + let data_commitment2_slot: u256 = 0xeeac6037a1009734a3fd8a7d8347d53da92d0725658242afb43dd0d755dbe634; + let data_commitment2: u256 = 0xc2b2d9e303ad14a5aeeda362d3d4177eedb43e1e0e4e6d42f6922f2ebfb23cc6; + hfr_dispatcher.set_slot_value(l1_addr, l1_block_num, data_commitment2_slot, data_commitment2); // state_dataCommitment[2] + + bsx.update_data_commitments_from_facts(l1_block_num); + assert!(bsx.get_state_proof_nonce() == 3, "state proof nonce invalid"); + assert!(bsx.get_state_data_commitment(0) == 0, "data commitment 0 invalid"); + assert!(bsx.get_state_data_commitment(1) == data_commitment1, "data commitment 1 invalid"); + assert!(bsx.get_state_data_commitment(2) == data_commitment2, "data commitment 2 invalid"); + assert!(bsx.get_state_data_commitment(3) == 0, "data commitment 3 invalid"); + // TODO: test invalid inputs and things +} From f9e4632ec52de8da8bca3312fef5d91531c01cc5 Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Fri, 15 Mar 2024 13:36:40 -0500 Subject: [PATCH 3/7] Scarb fmt --- src/blobstreamx.cairo | 23 ++++++++++++++++++----- src/tests/test_blobstreamx.cairo | 30 +++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/blobstreamx.cairo b/src/blobstreamx.cairo index b6c4fb4..fb13e3d 100644 --- a/src/blobstreamx.cairo +++ b/src/blobstreamx.cairo @@ -4,7 +4,9 @@ mod blobstreamx { use blobstream_sn::interfaces::{ DataRoot, TendermintXErrors, IBlobstreamX, IDAOracle, ITendermintX }; - use blobstream_sn::mocks::evm_facts_registry::{IEVMFactsRegistryMockDispatcher, IEVMFactsRegistryMockDispatcherImpl}; + use blobstream_sn::mocks::evm_facts_registry::{ + IEVMFactsRegistryMockDispatcher, IEVMFactsRegistryMockDispatcherImpl + }; use blobstream_sn::tree::binary::merkle_proof::BinaryMerkleProof; use core::starknet::event::EventEmitter; use core::traits::Into; @@ -408,11 +410,16 @@ mod blobstreamx { // Get the proof nonce for the new state data commitments let blobstreamx_l1_proof_nonce_slot: u256 = 0xfc; - let new_state_proof_nonce = herodotus_facts_registry.get_slot_value(blobstreamx_l1_contract, l1_block, blobstreamx_l1_proof_nonce_slot); + let new_state_proof_nonce = herodotus_facts_registry + .get_slot_value(blobstreamx_l1_contract, l1_block, blobstreamx_l1_proof_nonce_slot); assert!(new_state_proof_nonce.is_some(), "No proof nonce found for block {}", l1_block); // TODO: error handling on try_into let new_state_proof_nonce: u64 = new_state_proof_nonce.unwrap().try_into().unwrap(); - assert!(new_state_proof_nonce > self.get_state_proof_nonce(), "State proof nonce does not increase on block {}", l1_block); + assert!( + new_state_proof_nonce > self.get_state_proof_nonce(), + "State proof nonce does not increase on block {}", + l1_block + ); //// Loop though all the new state data commitments let blobstreamx_l1_data_commitment_map_slot: u256 = 0xfe; @@ -422,8 +429,14 @@ mod blobstreamx { dc_slot_encoded.append_u256(current_dc_id.into()); dc_slot_encoded.append_u256(blobstreamx_l1_data_commitment_map_slot); let dc_slot: u256 = dc_slot_encoded.keccak(); - let data_commitment = herodotus_facts_registry.get_slot_value(blobstreamx_l1_contract, l1_block, dc_slot); - assert!(data_commitment.is_some(), "No data commitment found for block {} and proof nonce {}", l1_block, current_dc_id); + let data_commitment = herodotus_facts_registry + .get_slot_value(blobstreamx_l1_contract, l1_block, dc_slot); + assert!( + data_commitment.is_some(), + "No data commitment found for block {} and proof nonce {}", + l1_block, + current_dc_id + ); self.state_data_commitments.write(current_dc_id, data_commitment.unwrap()); current_dc_id += 1; }; diff --git a/src/tests/test_blobstreamx.cairo b/src/tests/test_blobstreamx.cairo index 6a5a5e4..8ce50d2 100644 --- a/src/tests/test_blobstreamx.cairo +++ b/src/tests/test_blobstreamx.cairo @@ -4,7 +4,9 @@ use blobstream_sn::interfaces::{ IBlobstreamXDispatcher, IBlobstreamXDispatcherTrait, Validator, ITendermintXDispatcher, ITendermintXDispatcherTrait }; -use blobstream_sn::mocks::evm_facts_registry::{IEVMFactsRegistryMockDispatcher, IEVMFactsRegistryMockDispatcherImpl}; +use blobstream_sn::mocks::evm_facts_registry::{ + IEVMFactsRegistryMockDispatcher, IEVMFactsRegistryMockDispatcherImpl +}; use blobstream_sn::tests::common::{ setup_base, setup_spied, setup_succinct_gateway, TEST_START_BLOCK, TEST_END_BLOCK, TEST_HEADER, }; @@ -205,16 +207,30 @@ fn blobstreamx_data_commitments_from_herodotus_facts() { let l1_block_num: u256 = 0x100; let hfr_dispatcher = IEVMFactsRegistryMockDispatcher { contract_address: hfr_addr }; - hfr_dispatcher.set_slot_value(l1_addr, l1_block_num, 0xfc, 3.into()); // state_proofNonce at slot 0xfc + + // Set the state_proofNonce slot to 3 + + hfr_dispatcher + .set_slot_value(l1_addr, l1_block_num, 0xfc, 3.into()); // state_proofNonce at slot 0xfc + + // Add the 2 data commitments // Slot pos = keccak256(abi.encode(map_key, state_dataCommitment_slot)) ie keccak256(abi.encode(map_key, 0xfe)) - let data_commitment1_slot: u256 = 0x457c8a48b4735f56b938837eb0a8a5f9c55f23c1a85767ce3b65c3e59d3d32b7; + let data_commitment1_slot: u256 = + 0x457c8a48b4735f56b938837eb0a8a5f9c55f23c1a85767ce3b65c3e59d3d32b7; let data_commitment1: u256 = 0xe1078369756a0b28e3b8cc1fa6e0133630ccdf9d2bd5bde1d40d197793c3c8b4; - hfr_dispatcher.set_slot_value(l1_addr, l1_block_num, data_commitment1_slot, data_commitment1); // state_dataCommitment[1] + hfr_dispatcher + .set_slot_value( + l1_addr, l1_block_num, data_commitment1_slot, data_commitment1 + ); // state_dataCommitment[1] - let data_commitment2_slot: u256 = 0xeeac6037a1009734a3fd8a7d8347d53da92d0725658242afb43dd0d755dbe634; + let data_commitment2_slot: u256 = + 0xeeac6037a1009734a3fd8a7d8347d53da92d0725658242afb43dd0d755dbe634; let data_commitment2: u256 = 0xc2b2d9e303ad14a5aeeda362d3d4177eedb43e1e0e4e6d42f6922f2ebfb23cc6; - hfr_dispatcher.set_slot_value(l1_addr, l1_block_num, data_commitment2_slot, data_commitment2); // state_dataCommitment[2] + hfr_dispatcher + .set_slot_value( + l1_addr, l1_block_num, data_commitment2_slot, data_commitment2 + ); // state_dataCommitment[2] bsx.update_data_commitments_from_facts(l1_block_num); assert!(bsx.get_state_proof_nonce() == 3, "state proof nonce invalid"); @@ -222,5 +238,5 @@ fn blobstreamx_data_commitments_from_herodotus_facts() { assert!(bsx.get_state_data_commitment(1) == data_commitment1, "data commitment 1 invalid"); assert!(bsx.get_state_data_commitment(2) == data_commitment2, "data commitment 2 invalid"); assert!(bsx.get_state_data_commitment(3) == 0, "data commitment 3 invalid"); - // TODO: test invalid inputs and things +// TODO: test invalid inputs and things } From afbea4369097b49450afb85062cce6ac9ee23cda Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Mon, 18 Mar 2024 10:27:52 -0500 Subject: [PATCH 4/7] Test errors in fact registry, fix hardcoded values, scripts properly parse arguments, and verbose options --- scripts/request-data-commitments.sh | 138 +++++++++++++++++--------- scripts/wait-for-herodotus-fulfill.sh | 104 +++++++++---------- src/blobstreamx.cairo | 40 ++++---- src/interfaces.cairo | 3 + src/tests/common.cairo | 4 + src/tests/test_blobstreamx.cairo | 84 ++++++++++++++-- 6 files changed, 247 insertions(+), 126 deletions(-) diff --git a/scripts/request-data-commitments.sh b/scripts/request-data-commitments.sh index 5b08075..a8412c4 100755 --- a/scripts/request-data-commitments.sh +++ b/scripts/request-data-commitments.sh @@ -12,55 +12,87 @@ STATE_PROOF_NONCE_SLOT="0x000000000000000000000000000000000000000000000000000000 STATE_DATA_COMMITMENT_MAP_SLOT="0x00000000000000000000000000000000000000000000000000000000000000fe" # Optional arguments -# TODO: To public api? L1_RPC_URL="https://sepolia.infura.io/v3/bed8a8401c894421bd7cd31050e7ced6" STARKNET_RPC_URL="https://starknet-sepolia.infura.io/v3/bed8a8401c894421bd7cd31050e7ced6" BLOBSTREAMX_L1_ADDRESS="0x48B257EC1610d04191cC2c528d0c940AdbE1E439" -#TODO +#TODO : Change address to the Sepolia address once it is deployed BLOBSTREAMX_STARKNET_ADDRESS="0x48B257EC1610d04191cC2c528d0c940AdbE1E439" +NO_SEND=false +VERBOSE=false +WAIT=true + + SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" WORK_DIR=$SCRIPT_DIR/.. -#TODO: w/o webhook, verbose, w/o header access -#TODO: resume from id - display_help() { echo "Usage: $0 [option...] {arguments...}" echo + echo " -b, --blobstreamx-l1 ADDR BlobstreamX contract address on L1" + echo " (default: $BLOBSTREAMX_L1_ADDRESS (SEPOLIA))" + echo " -B, --blobstreamx-starknet ADDR BlobstreamX contract address on Starknet" + echo " (default: $BLOBSTREAMX_STARKNET_ADDRESS (SEPOLIA))" + echo " -r, --l1-rpc URL URL of the L1 RPC server" + echo " (default: $L1_RPC_URL)" + echo " -s, --starknet-rpc URL URL of the Starknet RPC server" + echo " (default: $STARKNET_RPC_URL)" + + echo echo " -h, --help display help" - echo " -r, --l1-rpc=URL URL of the L1 RPC server (default: $L1_RPC_URL)" - echo " -s, --starknet-rpc=URL URL of the Starknet RPC server (default: $STARKNET_RPC_URL)" - echo " -b, --blobstreamx-l1=ADDR BlobstreamX contract address on L1 (default: $BLOBSTREAMX_L1_ADDRESS)" - echo " -B, --blobstreamx-starknet=ADDR BlobstreamX contract address on Starknet (default: $BLOBSTREAMX_STARKNET_ADDRESS)" + echo " -n, --no-send Do not send the batch query to Herodotus, only print the query" + echo " -N, --no-wait Do not wait for the state proof nonce and data commitments to be relayed" + echo " -v, --verbose verbose output" echo echo "Example: $0" } -#TODO: Add support for optional arguments +# Transform long options to short ones +for arg in "$@"; do + shift + case "$arg" in + "--blobstreamx-l1") set -- "$@" "-b" ;; + "--blobstreamx-starknet") set -- "$@" "-B" ;; + "--l1-rpc") set -- "$@" "-r" ;; + "--starknet-rpc") set -- "$@" "-s" ;; + "--help") set -- "$@" "-h" ;; + "--no-send") set -- "$@" "-n" ;; + "--no-wait") set -- "$@" "-N" ;; + "--verbose") set -- "$@" "-v" ;; + *) set -- "$@" "$arg" + esac +done # Parse command line arguments -while getopts ":hr:-:" opt; do +while getopts ":hnvNb:B:r:s:-:" opt; do case ${opt} in - - ) - case "${OPTARG}" in - help ) - display_help - exit 0 - ;; - * ) - echo "Invalid option: --$OPTARG" 1>&2 - display_help - exit 1 - ;; - esac - ;; h ) display_help exit 0 ;; + n ) + NO_SEND=true + ;; + v ) + VERBOSE=true + ;; + N ) + WAIT=false + ;; + b ) + BLOBSTREAMX_L1_ADDRESS="$OPTARG" + ;; + B ) + BLOBSTREAMX_STARKNET_ADDRESS="$OPTARG" + ;; + r ) + L1_RPC_URL="$OPTARG" + ;; + s ) + STARKNET_RPC_URL="$OPTARG" + ;; \? ) echo "Invalid option: $OPTARG" 1>&2 display_help @@ -74,7 +106,8 @@ while getopts ":hr:-:" opt; do esac done -if [ -z "$HERODOTUS_API_KEY" ]; then +# Check if HERODOTUS_API_KEY is set if not set and no-send is not set, exit +if [ -z "$HERODOTUS_API_KEY" ] && [ "$NO_SEND" = false ]; then echo "HERODOTUS_API_KEY is not set. Get your API key from https://dashboard.herodotus.dev by signing up with your GitHub." exit 1 fi @@ -84,36 +117,30 @@ L1_BLOCK_NUM_RES=$(curl $L1_RPC_URL \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' 2>/dev/null) L1_BLOCK_NUM=$(echo $L1_BLOCK_NUM_RES | jq -r '.result') -echo "Latest L1 block number: $L1_BLOCK_NUM" -# echo -# echo "Getting current Starknet block number..." -# curl $STARKNET_RPC_URL \ -# -X POST \ -# -H "Content-Type: application/json" \ -# -d '{"jsonrpc":"2.0","method":"starknet_blockNumber","params":[],"id":1}' - -#TODO: latest -> block number LATEST_PROOF_NONCE_RES=$(curl $L1_RPC_URL \ -X POST \ -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","method":"eth_getStorageAt","params":["'"$BLOBSTREAMX_L1_ADDRESS"'","'"$STATE_PROOF_NONCE_SLOT"'","latest"],"id":1}' 2>/dev/null) + -d '{"jsonrpc":"2.0","method":"eth_getStorageAt","params":["'"$BLOBSTREAMX_L1_ADDRESS"'","'"$STATE_PROOF_NONCE_SLOT"'","'"$L1_BLOCK_NUM"'"],"id":1}' 2>/dev/null) LATEST_PROOF_NONCE=$(echo $LATEST_PROOF_NONCE_RES | jq -r '.result') -echo "Latest L1 state_proofNonce: $LATEST_PROOF_NONCE" -# TODO : This or latest option for just one proofNonce +#TODO: Do a call to get_state_proof_nonce() once the function is deployed & replace hardcoded #STARKNET_PROOF_NONCE_RES=$(curl $STARKNET_RPC_URL \ # -X POST \ # -H "Content-Type: application/json" \ # -d '{"jsonrpc":"2.0","method":"starknet_getState","params":["'"$BLOBSTREAMX_STARKNET_ADDRESS"'",'"$LATEST_PROOF_NONCE"'],"id":1}' 2>/dev/null) -STARKNET_PROOF_NONCE_RES=$(echo '{"jsonrpc":"2.0","id":1,"result":"0x00000000000000000000000000000000000000000000000000000000000006a4"}') +STARKNET_PROOF_NONCE_RES=$(echo '{"jsonrpc":"2.0","id":1,"result":"0x00000000000000000000000000000000000000000000000000000000000006cb"}') STARKNET_PROOF_NONCE=$(echo $STARKNET_PROOF_NONCE_RES | jq -r '.result') -echo "Latest Starknet state_proofNonce: $STARKNET_PROOF_NONCE" + +if [ "$VERBOSE" = true ]; then + echo "Latest L1 block number: $L1_BLOCK_NUM" + echo "Latest L1 state_proofNonce: $LATEST_PROOF_NONCE" + echo "Latest Starknet state_proofNonce: $STARKNET_PROOF_NONCE" +fi DATA_COMMITMENT_SLOTS='' -# TODO: Think about edge cases # Loop through each missing state_proofNonce to build the batch query slots -for ((i = STARKNET_PROOF_NONCE; i <= LATEST_PROOF_NONCE - 1; i++)); do +for ((i = $STARKNET_PROOF_NONCE; i <= LATEST_PROOF_NONCE - 1; i++)); do # Data commitment slot for each proofNonce is located at # keccak256(abi.encode(proofNonce, STATE_DATA_COMMITMENT_MAP_SLOT)) DATA_COMMITMENT_ENCODED_SLOT=$(printf "%064x%064x" $i $STATE_DATA_COMMITMENT_MAP_SLOT) @@ -165,25 +192,38 @@ HERODOTUS_QUERY='{ HERODOTUS_QUERY=$(echo $HERODOTUS_QUERY | jq '.') HERODOTUS_QUERY_URL=$(echo $HERODOTUS_API_URL | sed 's/\/$//')'/submit-batch-query?apiKey='$HERODOTUS_API_KEY +if [ "$VERBOSE" = true ] || [ "$NO_SEND" = true ]; then + echo + echo "Batch query to Herodotus API:" + echo "$HERODOTUS_QUERY" +fi + +if [ "$NO_SEND" = true ]; then + exit 0 +fi + echo echo "Submitting batch query to Herodotus API..." -echo "Query: $HERODOTUS_QUERY" -curl -X 'POST' \ +HERODOTUS_ID=$(curl -X 'POST' \ $HERODOTUS_QUERY_URL \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ - -d "$HERODOTUS_QUERY" + -d "$HERODOTUS_QUERY" 2>/dev/null) +echo "Batch query submitted. Herodotus Internal ID: $HERODOTUS_ID" + +if [ "$WAIT" = false ]; then + echo + echo "Query fulfillment can take some time, please wait for state proof nonce and data commitments to be relayed." + exit 0 +fi -#TODO: Wait option # Wait for state proof nonce slot to be relayed echo echo "Waiting for state proof nonce slot $STATE_PROOF_NONCE_SLOT to be relayed..." -$SCRIPT_DIR/wait-for-herodotus-fulfill.sh -b $L1_BLOCK_NUM_DEC -a $BLOBSTREAMX_L1_ADDRESS -S $STATE_PROOF_NONCE_SLOT +$SCRIPT_DIR/wait-for-herodotus-fulfill.sh -b $L1_BLOCK_NUM_DEC -a $BLOBSTREAMX_L1_ADDRESS -S $STATE_PROOF_NONCE_SLOT $([ "$VERBOSE" = true ] && echo "-v") # Loop thru each data commitment slot to wait for the data to be relayed (comma separated) for slot in $(echo $DATA_COMMITMENT_SLOTS | tr ',' '\n' | tr -d '"'); do echo echo "Waiting for data commitment at slot $slot to be relayed..." - $SCRIPT_DIR/wait-for-herodotus-fulfill.sh -b $L1_BLOCK_NUM_DEC -a $BLOBSTREAMX_L1_ADDRESS -S $slot + $SCRIPT_DIR/wait-for-herodotus-fulfill.sh -b $L1_BLOCK_NUM_DEC -a $BLOBSTREAMX_L1_ADDRESS -S $slot $([ "$VERBOSE" = true ] && echo "-v") done - -#TODO: do call to herodotus reg to update latest l1 block?, PR for verifying attestations, use herodotus to get data commitment and verify attestation from DC diff --git a/scripts/wait-for-herodotus-fulfill.sh b/scripts/wait-for-herodotus-fulfill.sh index 3308528..79554c3 100755 --- a/scripts/wait-for-herodotus-fulfill.sh +++ b/scripts/wait-for-herodotus-fulfill.sh @@ -4,72 +4,66 @@ # Monitors the Herodotus Fact registry on Starknet for the requested slot values. # Constants +HERODOTUS_GET_SLOT_VALUE_SELECTOR="0x01d02b5043fe08831f4d75f1582080c274c6b4d5245fae933747a6990009adff" # Optional arguments STARKNET_RPC_URL="https://starknet-sepolia.infura.io/v3/bed8a8401c894421bd7cd31050e7ced6" HERODOTUS_FACT_REGISTRY="0x07d3550237ecf2d6ddef9b78e59b38647ee511467fe000ce276f245a006b40bc" +VERBOSE=false + display_help() { echo "Usage: $0 [option...] {arguments...}" echo - echo " -h, --help display help" - echo " -s, --starknet-rpc=URL URL of the Starknet RPC server (default: $STARKNET_RPC_URL)" - echo " -F, --herodotus-fact-registry=ADDRESS Address of the Herodotus Fact registry on Starknet (default: $HERODOTUS_FACT_REGISTRY)" + echo " -s, --starknet-rpc URL URL of the Starknet RPC server" + echo " (default: $STARKNET_RPC_URL)" + echo " -F, --herodotus-fact-registry ADDRESS Address of the Herodotus Fact registry on Starknet" + echo " (default: $HERODOTUS_FACT_REGISTRY (SN_SEPOLIA))" echo - echo " -b, --block-number=NUMBER Block number for the slot fact (required)" - echo " -a, --address=ADDRESS Address of the contract for the slot fact (required)" - echo " -S, --slot=NUMBER Slot number for the slot fact (required)" + echo " -b, --block-number NUMBER Block number for the slot fact (required)" + echo " -a, --address ADDRESS Address of the contract for the slot fact (required)" + echo " -S, --slot NUMBER Slot number for the slot fact - Hex (required)" + + echo + echo " -h, --help display help" + echo " -v, --verbose display verbose output" echo echo "Example: $0" } -#TODO: Add support for optional arguments -#TODO: Add support for required arguments -#TODO: parse arg formats +# Transform long options to short ones +for arg in "$@"; do + shift + case "$arg" in + "--starknet-rpc") set -- "$@" "-s" ;; + "--herodotus-fact-registry") set -- "$@" "-F" ;; + "--block-number") set -- "$@" "-b" ;; + "--address") set -- "$@" "-a" ;; + "--slot") set -- "$@" "-S" ;; + "--help") set -- "$@" "-h" ;; + "--verbose") set -- "$@" "-v" ;; + *) set -- "$@" "$arg" + esac +done # Parse command line arguments -while getopts ":hb:a:S:-:" opt; do +while getopts ":hvs:F:b:a:S:" opt; do case ${opt} in - - ) - case "${OPTARG}" in - help ) - display_help - exit 0 - ;; - block-number ) - BLOCK_NUMBER="${!OPTIND}" - OPTIND=$((OPTIND + 1)) - ;; - block-number=* ) - BLOCK_NUMBER="${OPTARG#*=}" - ;; - address ) - ADDRESS="${!OPTIND}" - OPTIND=$((OPTIND + 1)) - ;; - address=* ) - ADDRESS="${OPTARG#*=}" - ;; - slot ) - SLOT="${!OPTIND}" - OPTIND=$((OPTIND + 1)) - ;; - slot=* ) - SLOT="${OPTARG#*=}" - ;; - * ) - echo "Invalid option: --$OPTARG" 1>&2 - display_help - exit 1 - ;; - esac - ;; h ) display_help exit 0 ;; + v ) + VERBOSE=true + ;; + s ) + STARKNET_RPC_URL="${OPTARG}" + ;; + F ) + HERODOTUS_FACT_REGISTRY="${OPTARG}" + ;; b ) BLOCK_NUMBER="${OPTARG}" ;; @@ -92,13 +86,18 @@ while getopts ":hb:a:S:-:" opt; do esac done +if [ -z $BLOCK_NUMBER ] || [ -z $ADDRESS ] || [ -z $SLOT ]; then + echo "Missing required arguments: block-number, address, slot" 1>&2 + display_help + exit 1 +fi + # Convert block number to 32-byte hex BLOCK_NUMBER=$(printf "%064x" $BLOCK_NUMBER) # Get the low and high 16 bytes BLOCK_NUMBER_HIGH=0x${BLOCK_NUMBER:0:32} BLOCK_NUMBER_LOW=0x${BLOCK_NUMBER:32:32} -# TODO: allow slots of non hex values # Left pad the slot value to 32 bytes if [[ $SLOT == 0x* ]]; then SLOT=${SLOT:2} @@ -110,7 +109,6 @@ done SLOT_HIGH=0x${SLOT:0:32} SLOT_LOW=0x${SLOT:32:32} -#TODO: Compute entry point selector from get_slot_value HERODOTUS_FACT_QUERY='{ "id": 1, "jsonrpc": "2.0", @@ -124,16 +122,20 @@ HERODOTUS_FACT_QUERY='{ "'$SLOT_LOW'", "'$SLOT_HIGH'" ], - "entry_point_selector": "0x01d02b5043fe08831f4d75f1582080c274c6b4d5245fae933747a6990009adff", + "entry_point_selector": "'$HERODOTUS_GET_SLOT_VALUE_SELECTOR'", "contract_address": "'$HERODOTUS_FACT_REGISTRY'" }, "pending" ] }' -echo "Query: $HERODOTUS_FACT_QUERY" -echo -echo "Waiting for Herodotus to fulfill the slot fact..." +if [ $VERBOSE == true ]; then + echo "Query: $HERODOTUS_FACT_QUERY" + + echo + echo "Waiting for Herodotus to fulfill the slot fact..." +fi + RES='{"jsonrpc":"2.0","id":1,"result":["0x1"]}' while [ $(echo $RES | jq -r '.result[0]') == "0x1" ]; do RES=$(curl $STARKNET_RPC_URL \ @@ -158,5 +160,3 @@ while [ $(echo $RES | jq -r '.result[0]') == "0x1" ]; do fi sleep 5 done - -#TODO: verbose, ... diff --git a/src/blobstreamx.cairo b/src/blobstreamx.cairo index fb13e3d..899bb61 100644 --- a/src/blobstreamx.cairo +++ b/src/blobstreamx.cairo @@ -36,6 +36,7 @@ mod blobstreamx { next_header_function_id: u256, frozen: bool, herodotus_facts_registry: ContractAddress, + blobstreamx_l1_contract: felt252, #[substorage(v0)] ownable: OwnableComponent::Storage, #[substorage(v0)] @@ -116,7 +117,8 @@ mod blobstreamx { header: u256, header_range_function_id: u256, next_header_function_id: u256, - herodotus_facts_registry: ContractAddress + herodotus_facts_registry: ContractAddress, + blobstreamx_l1_contract: felt252 ) { self.data_commitment_max.write(1000); self.gateway.write(gateway); @@ -128,6 +130,7 @@ mod blobstreamx { self.next_header_function_id.write(next_header_function_id); self.frozen.write(false); self.herodotus_facts_registry.write(herodotus_facts_registry); + self.blobstreamx_l1_contract.write(blobstreamx_l1_contract); } #[abi(embed_v0)] @@ -226,6 +229,13 @@ mod blobstreamx { self.ownable.assert_only_owner(); self.herodotus_facts_registry.write(facts_registry); } + fn get_blobstreamx_l1_contract(self: @ContractState) -> felt252 { + self.blobstreamx_l1_contract.read() + } + fn set_blobstreamx_l1_contract(ref self: ContractState, l1_contract: felt252) { + self.ownable.assert_only_owner(); + self.blobstreamx_l1_contract.write(l1_contract); + } /// Request a header_range proof for the next header hash and a data commitment for the block range [latest_block, _target_block). /// Used to skip from the latest block to the target block. @@ -395,25 +405,21 @@ mod blobstreamx { self.latest_block.write(next_block); } + /// This assumes all existing data_commitments mappings match L1 Blobstream fn update_data_commitments_from_facts(ref self: ContractState, l1_block: u256) { - // TODO: block_height_to_header_hash, latest_block, events - // TODO: Issue where this will only work if we are using the same data commitments from l1 from genesis - // Could be resolved if we only use the latest data commitments from l1 and check latest block assert(!self.frozen.read(), Errors::ContractFrozen); let herodotus_facts_registry = IEVMFactsRegistryMockDispatcher { contract_address: self.get_herodotus_facts_registry() }; - // TODO: use non-mock, and non-hardcoded values - let blobstreamx_l1_contract: felt252 = 0x48B257EC1610d04191cC2c528d0c940AdbE1E439; - // Get the proof nonce for the new state data commitments let blobstreamx_l1_proof_nonce_slot: u256 = 0xfc; let new_state_proof_nonce = herodotus_facts_registry - .get_slot_value(blobstreamx_l1_contract, l1_block, blobstreamx_l1_proof_nonce_slot); + .get_slot_value( + self.blobstreamx_l1_contract.read(), l1_block, blobstreamx_l1_proof_nonce_slot + ); assert!(new_state_proof_nonce.is_some(), "No proof nonce found for block {}", l1_block); - // TODO: error handling on try_into let new_state_proof_nonce: u64 = new_state_proof_nonce.unwrap().try_into().unwrap(); assert!( new_state_proof_nonce > self.get_state_proof_nonce(), @@ -421,24 +427,24 @@ mod blobstreamx { l1_block ); - //// Loop though all the new state data commitments + // Loop though all the new state data commitments let blobstreamx_l1_data_commitment_map_slot: u256 = 0xfe; - let mut current_dc_id = self.get_state_proof_nonce(); - while current_dc_id < new_state_proof_nonce { + let mut current_proof_nonce = self.get_state_proof_nonce(); + while current_proof_nonce < new_state_proof_nonce { let mut dc_slot_encoded: Bytes = BytesTrait::new_empty(); - dc_slot_encoded.append_u256(current_dc_id.into()); + dc_slot_encoded.append_u256(current_proof_nonce.into()); dc_slot_encoded.append_u256(blobstreamx_l1_data_commitment_map_slot); let dc_slot: u256 = dc_slot_encoded.keccak(); let data_commitment = herodotus_facts_registry - .get_slot_value(blobstreamx_l1_contract, l1_block, dc_slot); + .get_slot_value(self.blobstreamx_l1_contract.read(), l1_block, dc_slot); assert!( data_commitment.is_some(), "No data commitment found for block {} and proof nonce {}", l1_block, - current_dc_id + current_proof_nonce ); - self.state_data_commitments.write(current_dc_id, data_commitment.unwrap()); - current_dc_id += 1; + self.state_data_commitments.write(current_proof_nonce, data_commitment.unwrap()); + current_proof_nonce += 1; }; self.state_proof_nonce.write(new_state_proof_nonce); } diff --git a/src/interfaces.cairo b/src/interfaces.cairo index 1c06be1..7ebbf36 100644 --- a/src/interfaces.cairo +++ b/src/interfaces.cairo @@ -66,6 +66,9 @@ trait IBlobstreamX { // Address to the Herodotus Fact Registry contract. fn get_herodotus_facts_registry(self: @TContractState) -> ContractAddress; fn set_herodotus_facts_registry(ref self: TContractState, facts_registry: ContractAddress); + // L1 Address to the BlobstreamX proxy contract. + fn get_blobstreamx_l1_contract(self: @TContractState) -> felt252; + fn set_blobstreamx_l1_contract(ref self: TContractState, l1_contract: felt252); // Prove the validity of the header at the target block and a data commitment for the block range [latestBlock, _targetBlock). fn request_header_range(ref self: TContractState, _target_block: u64); // Commits the new header at targetBlock and the data commitment for the block range [trustedBlock, targetBlock). diff --git a/src/tests/common.cairo b/src/tests/common.cairo index e8037ea..f37d030 100644 --- a/src/tests/common.cairo +++ b/src/tests/common.cairo @@ -17,6 +17,9 @@ const HEADER_RANGE_DIGEST: u256 = 0xb646edd6dbb2e5482b2449404cf1888b8f4cd6958c79 const NEXT_HEADER_DIGEST: u256 = 0xfd6c88812a160ff288fe557111815b3433c539c77a3561086cfcdd9482bceb8; const TOTAL_SUPPLY: u256 = 0x100000000000000000000000000000001; +// Current Sepolia proxy contract address +const BLOBSTREAMX_L1_ADDRESS: felt252 = 0x48B257EC1610d04191cC2c528d0c940AdbE1E439; + fn setup_base() -> ContractAddress { // deploy the token associated with the fee vault let mut calldata = array![]; @@ -71,6 +74,7 @@ fn setup_base() -> ContractAddress { next_header_func_id.low.into(), next_header_func_id.high.into(), herodotus_facts_registry.into(), + BLOBSTREAMX_L1_ADDRESS.into() ]; blobstreamx_class.deploy(@calldata).unwrap() } diff --git a/src/tests/test_blobstreamx.cairo b/src/tests/test_blobstreamx.cairo index 8ce50d2..168a082 100644 --- a/src/tests/test_blobstreamx.cairo +++ b/src/tests/test_blobstreamx.cairo @@ -9,6 +9,7 @@ use blobstream_sn::mocks::evm_facts_registry::{ }; use blobstream_sn::tests::common::{ setup_base, setup_spied, setup_succinct_gateway, TEST_START_BLOCK, TEST_END_BLOCK, TEST_HEADER, + BLOBSTREAMX_L1_ADDRESS }; use openzeppelin::tests::utils::constants::OWNER; use snforge_std as snf; @@ -203,7 +204,6 @@ fn blobstreamx_data_commitments_from_herodotus_facts() { let bsx = setup_blobstreamx(); let hfr_addr = bsx.get_herodotus_facts_registry(); - let l1_addr: felt252 = 0x48B257EC1610d04191cC2c528d0c940AdbE1E439; let l1_block_num: u256 = 0x100; let hfr_dispatcher = IEVMFactsRegistryMockDispatcher { contract_address: hfr_addr }; @@ -211,26 +211,28 @@ fn blobstreamx_data_commitments_from_herodotus_facts() { // Set the state_proofNonce slot to 3 hfr_dispatcher - .set_slot_value(l1_addr, l1_block_num, 0xfc, 3.into()); // state_proofNonce at slot 0xfc + .set_slot_value( + BLOBSTREAMX_L1_ADDRESS, l1_block_num, 0xfc, 3.into() + ); // state_proofNonce at slot 0xfc // Add the 2 data commitments - // Slot pos = keccak256(abi.encode(map_key, state_dataCommitment_slot)) ie keccak256(abi.encode(map_key, 0xfe)) + // Slot pos = keccak256(abi.encode(map_key, state_dataCommitments_slot)) ie keccak256(abi.encode(map_key, 0xfe)) let data_commitment1_slot: u256 = 0x457c8a48b4735f56b938837eb0a8a5f9c55f23c1a85767ce3b65c3e59d3d32b7; let data_commitment1: u256 = 0xe1078369756a0b28e3b8cc1fa6e0133630ccdf9d2bd5bde1d40d197793c3c8b4; hfr_dispatcher .set_slot_value( - l1_addr, l1_block_num, data_commitment1_slot, data_commitment1 - ); // state_dataCommitment[1] + BLOBSTREAMX_L1_ADDRESS, l1_block_num, data_commitment1_slot, data_commitment1 + ); // state_dataCommitments[1] let data_commitment2_slot: u256 = 0xeeac6037a1009734a3fd8a7d8347d53da92d0725658242afb43dd0d755dbe634; let data_commitment2: u256 = 0xc2b2d9e303ad14a5aeeda362d3d4177eedb43e1e0e4e6d42f6922f2ebfb23cc6; hfr_dispatcher .set_slot_value( - l1_addr, l1_block_num, data_commitment2_slot, data_commitment2 - ); // state_dataCommitment[2] + BLOBSTREAMX_L1_ADDRESS, l1_block_num, data_commitment2_slot, data_commitment2 + ); // state_dataCommitments[2] bsx.update_data_commitments_from_facts(l1_block_num); assert!(bsx.get_state_proof_nonce() == 3, "state proof nonce invalid"); @@ -238,5 +240,71 @@ fn blobstreamx_data_commitments_from_herodotus_facts() { assert!(bsx.get_state_data_commitment(1) == data_commitment1, "data commitment 1 invalid"); assert!(bsx.get_state_data_commitment(2) == data_commitment2, "data commitment 2 invalid"); assert!(bsx.get_state_data_commitment(3) == 0, "data commitment 3 invalid"); -// TODO: test invalid inputs and things +} + +#[test] +#[should_panic(expected: ("No proof nonce found for block 256",))] +fn blobstreamx_invalid_request_for_herodotus_facts() { + let bsx = setup_blobstreamx(); + let l1_block_num: u256 = 0x100; + + bsx.update_data_commitments_from_facts(l1_block_num); +} + +#[test] +#[should_panic(expected: ("No data commitment found for block 256 and proof nonce 2",))] +fn blobstreamx_incomplete_data_commitments_relayed() { + let bsx = setup_blobstreamx(); + + let hfr_addr = bsx.get_herodotus_facts_registry(); + let l1_block_num: u256 = 0x100; + + let hfr_dispatcher = IEVMFactsRegistryMockDispatcher { contract_address: hfr_addr }; + + // Set the state_proofNonce slot to 3 + + hfr_dispatcher + .set_slot_value( + BLOBSTREAMX_L1_ADDRESS, l1_block_num, 0xfc, 3.into() + ); // state_proofNonce at slot 0xfc + + // Add only 1 of 2 data commitments + + // Slot pos = keccak256(abi.encode(map_key, state_dataCommitments_slot)) ie keccak256(abi.encode(map_key, 0xfe)) + let data_commitment1_slot: u256 = + 0x457c8a48b4735f56b938837eb0a8a5f9c55f23c1a85767ce3b65c3e59d3d32b7; + let data_commitment1: u256 = 0xe1078369756a0b28e3b8cc1fa6e0133630ccdf9d2bd5bde1d40d197793c3c8b4; + hfr_dispatcher + .set_slot_value( + BLOBSTREAMX_L1_ADDRESS, l1_block_num, data_commitment1_slot, data_commitment1 + ); // state_dataCommitments[1] + + bsx.update_data_commitments_from_facts(l1_block_num); +} + +#[test] +#[should_panic(expected: ("State proof nonce does not increase on block 256",))] +fn blobstreamx_invalid_proof_nonce_from_facts() { + let bsx = setup_blobstreamx(); + + let hfr_addr = bsx.get_herodotus_facts_registry(); + let l1_block_num: u256 = 0x100; + + let hfr_dispatcher = IEVMFactsRegistryMockDispatcher { contract_address: hfr_addr }; + + // Set the state_proofNonce slot to 2 + + hfr_dispatcher + .set_slot_value( + BLOBSTREAMX_L1_ADDRESS, l1_block_num, 0xfc, 2.into() + ); // state_proofNonce at slot 0xfc + + // Set state_proof_nonce to 3 in BlobstreamX + snf::store( + bsx.contract_address, + selector!("state_proof_nonce"), + array![3.into()].span() + ); + + bsx.update_data_commitments_from_facts(l1_block_num) } From ffd71787733777adb2ff044dd786c75e7ac03134 Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Mon, 18 Mar 2024 10:50:17 -0500 Subject: [PATCH 5/7] Scarb fmt --- src/tests/test_blobstreamx.cairo | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/tests/test_blobstreamx.cairo b/src/tests/test_blobstreamx.cairo index 168a082..3cbb58b 100644 --- a/src/tests/test_blobstreamx.cairo +++ b/src/tests/test_blobstreamx.cairo @@ -300,11 +300,7 @@ fn blobstreamx_invalid_proof_nonce_from_facts() { ); // state_proofNonce at slot 0xfc // Set state_proof_nonce to 3 in BlobstreamX - snf::store( - bsx.contract_address, - selector!("state_proof_nonce"), - array![3.into()].span() - ); - + snf::store(bsx.contract_address, selector!("state_proof_nonce"), array![3.into()].span()); + bsx.update_data_commitments_from_facts(l1_block_num) } From 00d4ed2abf4f1159c272c99105ee48ed4f48c038 Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Tue, 19 Mar 2024 09:44:57 -0500 Subject: [PATCH 6/7] Hardcoded 4 requests for DC --- scripts/latest-proof-nonce.sh | 101 ++++++++++++++++++++++++++++ scripts/request-data-commitments.sh | 6 +- 2 files changed, 105 insertions(+), 2 deletions(-) create mode 100755 scripts/latest-proof-nonce.sh diff --git a/scripts/latest-proof-nonce.sh b/scripts/latest-proof-nonce.sh new file mode 100755 index 0000000..b2232e4 --- /dev/null +++ b/scripts/latest-proof-nonce.sh @@ -0,0 +1,101 @@ +#!/bin/bash +# +# Request relay of the latest BlobstreamX data commitments from L1 to Starknet +# Uses the Herodotus API to submit a batch query for all data commitments +# up to the latest state_proofNonce + +# Constants +HERODOTUS_API_URL="https://api.herodotus.cloud/" +BLOBSTREAMX_SOURCE_CHAIN_ID="11155111" +BLOBSTREAMX_DESTINATION_CHAIN_ID="SN_SEPOLIA" +STATE_PROOF_NONCE_SLOT="0x00000000000000000000000000000000000000000000000000000000000000fc" +STATE_DATA_COMMITMENT_MAP_SLOT="0x00000000000000000000000000000000000000000000000000000000000000fe" + +# Optional arguments +# TODO: To public api? +L1_RPC_URL="https://sepolia.infura.io/v3/bed8a8401c894421bd7cd31050e7ced6" +STARKNET_RPC_URL="https://starknet-sepolia.infura.io/v3/bed8a8401c894421bd7cd31050e7ced6" +BLOBSTREAMX_L1_ADDRESS="0x48B257EC1610d04191cC2c528d0c940AdbE1E439" +#TODO +BLOBSTREAMX_STARKNET_ADDRESS="0x48B257EC1610d04191cC2c528d0c940AdbE1E439" + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +WORK_DIR=$SCRIPT_DIR/.. + +#TODO: w/o webhook, verbose, w/o header access +#TODO: resume from id + +display_help() { + echo "Usage: $0 [option...] {arguments...}" + echo + + echo " -h, --help display help" + echo " -r, --l1-rpc=URL URL of the L1 RPC server (default: $L1_RPC_URL)" + echo " -s, --starknet-rpc=URL URL of the Starknet RPC server (default: $STARKNET_RPC_URL)" + echo " -b, --blobstreamx-l1=ADDR BlobstreamX contract address on L1 (default: $BLOBSTREAMX_L1_ADDRESS)" + echo " -B, --blobstreamx-starknet=ADDR BlobstreamX contract address on Starknet (default: $BLOBSTREAMX_STARKNET_ADDRESS)" + + echo + echo "Example: $0" +} + +#TODO: Add support for optional arguments + +# Parse command line arguments +while getopts ":hp:-:" opt; do + case ${opt} in + - ) + case "${OPTARG}" in + help ) + display_help + exit 0 + ;; + * ) + echo "Invalid option: --$OPTARG" 1>&2 + display_help + exit 1 + ;; + esac + ;; + h ) + display_help + exit 0 + ;; + \? ) + echo "Invalid option: $OPTARG" 1>&2 + display_help + exit 1 + ;; + : ) + echo "Invalid option: $OPTARG requires an argument" 1>&2 + display_help + exit 1 + ;; + esac +done + +if [ -z "$HERODOTUS_API_KEY" ]; then + echo "HERODOTUS_API_KEY is not set. Get your API key from https://dashboard.herodotus.dev by signing up with your GitHub." + exit 1 +fi + +L1_BLOCK_NUM_RES=$(curl $L1_RPC_URL \ + -X POST \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' 2>/dev/null) +L1_BLOCK_NUM=$(echo $L1_BLOCK_NUM_RES | jq -r '.result') +echo "Latest L1 block number: $L1_BLOCK_NUM" + +# echo +# echo "Getting current Starknet block number..." +# curl $STARKNET_RPC_URL \ +# -X POST \ +# -H "Content-Type: application/json" \ +# -d '{"jsonrpc":"2.0","method":"starknet_blockNumber","params":[],"id":1}' + +LATEST_PROOF_NONCE_RES=$(curl $L1_RPC_URL \ + -X POST \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"eth_getStorageAt","params":["'"$BLOBSTREAMX_L1_ADDRESS"'","'"$STATE_PROOF_NONCE_SLOT"'","latest"],"id":1}' 2>/dev/null) +LATEST_PROOF_NONCE=$(echo $LATEST_PROOF_NONCE_RES | jq -r '.result') +echo "Latest L1 state_proofNonce: $LATEST_PROOF_NONCE" diff --git a/scripts/request-data-commitments.sh b/scripts/request-data-commitments.sh index a8412c4..13d9215 100755 --- a/scripts/request-data-commitments.sh +++ b/scripts/request-data-commitments.sh @@ -129,8 +129,10 @@ LATEST_PROOF_NONCE=$(echo $LATEST_PROOF_NONCE_RES | jq -r '.result') # -X POST \ # -H "Content-Type: application/json" \ # -d '{"jsonrpc":"2.0","method":"starknet_getState","params":["'"$BLOBSTREAMX_STARKNET_ADDRESS"'",'"$LATEST_PROOF_NONCE"'],"id":1}' 2>/dev/null) -STARKNET_PROOF_NONCE_RES=$(echo '{"jsonrpc":"2.0","id":1,"result":"0x00000000000000000000000000000000000000000000000000000000000006cb"}') -STARKNET_PROOF_NONCE=$(echo $STARKNET_PROOF_NONCE_RES | jq -r '.result') +#TODO: Remove the hardcoded value once the blobstreamx contract is deployed on Starknet +#STARKNET_PROOF_NONCE_RES=$(echo '{"jsonrpc":"2.0","id":1,"result":"0x00000000000000000000000000000000000000000000000000000000000006cb"}') +#STARKNET_PROOF_NONCE=$(echo $STARKNET_PROOF_NONCE_RES | jq -r '.result') +STARKNET_PROOF_NONCE=$((LATEST_PROOF_NONCE - 4)) if [ "$VERBOSE" = true ]; then echo "Latest L1 block number: $L1_BLOCK_NUM" From 1fe4cd1dd7ddfc674187a79f20ef5862db982959 Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Tue, 19 Mar 2024 09:49:08 -0500 Subject: [PATCH 7/7] Remove latest-proof-nonce script --- scripts/latest-proof-nonce.sh | 101 ---------------------------------- 1 file changed, 101 deletions(-) delete mode 100755 scripts/latest-proof-nonce.sh diff --git a/scripts/latest-proof-nonce.sh b/scripts/latest-proof-nonce.sh deleted file mode 100755 index b2232e4..0000000 --- a/scripts/latest-proof-nonce.sh +++ /dev/null @@ -1,101 +0,0 @@ -#!/bin/bash -# -# Request relay of the latest BlobstreamX data commitments from L1 to Starknet -# Uses the Herodotus API to submit a batch query for all data commitments -# up to the latest state_proofNonce - -# Constants -HERODOTUS_API_URL="https://api.herodotus.cloud/" -BLOBSTREAMX_SOURCE_CHAIN_ID="11155111" -BLOBSTREAMX_DESTINATION_CHAIN_ID="SN_SEPOLIA" -STATE_PROOF_NONCE_SLOT="0x00000000000000000000000000000000000000000000000000000000000000fc" -STATE_DATA_COMMITMENT_MAP_SLOT="0x00000000000000000000000000000000000000000000000000000000000000fe" - -# Optional arguments -# TODO: To public api? -L1_RPC_URL="https://sepolia.infura.io/v3/bed8a8401c894421bd7cd31050e7ced6" -STARKNET_RPC_URL="https://starknet-sepolia.infura.io/v3/bed8a8401c894421bd7cd31050e7ced6" -BLOBSTREAMX_L1_ADDRESS="0x48B257EC1610d04191cC2c528d0c940AdbE1E439" -#TODO -BLOBSTREAMX_STARKNET_ADDRESS="0x48B257EC1610d04191cC2c528d0c940AdbE1E439" - -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -WORK_DIR=$SCRIPT_DIR/.. - -#TODO: w/o webhook, verbose, w/o header access -#TODO: resume from id - -display_help() { - echo "Usage: $0 [option...] {arguments...}" - echo - - echo " -h, --help display help" - echo " -r, --l1-rpc=URL URL of the L1 RPC server (default: $L1_RPC_URL)" - echo " -s, --starknet-rpc=URL URL of the Starknet RPC server (default: $STARKNET_RPC_URL)" - echo " -b, --blobstreamx-l1=ADDR BlobstreamX contract address on L1 (default: $BLOBSTREAMX_L1_ADDRESS)" - echo " -B, --blobstreamx-starknet=ADDR BlobstreamX contract address on Starknet (default: $BLOBSTREAMX_STARKNET_ADDRESS)" - - echo - echo "Example: $0" -} - -#TODO: Add support for optional arguments - -# Parse command line arguments -while getopts ":hp:-:" opt; do - case ${opt} in - - ) - case "${OPTARG}" in - help ) - display_help - exit 0 - ;; - * ) - echo "Invalid option: --$OPTARG" 1>&2 - display_help - exit 1 - ;; - esac - ;; - h ) - display_help - exit 0 - ;; - \? ) - echo "Invalid option: $OPTARG" 1>&2 - display_help - exit 1 - ;; - : ) - echo "Invalid option: $OPTARG requires an argument" 1>&2 - display_help - exit 1 - ;; - esac -done - -if [ -z "$HERODOTUS_API_KEY" ]; then - echo "HERODOTUS_API_KEY is not set. Get your API key from https://dashboard.herodotus.dev by signing up with your GitHub." - exit 1 -fi - -L1_BLOCK_NUM_RES=$(curl $L1_RPC_URL \ - -X POST \ - -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' 2>/dev/null) -L1_BLOCK_NUM=$(echo $L1_BLOCK_NUM_RES | jq -r '.result') -echo "Latest L1 block number: $L1_BLOCK_NUM" - -# echo -# echo "Getting current Starknet block number..." -# curl $STARKNET_RPC_URL \ -# -X POST \ -# -H "Content-Type: application/json" \ -# -d '{"jsonrpc":"2.0","method":"starknet_blockNumber","params":[],"id":1}' - -LATEST_PROOF_NONCE_RES=$(curl $L1_RPC_URL \ - -X POST \ - -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","method":"eth_getStorageAt","params":["'"$BLOBSTREAMX_L1_ADDRESS"'","'"$STATE_PROOF_NONCE_SLOT"'","latest"],"id":1}' 2>/dev/null) -LATEST_PROOF_NONCE=$(echo $LATEST_PROOF_NONCE_RES | jq -r '.result') -echo "Latest L1 state_proofNonce: $LATEST_PROOF_NONCE"