Skip to content

Commit

Permalink
Merge pull request #340 from chainwayxyz/proto-design
Browse files Browse the repository at this point in the history
Add initial gRPC support with new RPC interfaces
  • Loading branch information
ekrembal authored Nov 5, 2024
2 parents c0c61e8 + 0e95811 commit 44ce440
Show file tree
Hide file tree
Showing 18 changed files with 2,924 additions and 27 deletions.
3 changes: 3 additions & 0 deletions .github/actions/service-action/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ runs:
- name: Create funds in Bitcoin regtest
shell: bash
run: bitcoin-27.0/bin/bitcoin-cli -regtest -rpcuser=admin -rpcpassword=admin -rpcport=18443 generatetoaddress 101 $(bitcoin-27.0/bin/bitcoin-cli -regtest -rpcuser=admin -rpcpassword=admin -rpcport=18443 getnewaddress)

- name: Install Protoc
uses: arduino/setup-protoc@v3
2 changes: 2 additions & 0 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:

steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/service-action

- name: Save build artifacts
uses: Swatinem/rust-cache@v2
Expand All @@ -32,6 +33,7 @@ jobs:

steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/service-action

- name: Save build artifacts
uses: Swatinem/rust-cache@v2
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/code_checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ jobs:
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Generate code coverage and print that to a JSON file
run: cargo llvm-cov $CARGOFLAGS --json --output-path lcov.json
run: cargo llvm-cov $CARGOFLAGS --json --output-path lcov.json --ignore-filename-regex core/src/rpc/clementine.rs # TODO: Remove ignore and test auto generated code too
- name: Check coverage
run: scripts/check_json_code_coverage.py lcov.json

Expand Down
38 changes: 24 additions & 14 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,41 @@ resolver = "2"
members = ["core"] # TODO: Add "circuits" back later

[workspace.dependencies]
bitcoin = "0.32.2"
bitcoincore-rpc = "0.19.0"
hex = "0.4.3"
lazy_static = { version = "1.5.0", default-features = false }
sha2 = { version = "=0.10.8", default-features = false }
# risc0-zkvm = "0.21.0"
serde = { version = "1.0", default-features = false }
serde_json = "1.0.127"
# byteorder = "1.5.0"
secp256k1 = { version = "0.29.0", features = ["serde"] }
crypto-bigint = { version = "=0.5.5", features = ["rand_core"] }
thiserror = "1.0.63"
serde_json = "1.0.128"
thiserror = "1.0.64"
tracing = { version = "0.1.40", default-features = false }
tracing-subscriber = { version = "0.3.18", features = ["json"] }
tokio = "1.39.3"
jsonrpsee = "0.22.5"
async-trait = "0.1.81"
futures = "0.3.30"
clap = "4.5.16"
async-trait = "0.1.83"
clap = "4.5.20"
toml = "0.8.19"
sqlx = { version = "0.7.4", default-features = false }
# k256 = { version = "=0.13.3", default-features = false }
# risc0-build = "0.21.0"
bitcoin-mock-rpc = { git = "https://github.com/chainwayxyz/bitcoin-mock-rpc", tag = "v0.0.11" }

# bitcoin
bitcoin = "0.32.3"
bitcoincore-rpc = "0.19.0"
musig2 = { version = "0.0.11", features = ["serde"] }
secp256k1 = { version = "0.29.1", features = ["serde"] }

# async + gRPC
tonic = { version = "0.12.3", features = [ "tls" ] }
prost = "0.13.3"
tokio = { version = "1.40.0", features = [ "full" ] }
tokio-stream = { version = "0.1.16", features = [ "sync" ] }
futures = "0.3.31"

# Circuits
sha2 = { version = "=0.10.8", default-features = false }
crypto-bigint = { version = "=0.5.5", features = ["rand_core"] }
# risc0-zkvm = "0.21.0"

# TODO: Make this dev-dependency (see #181)
bitcoin-mock-rpc = { git = "https://github.com/chainwayxyz/bitcoin-mock-rpc", tag = "v0.0.11" }

[profile.release]
lto = true
Expand Down
7 changes: 7 additions & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ name = "clementine-core"
version = "0.2.0"
edition = "2021"

[build-dependencies]
tonic-build = "0.12"

[dependencies]
# clementine-circuits = { path = "../circuits" }
bitcoin = { workspace = true, features = ["rand", "bitcoinconsensus"] }
Expand All @@ -29,6 +32,10 @@ sqlx = { workspace = true, features = ["runtime-tokio", "postgres", "macros"] }
bitcoin-mock-rpc = { workspace = true }
musig2 = { workspace = true }

tonic = { workspace = true}
prost = { workspace = true }
tokio-stream = {workspace = true }

[features]
default = []
mock_rpc = []
Expand Down
54 changes: 54 additions & 0 deletions core/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use std::{env, path::Path, process::Command};

fn trim_ascii_end(s: &str) -> &str {
let trimmed_len = s
.as_bytes()
.iter()
.rposition(|&b| !b.is_ascii_whitespace())
.map_or(0, |pos| pos + 1);
&s[..trimmed_len]
}

