diff --git a/rust-lightclient/.gitignore b/rust-lightclient/.gitignore new file mode 100644 index 0000000..2c96eb1 --- /dev/null +++ b/rust-lightclient/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock diff --git a/rust-lightclient/Cargo.toml b/rust-lightclient/Cargo.toml new file mode 100644 index 0000000..8bdcd89 --- /dev/null +++ b/rust-lightclient/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "rust-lightclient" +version = "0.1.0" +edition = "2018" + +[dependencies] +tower-grpc = { git = "https://github.com/tower-rs/tower-grpc" } +futures = "0.1" +bytes = "0.4" +env_logger = { version = "0.5", default-features = false } +log = "0.4" +http = "0.1" +prost = "0.5" +tokio = "0.1" +tower-request-modifier = { git = "https://github.com/tower-rs/tower-http" } +tower-hyper = "0.1" +hyper = "0.12" +tower-service = "0.2" +tower-util = "0.1" + +[build-dependencies] +tower-grpc-build = { git = "https://github.com/tower-rs/tower-grpc", features = ["tower-hyper"] } diff --git a/rust-lightclient/build.rs b/rust-lightclient/build.rs new file mode 100644 index 0000000..5fda5fe --- /dev/null +++ b/rust-lightclient/build.rs @@ -0,0 +1,12 @@ +fn main() { + // Build proto files + tower_grpc_build::Config::new() + .enable_server(false) + .enable_client(true) + .build( + &["proto/service.proto", "proto/compact_formats.proto"], + &["proto"], + ) + .unwrap_or_else(|e| panic!("protobuf compilation failed: {}", e)); + println!("cargo:rerun-if-changed=proto/service.proto"); +} diff --git a/rust-lightclient/proto/compact_formats.proto b/rust-lightclient/proto/compact_formats.proto new file mode 100644 index 0000000..7e1bc54 --- /dev/null +++ b/rust-lightclient/proto/compact_formats.proto @@ -0,0 +1,48 @@ +syntax = "proto3"; +package cash.z.wallet.sdk.rpc; +option go_package = "walletrpc"; + +// Remember that proto3 fields are all optional. A field that is not present will be set to its zero value. +// bytes fields of hashes are in canonical little-endian format. + +// CompactBlock is a packaging of ONLY the data from a block that's needed to: +// 1. Detect a payment to your shielded Sapling address +// 2. Detect a spend of your shielded Sapling notes +// 3. Update your witnesses to generate new Sapling spend proofs. +message CompactBlock { + uint32 protoVersion = 1; // the version of this wire format, for storage + uint64 height = 2; // the height of this block + bytes hash = 3; + bytes prevHash = 4; + uint32 time = 5; + bytes header = 6; // (hash, prevHash, and time) OR (full header) + repeated CompactTx vtx = 7; // compact transactions from this block +} + +message CompactTx { + // Index and hash will allow the receiver to call out to chain + // explorers or other data structures to retrieve more information + // about this transaction. + uint64 index = 1; + bytes hash = 2; + + // The transaction fee: present if server can provide. In the case of a + // stateless server and a transaction with transparent inputs, this will be + // unset because the calculation requires reference to prior transactions. + // in a pure-Sapling context, the fee will be calculable as: + // valueBalance + (sum(vPubNew) - sum(vPubOld) - sum(tOut)) + uint32 fee = 3; + + repeated CompactSpend spends = 4; + repeated CompactOutput outputs = 5; +} + +message CompactSpend { + bytes nf = 1; +} + +message CompactOutput { + bytes cmu = 1; + bytes epk = 2; + bytes ciphertext = 3; +} diff --git a/rust-lightclient/proto/service.proto b/rust-lightclient/proto/service.proto new file mode 100644 index 0000000..2ae46d2 --- /dev/null +++ b/rust-lightclient/proto/service.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; +package cash.z.wallet.sdk.rpc; +option go_package = "walletrpc"; + +import "compact_formats.proto"; + +// A BlockID message contains identifiers to select a block: a height or a +// hash. If the hash is present it takes precedence. +message BlockID { + uint64 height = 1; + bytes hash = 2; +} + +// BlockRange technically allows ranging from hash to hash etc but this is not +// currently intended for support, though there is no reason you couldn't do +// it. Further permutations are left as an exercise. +message BlockRange { + BlockID start = 1; + BlockID end = 2; +} + +// A TxFilter contains the information needed to identify a particular +// transaction: either a block and an index, or a direct transaction hash. +message TxFilter { + BlockID block = 1; + uint64 index = 2; + bytes hash = 3; +} + +// RawTransaction contains the complete transaction data. +message RawTransaction { + bytes data = 1; +} + +message SendResponse { + int32 errorCode = 1; + string errorMessage = 2; +} + +// Empty placeholder. Someday we may want to specify e.g. a particular chain fork. +message ChainSpec {} + +service CompactTxStreamer { + rpc GetLatestBlock(ChainSpec) returns (BlockID) {} + rpc GetBlock(BlockID) returns (CompactBlock) {} + rpc GetBlockRange(BlockRange) returns (stream CompactBlock) {} + rpc GetTransaction(TxFilter) returns (RawTransaction) {} + rpc SendTransaction(RawTransaction) returns (SendResponse) {} +} diff --git a/rust-lightclient/src/main.rs b/rust-lightclient/src/main.rs new file mode 100644 index 0000000..a74996d --- /dev/null +++ b/rust-lightclient/src/main.rs @@ -0,0 +1,48 @@ + +use futures::Future; +use hyper::client::connect::{Destination, HttpConnector}; +use tower_grpc::Request; +use tower_hyper::{client, util}; +use tower_util::MakeService; + +pub mod grpc_client { + include!(concat!(env!("OUT_DIR"), "/cash.z.wallet.sdk.rpc.rs")); +} + +pub fn main() { + let uri: http::Uri = format!("http://127.0.0.1:9067").parse().unwrap(); + + let dst = Destination::try_from_uri(uri.clone()).unwrap(); + let connector = util::Connector::new(HttpConnector::new(4)); + let settings = client::Builder::new().http2_only(true).clone(); + let mut make_client = client::Connect::with_builder(connector, settings); + + let say_hello = make_client + .make_service(dst) + .map_err(|e| panic!("connect error: {:?}", e)) + .and_then(move |conn| { + use crate::grpc_client::client::CompactTxStreamer; + + let conn = tower_request_modifier::Builder::new() + .set_origin(uri) + .build(conn) + .unwrap(); + + // Wait until the client is ready... + CompactTxStreamer::new(conn).ready() + }) + .and_then(|mut client| { + use crate::grpc_client::ChainSpec; + + client.get_latest_block(Request::new(ChainSpec {})) + }) + .and_then(|response| { + println!("RESPONSE = {:?}", response); + Ok(()) + }) + .map_err(|e| { + println!("ERR = {:?}", e); + }); + + tokio::run(say_hello); +} \ No newline at end of file