A gRPC service for managing storage slot locks in EVM smart contracts on Sova.
sova-sentinel/
├── Cargo.toml
└── crates/
├── proto/ # Protocol definitions and generated gRPC code
├── client/ # Client library for interacting with the service
└── server/ # Server implementation with SQLite backend
- sova-sentinel-proto: Contains the protobuf service definitions and generated gRPC code.
- sova-sentinel-client: Provides a Rust client library for interacting with the service.
- sova-sentinel-server: Implements the gRPC service with a SQLite backend.
git clone https://github.com/SovaNetwork/sova-sentinel.git
cd sova-sentinel
The server can be configured using environment variables or a .env
file.
Create a .env
file in the root directory:
SOVA_SENTINEL_HOST=[::1]
SOVA_SENTINEL_PORT=50051
SOVA_SENTINEL_DB_PATH=data/db.sqlite
BITCOIN_RPC_URL=http://localhost:18443
BITCOIN_RPC_USER=user
BITCOIN_RPC_PASS=password
BITCOIN_CONFIRMATION_THRESHOLD=6
BITCOIN_REVERT_THRESHOLD=18
BITCOIN_RPC_MAX_RETRIES=5
Available configuration options:
SOVA_SENTINEL_HOST
: Host for the gRPC server (default:[::1]
)SOVA_SENTINEL_PORT
: Port for the gRPC server (default: 50051)SOVA_SENTINEL_DB_PATH
: Path to the SQLite database file (default: slot_locks.db)BITCOIN_RPC_URL
: Bitcoin node RPC URL (default: http://localhost:18443)BITCOIN_RPC_USER
: Bitcoin node RPC username (default: user)BITCOIN_RPC_PASS
: Bitcoin node RPC password (default: pass)BITCOIN_CONFIRMATION_THRESHOLD
: Number of confirmations required to unlock a slot (default: 6)BITCOIN_REVERT_THRESHOLD
: Number of blocks after which a locked slot will revert (default: 18)BITCOIN_RPC_MAX_RETRIES
: Maximum number of retries for Bitcoin RPC calls (default: 5)
The project uses Just as a command runner. There are other options shown below for running the service that do not require just.
Available commands:
just proto # Build protocol buffers
just clean-proto # Clean generated protobuf code
just build # Build entire workspace
just test # Run all tests
just server # Run the server with default settings
just client # Run the example client
just server-custom 3000 ./my.db # Run server with custom port and database path
You can see all available commands with:
just --list
If you prefer not to use Just, you can run the commands directly:
Using default configuration:
cargo run -p sova-sentinel-server
Using custom configuration:
SOVA_SENTINEL_PORT=3000 SOVA_SENTINEL_DB_PATH=/path/to/db.sqlite cargo run -p sova-sentinel-server
Running the example client:
cargo run -p sova-sentinel-client --example client
Build the Docker image:
docker build -t sova-sentinel .
Run the container:
docker run -d \
--name sova-sentinel \
-p 50051:50051 \
-v sova-data:/app/data \
-e BITCOIN_RPC_URL=http://your-bitcoin-node:8332 \
-e BITCOIN_RPC_USER=your-username \
-e BITCOIN_RPC_PASS=your-password \
--network your-bitcoin-network \
sova-sentinel
To use the client library in your project:
-
Clone this repository:
git clone git@github.com:SovaNetwork/sova-sentinel.git cd sova-sentinel
-
Build the protocol buffers:
just proto
-
Add the client library to your project's
Cargo.toml
:[dependencies] sova-sentinel-client = { path = "path/to/sova-sentinel/crates/client" }
-
See the example client for usage details.
lock_slot
: Lock a slot with revert value and current valueget_slot_status
: Check if a slot is locked, unlocked, or reverted
batch_lock_slot
: Lock multiple slots in a single transactionbatch_get_slot_status
: Get status of multiple slots efficientlybatch_unlock_slot
: (Development Only) Force unlock multiple slots without BTC confirmation
// Check slot status
let status_response = client.get_slot_status(
current_block, // Current block
contract_address, // Contract address
slot_index, // Slot index as bytes
).await?;
// Lock a single slot
let lock_response = client.lock_slot(
locked_at_block, // Block where lock takes effect
contract_address, // Contract address
slot_index, // Slot index as bytes
revert_value, // Value to revert to if BTC tx fails
current_value, // Current value of the slot
btc_txid, // Bitcoin transaction ID
btc_block, // Bitcoin block number
).await?;
use sova_sentinel_proto::proto::{SlotData, SlotIdentifier};
// Lock multiple slots
let lock_response = client.batch_lock_slot(
locked_at_block, // Block where locks take effect
btc_block, // Bitcoin block number
vec![ // Vec<SlotData>
SlotData {
contract_address: "0x123".to_string(),
slot_index: slot_index,
revert_value: revert_bytes,
current_value: current_bytes,
btc_txid: "txid1".to_string(),
},
// ... more slots ...
],
).await?;
// Check status of multiple slots
let status_response = client.batch_get_slot_status(
current_block,
btc_block,
vec![ // Vec<SlotIdentifier>
SlotIdentifier {
contract_address: "0x123".to_string(),
slot_index: slot_index,
},
// ... more slots ...
],
).await?;
// Development Only: Force unlock slots without BTC confirmation
let unlock_response = client.batch_unlock_slot(
current_block,
btc_block,
vec![ // Vec<SlotIdentifier>
SlotIdentifier {
contract_address: "0x123".to_string(),
slot_index: slot_index,
},
// ... more slots ...
],
).await?;
Note: The batch_unlock_slot
operation is provided for development convenience only. In production, slots should be unlocked through the normal Bitcoin confirmation process using batch_get_slot_status
.
The service implements an exponential backoff retry strategy for Bitcoin RPC calls:
- Base delay starts at 100ms and doubles with each retry
- Jitter is added to prevent thundering herd problems
- Only connectivity errors are retried (other errors fail immediately)
- Maximum retries is configurable via
BITCOIN_RPC_MAX_RETRIES
- After max retries, returns a gRPC
UNAVAILABLE
status code with aBitcoinNodeUnreachable
error message
cargo test