diff --git a/.gitignore b/.gitignore index 1fc478b..89b5bbc 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ docs/_site/ *.db Cargo.lock +node_modules/ diff --git a/proto/compact_formats.proto b/proto/compact_formats.proto index bf51ebb..f2129f2 100644 --- a/proto/compact_formats.proto +++ b/proto/compact_formats.proto @@ -37,20 +37,30 @@ message CompactTx { // valueBalance + (sum(vPubNew) - sum(vPubOld) - sum(tOut)) uint32 fee = 3; - repeated CompactSpend spends = 4; // inputs - repeated CompactOutput outputs = 5; // outputs + repeated CompactSaplingSpend spends = 4; // inputs + repeated CompactSaplingOutput outputs = 5; // outputs + repeated CompactOrchardAction actions = 6; } -// CompactSpend is a Sapling Spend Description as described in 7.3 of the Zcash +// CompactSaplingSpend is a Sapling Spend Description as described in 7.3 of the Zcash // protocol specification. -message CompactSpend { +message CompactSaplingSpend { bytes nf = 1; // nullifier (see the Zcash protocol specification) } // output is a Sapling Output Description as described in section 7.4 of the // Zcash protocol spec. Total size is 948. -message CompactOutput { +message CompactSaplingOutput { bytes cmu = 1; // note commitment u-coordinate bytes epk = 2; // ephemeral public key - bytes ciphertext = 3; // ciphertext and zkproof + bytes ciphertext = 3; // first 52 bytes of ciphertext +} + +// https://github.com/zcash/zips/blob/main/zip-0225.rst#orchard-action-description-orchardaction +// (but not all fields are needed) +message CompactOrchardAction { + bytes nullifier = 1; // [32] The nullifier of the input note + bytes cmx = 2; // [32] The x-coordinate of the note commitment for the output note + bytes ephemeralKey = 3; // [32] An encoding of an ephemeral Pallas public key + bytes ciphertext = 4; // [52] The note plaintext component of the encCiphertext field } diff --git a/proto/service.proto b/proto/service.proto index 6877ff7..49f97ea 100644 --- a/proto/service.proto +++ b/proto/service.proto @@ -32,7 +32,8 @@ message TxFilter { } // RawTransaction contains the complete transaction data. It also optionally includes -// the block height in which the transaction was included. +// the block height in which the transaction was included, or, when returned +// by GetMempoolStream(), the latest block height. message RawTransaction { bytes data = 1; // exact data returned by Zcash 'getrawtransaction' uint64 height = 2; // height that the transaction was mined (or -1) @@ -109,11 +110,12 @@ message Exclude { // The TreeState is derived from the Zcash z_gettreestate rpc. message TreeState { - string network = 1; // "main" or "test" - uint64 height = 2; - string hash = 3; // block id - uint32 time = 4; // Unix epoch time when the block was mined - string tree = 5; // sapling commitment tree state + string network = 1; // "main" or "test" + uint64 height = 2; // block height + string hash = 3; // block id + uint32 time = 4; // Unix epoch time when the block was mined + string saplingTree = 5; // sapling commitment tree state + string orchardTree = 6; // orchard commitment tree state } // Results are sorted by height, which makes it easy to issue another @@ -135,27 +137,6 @@ message GetAddressUtxosReplyList { repeated GetAddressUtxosReply addressUtxos = 1; } - -message PriceRequest { - // List of timestamps(in sec) at which the price is being requested - uint64 timestamp = 1; - - // 3 letter currency-code - string currency = 2; -} - -message PriceResponse { - // Timestamp at which this price quote was fetched. Note, this may not be the same - // as the request timestamp, but the server will respond with the closest timestamp that it has/can fetch - int64 timestamp = 1; - - // 3-letter currency code, matching the request - string currency = 2; - - // price of ZEC - double price = 3; -} - service CompactTxStreamer { // Return the height of the tip of the best chain rpc GetLatestBlock(ChainSpec) returns (BlockID) {} @@ -163,10 +144,6 @@ service CompactTxStreamer { rpc GetBlock(BlockID) returns (CompactBlock) {} // Return a list of consecutive compact blocks rpc GetBlockRange(BlockRange) returns (stream CompactBlock) {} - - // Get the historical and current prices - rpc GetZECPrice(PriceRequest) returns (PriceResponse) {} - rpc GetCurrentZECPrice(Empty) returns (PriceResponse) {} // Return the requested full (not compact) transaction (as from zcashd) rpc GetTransaction(TxFilter) returns (RawTransaction) {} @@ -175,10 +152,6 @@ service CompactTxStreamer { // Return the txids corresponding to the given t-address within the given block range rpc GetTaddressTxids(TransparentAddressBlockFilter) returns (stream RawTransaction) {} - - // Legacy API that is used as a fallback for t-Address support, if the server is running the old version (lwdv2) - rpc GetAddressTxids(TransparentAddressBlockFilter) returns (stream RawTransaction) {} - rpc GetTaddressBalance(AddressList) returns (Balance) {} rpc GetTaddressBalanceStream(stream Address) returns (Balance) {} @@ -193,6 +166,10 @@ service CompactTxStreamer { // in the exclude list that don't exist in the mempool are ignored. rpc GetMempoolTx(Exclude) returns (stream CompactTx) {} + // Return a stream of current Mempool transactions. This will keep the output stream open while + // there are mempool transactions. It will close the returned stream when a new block is mined. + rpc GetMempoolStream(Empty) returns (stream RawTransaction) {} + // GetTreeState returns the note commitment tree state corresponding to the given block. // See section 3.7 of the Zcash protocol specification. It returns several other useful // values also (even though they can be obtained using GetBlock). diff --git a/src/api/sync.rs b/src/api/sync.rs index 9b766b4..5f12aef 100644 --- a/src/api/sync.rs +++ b/src/api/sync.rs @@ -90,7 +90,7 @@ async fn fetch_and_store_tree_state( .get_tree_state(Request::new(block_id)) .await? .into_inner(); - let tree = CTree::read(&*hex::decode(&tree_state.tree)?)?; + let tree = CTree::read(&*hex::decode(&tree_state.sapling_tree)?)?; c.db()? .store_block(height, &block.hash, block.time, &tree)?; Ok(()) diff --git a/src/chain.rs b/src/chain.rs index 9df1db2..a3b0ae8 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -167,7 +167,7 @@ pub struct DecryptedNote { pub output_index: usize, } -pub fn to_output_description(co: &CompactOutput) -> CompactOutputDescription { +pub fn to_output_description(co: &CompactSaplingOutput) -> CompactOutputDescription { let mut cmu = [0u8; 32]; cmu.copy_from_slice(&co.cmu); let cmu = bls12_381::Scalar::from_repr(cmu).unwrap(); @@ -201,7 +201,7 @@ impl<'a, N: Parameters> AccountOutput<'a, N> { output_index: usize, block_output_index: usize, vtx: &'a CompactTx, - co: &CompactOutput, + co: &CompactSaplingOutput, ) -> Self { let mut epk_bytes = [0u8; 32]; epk_bytes.copy_from_slice(&co.epk); @@ -355,7 +355,7 @@ async fn get_tree_state(client: &mut CompactTxStreamerClient, height: u .await .unwrap() .into_inner(); - rep.tree + rep.sapling_tree } /* Using the IncrementalWitness */ diff --git a/src/generated/cash.z.wallet.sdk.rpc.rs b/src/generated/cash.z.wallet.sdk.rpc.rs index 6fda771..89c8138 100644 --- a/src/generated/cash.z.wallet.sdk.rpc.rs +++ b/src/generated/cash.z.wallet.sdk.rpc.rs @@ -46,15 +46,17 @@ pub struct CompactTx { pub fee: u32, /// inputs #[prost(message, repeated, tag="4")] - pub spends: ::prost::alloc::vec::Vec, + pub spends: ::prost::alloc::vec::Vec, /// outputs #[prost(message, repeated, tag="5")] - pub outputs: ::prost::alloc::vec::Vec, + pub outputs: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="6")] + pub actions: ::prost::alloc::vec::Vec, } -/// CompactSpend is a Sapling Spend Description as described in 7.3 of the Zcash +/// CompactSaplingSpend is a Sapling Spend Description as described in 7.3 of the Zcash /// protocol specification. #[derive(Clone, PartialEq, ::prost::Message)] -pub struct CompactSpend { +pub struct CompactSaplingSpend { /// nullifier (see the Zcash protocol specification) #[prost(bytes="vec", tag="1")] pub nf: ::prost::alloc::vec::Vec, @@ -62,17 +64,34 @@ pub struct CompactSpend { /// output is a Sapling Output Description as described in section 7.4 of the /// Zcash protocol spec. Total size is 948. #[derive(Clone, PartialEq, ::prost::Message)] -pub struct CompactOutput { +pub struct CompactSaplingOutput { /// note commitment u-coordinate #[prost(bytes="vec", tag="1")] pub cmu: ::prost::alloc::vec::Vec, /// ephemeral public key #[prost(bytes="vec", tag="2")] pub epk: ::prost::alloc::vec::Vec, - /// ciphertext and zkproof + /// first 52 bytes of ciphertext #[prost(bytes="vec", tag="3")] pub ciphertext: ::prost::alloc::vec::Vec, } +/// +/// (but not all fields are needed) +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CompactOrchardAction { + /// \[32\] The nullifier of the input note + #[prost(bytes="vec", tag="1")] + pub nullifier: ::prost::alloc::vec::Vec, + /// \[32\] The x-coordinate of the note commitment for the output note + #[prost(bytes="vec", tag="2")] + pub cmx: ::prost::alloc::vec::Vec, + /// \[32\] An encoding of an ephemeral Pallas public key + #[prost(bytes="vec", tag="3")] + pub ephemeral_key: ::prost::alloc::vec::Vec, + /// \[52\] The note plaintext component of the encCiphertext field + #[prost(bytes="vec", tag="4")] + pub ciphertext: ::prost::alloc::vec::Vec, +} /// A BlockID message contains identifiers to select a block: a height or a /// hash. Specification by hash is not implemented, but may be in the future. #[derive(Clone, PartialEq, ::prost::Message)] @@ -107,7 +126,8 @@ pub struct TxFilter { pub hash: ::prost::alloc::vec::Vec, } /// RawTransaction contains the complete transaction data. It also optionally includes -/// the block height in which the transaction was included. +/// the block height in which the transaction was included, or, when returned +/// by GetMempoolStream(), the latest block height. #[derive(Clone, PartialEq, ::prost::Message)] pub struct RawTransaction { /// exact data returned by Zcash 'getrawtransaction' @@ -231,6 +251,7 @@ pub struct TreeState { /// "main" or "test" #[prost(string, tag="1")] pub network: ::prost::alloc::string::String, + /// block height #[prost(uint64, tag="2")] pub height: u64, /// block id @@ -241,7 +262,10 @@ pub struct TreeState { pub time: u32, /// sapling commitment tree state #[prost(string, tag="5")] - pub tree: ::prost::alloc::string::String, + pub sapling_tree: ::prost::alloc::string::String, + /// orchard commitment tree state + #[prost(string, tag="6")] + pub orchard_tree: ::prost::alloc::string::String, } /// Results are sorted by height, which makes it easy to issue another /// request that picks up from where the previous left off. @@ -275,28 +299,6 @@ pub struct GetAddressUtxosReplyList { #[prost(message, repeated, tag="1")] pub address_utxos: ::prost::alloc::vec::Vec, } -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct PriceRequest { - /// List of timestamps(in sec) at which the price is being requested - #[prost(uint64, tag="1")] - pub timestamp: u64, - /// 3 letter currency-code - #[prost(string, tag="2")] - pub currency: ::prost::alloc::string::String, -} -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct PriceResponse { - /// Timestamp at which this price quote was fetched. Note, this may not be the same - /// as the request timestamp, but the server will respond with the closest timestamp that it has/can fetch - #[prost(int64, tag="1")] - pub timestamp: i64, - /// 3-letter currency code, matching the request - #[prost(string, tag="2")] - pub currency: ::prost::alloc::string::String, - /// price of ZEC - #[prost(double, tag="3")] - pub price: f64, -} /// Generated client implementations. pub mod compact_tx_streamer_client { #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] @@ -406,9 +408,9 @@ pub mod compact_tx_streamer_client { &mut self, request: impl tonic::IntoRequest, ) -> Result< - tonic::Response>, - tonic::Status, - > { + tonic::Response>, + tonic::Status, + > { self.inner .ready() .await @@ -424,45 +426,6 @@ pub mod compact_tx_streamer_client { ); self.inner.server_streaming(request.into_request(), path, codec).await } - /// Get the historical and current prices - pub async fn get_zec_price( - &mut self, - request: impl tonic::IntoRequest, - ) -> Result, tonic::Status> { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetZECPrice", - ); - self.inner.unary(request.into_request(), path, codec).await - } - pub async fn get_current_zec_price( - &mut self, - request: impl tonic::IntoRequest, - ) -> Result, tonic::Status> { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetCurrentZECPrice", - ); - self.inner.unary(request.into_request(), path, codec).await - } /// Return the requested full (not compact) transaction (as from zcashd) pub async fn get_transaction( &mut self, @@ -508,9 +471,9 @@ pub mod compact_tx_streamer_client { &mut self, request: impl tonic::IntoRequest, ) -> Result< - tonic::Response>, - tonic::Status, - > { + tonic::Response>, + tonic::Status, + > { self.inner .ready() .await @@ -526,29 +489,6 @@ pub mod compact_tx_streamer_client { ); self.inner.server_streaming(request.into_request(), path, codec).await } - /// Legacy API that is used as a fallback for t-Address support, if the server is running the old version (lwdv2) - pub async fn get_address_txids( - &mut self, - request: impl tonic::IntoRequest, - ) -> Result< - tonic::Response>, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetAddressTxids", - ); - self.inner.server_streaming(request.into_request(), path, codec).await - } pub async fn get_taddress_balance( &mut self, request: impl tonic::IntoRequest, @@ -602,9 +542,9 @@ pub mod compact_tx_streamer_client { &mut self, request: impl tonic::IntoRequest, ) -> Result< - tonic::Response>, - tonic::Status, - > { + tonic::Response>, + tonic::Status, + > { self.inner .ready() .await @@ -620,6 +560,30 @@ pub mod compact_tx_streamer_client { ); self.inner.server_streaming(request.into_request(), path, codec).await } + /// Return a stream of current Mempool transactions. This will keep the output stream open while + /// there are mempool transactions. It will close the returned stream when a new block is mined. + pub async fn get_mempool_stream( + &mut self, + request: impl tonic::IntoRequest, + ) -> Result< + tonic::Response>, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetMempoolStream", + ); + self.inner.server_streaming(request.into_request(), path, codec).await + } /// GetTreeState returns the note commitment tree state corresponding to the given block. /// See section 3.7 of the Zcash protocol specification. It returns several other useful /// values also (even though they can be obtained using GetBlock). @@ -666,9 +630,9 @@ pub mod compact_tx_streamer_client { &mut self, request: impl tonic::IntoRequest, ) -> Result< - tonic::Response>, - tonic::Status, - > { + tonic::Response>, + tonic::Status, + > { self.inner .ready() .await @@ -754,15 +718,6 @@ pub mod compact_tx_streamer_server { &self, request: tonic::Request, ) -> Result, tonic::Status>; - /// Get the historical and current prices - async fn get_zec_price( - &self, - request: tonic::Request, - ) -> Result, tonic::Status>; - async fn get_current_zec_price( - &self, - request: tonic::Request, - ) -> Result, tonic::Status>; /// Return the requested full (not compact) transaction (as from zcashd) async fn get_transaction( &self, @@ -784,17 +739,6 @@ pub mod compact_tx_streamer_server { &self, request: tonic::Request, ) -> Result, tonic::Status>; - ///Server streaming response type for the GetAddressTxids method. - type GetAddressTxidsStream: futures_core::Stream< - Item = Result, - > - + Send - + 'static; - /// Legacy API that is used as a fallback for t-Address support, if the server is running the old version (lwdv2) - async fn get_address_txids( - &self, - request: tonic::Request, - ) -> Result, tonic::Status>; async fn get_taddress_balance( &self, request: tonic::Request, @@ -822,6 +766,18 @@ pub mod compact_tx_streamer_server { &self, request: tonic::Request, ) -> Result, tonic::Status>; + ///Server streaming response type for the GetMempoolStream method. + type GetMempoolStreamStream: futures_core::Stream< + Item = Result, + > + + Send + + 'static; + /// Return a stream of current Mempool transactions. This will keep the output stream open while + /// there are mempool transactions. It will close the returned stream when a new block is mined. + async fn get_mempool_stream( + &self, + request: tonic::Request, + ) -> Result, tonic::Status>; /// GetTreeState returns the note commitment tree state corresponding to the given block. /// See section 3.7 of the Zcash protocol specification. It returns several other useful /// values also (even though they can be obtained using GetBlock). @@ -1020,84 +976,6 @@ pub mod compact_tx_streamer_server { }; Box::pin(fut) } - "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetZECPrice" => { - #[allow(non_camel_case_types)] - struct GetZECPriceSvc(pub Arc); - impl< - T: CompactTxStreamer, - > tonic::server::UnaryService - for GetZECPriceSvc { - type Response = super::PriceResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = self.0.clone(); - let fut = async move { - (*inner).get_zec_price(request).await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let inner = self.inner.clone(); - let fut = async move { - let inner = inner.0; - let method = GetZECPriceSvc(inner); - let codec = tonic::codec::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetCurrentZECPrice" => { - #[allow(non_camel_case_types)] - struct GetCurrentZECPriceSvc(pub Arc); - impl tonic::server::UnaryService - for GetCurrentZECPriceSvc { - type Response = super::PriceResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = self.0.clone(); - let fut = async move { - (*inner).get_current_zec_price(request).await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let inner = self.inner.clone(); - let fut = async move { - let inner = inner.0; - let method = GetCurrentZECPriceSvc(inner); - let codec = tonic::codec::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTransaction" => { #[allow(non_camel_case_types)] struct GetTransactionSvc(pub Arc); @@ -1220,48 +1098,6 @@ pub mod compact_tx_streamer_server { }; Box::pin(fut) } - "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetAddressTxids" => { - #[allow(non_camel_case_types)] - struct GetAddressTxidsSvc(pub Arc); - impl< - T: CompactTxStreamer, - > tonic::server::ServerStreamingService< - super::TransparentAddressBlockFilter, - > for GetAddressTxidsSvc { - type Response = super::RawTransaction; - type ResponseStream = T::GetAddressTxidsStream; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = self.0.clone(); - let fut = async move { - (*inner).get_address_txids(request).await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let inner = self.inner.clone(); - let fut = async move { - let inner = inner.0; - let method = GetAddressTxidsSvc(inner); - let codec = tonic::codec::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ); - let res = grpc.server_streaming(method, req).await; - Ok(res) - }; - Box::pin(fut) - } "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTaddressBalance" => { #[allow(non_camel_case_types)] struct GetTaddressBalanceSvc(pub Arc); @@ -1383,6 +1219,47 @@ pub mod compact_tx_streamer_server { }; Box::pin(fut) } + "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetMempoolStream" => { + #[allow(non_camel_case_types)] + struct GetMempoolStreamSvc(pub Arc); + impl< + T: CompactTxStreamer, + > tonic::server::ServerStreamingService + for GetMempoolStreamSvc { + type Response = super::RawTransaction; + type ResponseStream = T::GetMempoolStreamStream; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = self.0.clone(); + let fut = async move { + (*inner).get_mempool_stream(request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetMempoolStreamSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTreeState" => { #[allow(non_camel_case_types)] struct GetTreeStateSvc(pub Arc); diff --git a/src/main/rpc.rs b/src/main/rpc.rs index 982faa5..d569cc2 100644 --- a/src/main/rpc.rs +++ b/src/main/rpc.rs @@ -243,7 +243,7 @@ pub fn new_diversified_address() -> Result { Ok(address) } -#[get("/make_payment_uri", data = "")] +#[post("/make_payment_uri", data = "")] pub fn make_payment_uri(payment: Json) -> Result { let uri = warp_api_ffi::api::payment_uri::make_payment_uri( &payment.address, diff --git a/test-scripts/package.json b/test-scripts/package.json new file mode 100644 index 0000000..ad57ae7 --- /dev/null +++ b/test-scripts/package.json @@ -0,0 +1,12 @@ +{ + "name": "test-scripts", + "version": "1.0.0", + "main": "test.js", + "license": "MIT", + "devDependencies": { + "chakram": "^1.5.0" + }, + "dependencies": { + "mocha": "^10.0.0" + } +} diff --git a/test-scripts/rpc.sh b/test-scripts/rpc.sh new file mode 100644 index 0000000..6faaa33 --- /dev/null +++ b/test-scripts/rpc.sh @@ -0,0 +1,18 @@ +curl -X POST -H 'Content-Type: application/json' -d '{"coin": 0, "name": "test", "key": "zxviews1qvq5u9dnqqqqpqyc3wd00lql5njzdqx3xk670z5ldjuqkxm3lcphkvcyvpg2kavcze2edwj4f27k0s62q48dqdlnxlnr7jhczv2d8c9y2hw4uxgqrx9sluja52jqjtlqqsl65hd6p9xrdhzh7spgd0s4zud9xtv7sqepahchq06ng6w5vm36l9zjg0n2upkdv4k4yd7mhfsdpu9lqra0mnyq0k8v8f23j8pqq0ekr4f4aa7gf538eudpwck0yw3l74s37z4hf2xkyfq8eswwx"}' http://localhost:8000/new_account +curl -X GET http://localhost:8000/accounts +curl -X POST 'http://localhost:8000/set_active?coin=0&id_account=1' +curl -X GET http://localhost:8000/backup +curl -X GET http://localhost:8000/latest_height +curl -X GET http://localhost:8000/balance +curl -X GET http://localhost:8000/tx_history +curl -X GET http://localhost:8000/new_diversified_address +curl -X POST 'http://localhost:8000/sync?offset=0' +curl -X POST 'http://localhost:8000/rewind?height=1600000' +# Sync to latest height - 20 to calculate the witnesses for later +curl -X POST 'http://localhost:8000/sync?offset=20' +curl -X POST http://localhost:8000/mark_synced +curl -X POST -H 'Content-Type: application/json' -d '{"address": "zs1hn7qwpjz6p5n24hjhks73y6vn0tpk3c2cfu8wzgtgl4j9ht8ycjgjr47c94scce3uahaje9jkxn", "amount": 100000, "memo": "Hello"}' http://localhost:8000/make_payment_uri +curl -X GET http://localhost:8000/parse_payment_uri?uri=zcash%3Azs1hn7qwpjz6p5n24hjhks73y6vn0tpk3c2cfu8wzgtgl4j9ht8ycjgjr47c94scce3uahaje9jkxn%3Famount%3D0.001%26memo%3DSGVsbG8 +curl -X POST -H 'Content-Type: application/json' -d '{"recipients": [{"address": "zs1hn7qwpjz6p5n24hjhks73y6vn0tpk3c2cfu8wzgtgl4j9ht8ycjgjr47c94scce3uahaje9jkxn", "amount": 100000, "memo": "Hello", "reply_to": false, "subject": "hello", "max_amount_per_note": 0}], "confirmations": 10}' http://localhost:8000/create_offline_tx + +// offline signing and pay need an account with a secret key diff --git a/test-scripts/test.js b/test-scripts/test.js new file mode 100644 index 0000000..2096439 --- /dev/null +++ b/test-scripts/test.js @@ -0,0 +1,122 @@ +const chakram = require('chakram'), expect = chakram.expect; + +describe('warp', async function () { + var id_account; + + it("should allow creating new accounts", async function () { + const account = await chakram.post('http://localhost:8000/new_account', { + coin: 0, + name: 'zecpages', + key: 'zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz' + }) + + expect(account).to.have.status(200) + id_account = account.body + await chakram.post(`http://localhost:8000/set_active?coin=0&id_account=${id_account}`) + }) + + it("should give you the backup info", async function () { + const backup = await chakram.get('http://localhost:8000/backup') + + expect(backup.body).to.deep.equal({ + "fvk": "zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz", + "seed": null, + "sk": null + }) + }) + + it("should give you the latest block height", async function () { + const heights = await chakram.get('http://localhost:8000/latest_height') + + expect(heights.body.latest > 1000000) + }) + + it("should not have balance because we didn't sync", async function () { + const balance = await chakram.get('http://localhost:8000/balance') + + expect(balance.body).to.equals(0) + }) + + it("should not have a tx history because we didn't sync", async function () { + const tx_history = await chakram.get('http://localhost:8000/tx_history') + + expect(tx_history.body.length).to.equals(0) + }) + + it("should sync", async function () { + this.timeout(120000) + await chakram.post('http://localhost:8000/sync?offset=0') + }) + + it("should rewind and resync", async function () { + this.timeout(120000) + await chakram.post('http://localhost:8000/rewind?height=419200') + await chakram.post('http://localhost:8000/sync?offset=0') + const balance = await chakram.get('http://localhost:8000/balance') + expect(balance.body).to.not.equals(0) + }) + + it("should rewind to a past block", async function () { + this.timeout(120000) + await chakram.post('http://localhost:8000/rewind?height=1000000') + const balance = await chakram.get('http://localhost:8000/balance') + expect(balance.body).to.equals(364779600) // that's the balance at this block + }) + + it("can skip blocks", async function () { + await chakram.post('http://localhost:8000/rewind?height=1000000') + await chakram.post('http://localhost:8000/mark_synced') + const balance = await chakram.get('http://localhost:8000/balance') + expect(balance.body).to.equals(364779600) // that's the balance at this block + }) + + it("should create and parse payment uri", async function () { + const p = { + address: "zs1hn7qwpjz6p5n24hjhks73y6vn0tpk3c2cfu8wzgtgl4j9ht8ycjgjr47c94scce3uahaje9jkxn", + amount: 100000, + memo: "Hello" + } + const p_uri = await chakram.post('http://localhost:8000/make_payment_uri', p) + expect(p_uri.body).to.equals('zcash:zs1hn7qwpjz6p5n24hjhks73y6vn0tpk3c2cfu8wzgtgl4j9ht8ycjgjr47c94scce3uahaje9jkxn?amount=0.001&memo=SGVsbG8') + + const payment = await chakram.get('http://localhost:8000/parse_payment_uri?uri=zcash%3Azs1hn7qwpjz6p5n24hjhks73y6vn0tpk3c2cfu8wzgtgl4j9ht8ycjgjr47c94scce3uahaje9jkxn%3Famount%3D0.001%26memo%3DSGVsbG8') + expect(payment.body).to.deep.equal(p) + }) + + it("should create offline payment requests", async function () { + this.timeout(120000) + await chakram.post('http://localhost:8000/rewind?height=1600000') + await chakram.post('http://localhost:8000/sync?offset=10') // sync to compute note witnesses + const payment_request = { + recipients: [{ + address: "zs1hn7qwpjz6p5n24hjhks73y6vn0tpk3c2cfu8wzgtgl4j9ht8ycjgjr47c94scce3uahaje9jkxn", + amount: 100000, + memo: "Hello", + reply_to: false, + subject: "hello", + max_amount_per_note: 0 + }], + confirmations: 10 + } + + const unsigned_tx = await chakram.post('http://localhost:8000/create_offline_tx', payment_request) + expect(unsigned_tx.body.outputs).to.deep.equal( // we cannot check the other fields because they change from run to run + [ + { + "addr": "zs1hn7qwpjz6p5n24hjhks73y6vn0tpk3c2cfu8wzgtgl4j9ht8ycjgjr47c94scce3uahaje9jkxn", + "amount": 100000, + "memo": "f09f9ba14d53470a0a68656c6c6f0a48656c6c6f", + "ovk": "459121112ebe923d49af689b5aa2f67cccd62af3656549992e62aa373768ffef" + } + ] + ) + }) + + it("should give you the tx_history", async function () { + const txs = await chakram.get('http://localhost:8000/tx_history') + expect(txs.body.length).to.not.equals(0) + }) + + // cannot test signing without a secret key + // need to setup a dummy account +})