fn compile_protobuf() {
// Try to set PROTOC env var if on *nix.
if let Ok(output) = Command::new("which").args(["protoc"]).output() {
// Skip compilation, if command failed.
if !output.status.success() {
return;
}

// Set env var.
let path = String::from_utf8_lossy(&output.stdout);
env::set_var("PROTOC", trim_ascii_end(&path));
}

// Skip compilation if env var is not set.
if env::var("PROTOC").is_err() {
return;
};

let proto_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/rpc/");
let protos = &["clementine.proto"];

let proto_files: Vec<String> = protos
.iter()
.map(|proto| proto_root.join(proto).to_str().unwrap().to_owned())
.collect();

// Tell Cargo that if a proto file changes, rerun this build script.
for pf in &proto_files {
println!("cargo:rerun-if-changed={}", pf);
}

// Compile server and client code from proto files
tonic_build::configure()
.build_server(true)
.build_client(true)
.out_dir("./src/rpc")
.compile_protos(&proto_files, &[proto_root.to_str().unwrap()])
.unwrap();
}

fn main() {
compile_protobuf();
}
9 changes: 9 additions & 0 deletions core/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ pub enum BridgeError {
/// JSON RPC call failed
#[error("JsonRpcError: {0}")]
JsonRpcError(#[from] jsonrpsee::core::client::Error),
/// RPC interface requires a parameter
#[error("RPC function field {0} is required!")]
RPCRequiredFieldError(&'static str),
/// ConfigError is returned when the configuration is invalid
#[error("ConfigError: {0}")]
ConfigError(String),
Expand Down Expand Up @@ -183,3 +186,9 @@ impl From<BridgeError> for ErrorObject<'static> {
ErrorObject::owned(-30000, format!("{:?}", val), Some(1))
}
}

impl From<BridgeError> for tonic::Status {
fn from(val: BridgeError) -> Self {
tonic::Status::from_error(Box::new(val))
}
}
2 changes: 2 additions & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ pub mod merkle;
pub mod mock;
pub mod musig2;
pub mod operator;
pub mod rpc;
pub mod servers;
pub mod traits;
pub mod user;
pub mod utils;
pub mod verifier;
pub mod watchtower;

pub type ConnectorUTXOTree = Vec<Vec<OutPoint>>;
// pub type HashTree = Vec<Vec<HashType>>;
Expand Down
47 changes: 35 additions & 12 deletions core/src/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,15 +476,25 @@ where
Ok(self.rpc.send_raw_transaction(&signed_tx)?)
}

#[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))]
async fn withdrawal_proved_on_citrea(
/// Checks Citrea if a withdrawal is finalized.
///
/// Calls `withdrawFillers(withdrawal_idx)` to check the returned id is our
/// operator's id. Then calculates `move_txid` and calls
/// `txIdToDepositId(move_txid)` to check if returned id is
/// `withdrawal_idx`.
pub async fn check_citrea_for_withdrawal(
&self,
withdrawal_idx: u32,
deposit_outpoint: OutPoint,
) -> Result<Vec<String>, BridgeError> {
// call withdrawFillers(withdrawal_idx) check the returned id is our operator id.
// calculate the move_txid, txIdToDepositId(move_txid) check the returned id is withdrawal_idx
if let Some(citrea_client) = &self.citrea_client {
) -> Result<(), BridgeError> {
// Don't check anything if Citrea client is not specified.
let citrea_client = match &self.citrea_client {
Some(c) => c,
None => return Ok(()),
};

// Check for operator id.
{
// See: https://gist.github.com/okkothejawa/a9379b02a16dada07a2b85cbbd3c1e80
let params = rpc_params![
json!({
Expand All @@ -505,22 +515,23 @@ where
self.idx,
));
}
}

// Calculate move_txid.
let move_tx = builder::transaction::create_move_tx(
// Check for withdrawal idx.
{
let move_txid = builder::transaction::create_move_tx(
deposit_outpoint,
self.nofn_xonly_pk,
self.config.bridge_amount_sats,
self.config.network,
);
let move_txid = move_tx.compute_txid();
let move_txid_bytes = move_txid.to_byte_array();
)
.compute_txid();

// See: https://gist.github.com/okkothejawa/a9379b02a16dada07a2b85cbbd3c1e80
let params = rpc_params![json!({
"to": "0x3100000000000000000000000000000000000002",
"data": format!("0x11e53a01{}",
hex::encode(move_txid_bytes)),
hex::encode(move_txid.to_byte_array())),
})];
let response: String = citrea_client.request("eth_call", params).await?;

Expand All @@ -536,6 +547,18 @@ where
}
}

Ok(())
}

#[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))]
pub(crate) async fn withdrawal_proved_on_citrea(
&self,
withdrawal_idx: u32,
deposit_outpoint: OutPoint,
) -> Result<Vec<String>, BridgeError> {
self.check_citrea_for_withdrawal(withdrawal_idx, deposit_outpoint)
.await?;

let kickoff_utxo = self
.db
.get_kickoff_utxo(None, deposit_outpoint)
Expand Down
21 changes: 21 additions & 0 deletions core/src/rpc/aggregator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use super::clementine::{
clementine_aggregator_server::ClementineAggregator, DepositParams, Empty, RawSignedMoveTx,
};
use crate::aggregator::Aggregator;
use tonic::{async_trait, Request, Response, Status};

#[async_trait]
impl ClementineAggregator for Aggregator {
#[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))]
async fn setup(&self, _request: Request<Empty>) -> Result<Response<Empty>, Status> {
todo!()
}

#[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))]
async fn new_deposit(
&self,
_request: Request<DepositParams>,
) -> Result<Response<RawSignedMoveTx>, Status> {
todo!()
}
}
Loading

0 comments on commit 44ce440

Please sign in to comment.