Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Herodotus Data Commitment Facts #120

Merged
merged 8 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 231 additions & 0 deletions scripts/request-data-commitments.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
#!/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
L1_RPC_URL="https://sepolia.infura.io/v3/bed8a8401c894421bd7cd31050e7ced6"
STARKNET_RPC_URL="https://starknet-sepolia.infura.io/v3/bed8a8401c894421bd7cd31050e7ced6"
BLOBSTREAMX_L1_ADDRESS="0x48B257EC1610d04191cC2c528d0c940AdbE1E439"
#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/..

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 " -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"
}

# 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 ":hnvNb:B:r:s:-:" opt; do
case ${opt} in
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
exit 1
;;
: )
echo "Invalid option: $OPTARG requires an argument" 1>&2
display_help
exit 1
;;
esac
done

# 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

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')

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"'","'"$L1_BLOCK_NUM"'"],"id":1}' 2>/dev/null)
LATEST_PROOF_NONCE=$(echo $LATEST_PROOF_NONCE_RES | jq -r '.result')

#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)
#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"
echo "Latest L1 state_proofNonce: $LATEST_PROOF_NONCE"
echo "Latest Starknet state_proofNonce: $STARKNET_PROOF_NONCE"
fi

DATA_COMMITMENT_SLOTS=''
# 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

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..."
HERODOTUS_ID=$(curl -X 'POST' \
$HERODOTUS_QUERY_URL \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-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

# 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 $([ "$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 $([ "$VERBOSE" = true ] && echo "-v")
done
162 changes: 162 additions & 0 deletions scripts/wait-for-herodotus-fulfill.sh
Original file line number Diff line number Diff line change
@@ -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
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 " -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 - Hex (required)"

echo
echo " -h, --help display help"
echo " -v, --verbose display verbose output"

echo
echo "Example: $0"
}

# 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 ":hvs:F:b:a:S:" opt; do
case ${opt} in
h )
display_help
exit 0
;;
v )
VERBOSE=true
;;
s )
STARKNET_RPC_URL="${OPTARG}"
;;
F )
HERODOTUS_FACT_REGISTRY="${OPTARG}"
;;
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

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}

# 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}

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": "'$HERODOTUS_GET_SLOT_VALUE_SELECTOR'",
"contract_address": "'$HERODOTUS_FACT_REGISTRY'"
},
"pending"
]
}'

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 \
-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
Loading