diff --git a/src/fiber/config.rs b/src/fiber/config.rs index 39e21c9e..1ef0b751 100644 --- a/src/fiber/config.rs +++ b/src/fiber/config.rs @@ -7,6 +7,7 @@ use clap_serde_derive::{ }; use serde::{Deserialize, Deserializer, Serializer}; use std::{fs, path::PathBuf}; +use tentacle::secio::{PublicKey, SecioKeyPair}; pub const CKB_SHANNONS: u64 = 100_000_000; // 1 CKB = 10 ^ 8 shannons pub const DEFAULT_MIN_INBOUND_LIQUIDITY: u64 = 100 * CKB_SHANNONS; // 100 CKB for minimal inbound liquidity @@ -307,6 +308,14 @@ impl FiberConfig { self.announce_node_interval_seconds .unwrap_or(DEFAULT_ANNOUNCE_NODE_INTERVAL_SECONDS) } + + pub fn public_key(&self) -> PublicKey { + let secio_kp: SecioKeyPair = self + .read_or_generate_secret_key() + .expect("read or generate secret key") + .into(); + secio_kp.public_key() + } } // Basically ckb_sdk::types::NetworkType. But we added a `Mocknet` variant. diff --git a/src/fiber/network.rs b/src/fiber/network.rs index 539e2829..3d0df8d9 100644 --- a/src/fiber/network.rs +++ b/src/fiber/network.rs @@ -3238,11 +3238,7 @@ pub async fn start_network< store: S, channel_subscribers: ChannelSubscribers, ) -> ActorRef { - let secio_kp: SecioKeyPair = config - .read_or_generate_secret_key() - .expect("read or generate secret key") - .into(); - let my_pubkey = secio_kp.public_key(); + let my_pubkey = config.public_key(); let my_peer_id = PeerId::from_public_key(&my_pubkey); let (actor, _handle) = Actor::spawn_linked( diff --git a/src/main.rs b/src/main.rs index d32bf6d0..86b42f1b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -63,6 +63,7 @@ pub async fn main() { let store = Store::new(config.fiber.as_ref().unwrap().store_path()); let subscribers = ChannelSubscribers::default(); + let mut node_public_key = None; let fiber_command_sender = match config.fiber { Some(fiber_config) => { @@ -70,6 +71,7 @@ pub async fn main() { // for the user to fix the error and start the node. let ckb_config = config.ckb.expect("ckb service is required for ckb service. \ Add ckb service to the services list in the config file and relevant configuration to the ckb section of the config file."); + node_public_key = Some(fiber_config.public_key()); let _ = init_contracts_context(fiber_config.network, Some(&ckb_config)); @@ -182,14 +184,21 @@ pub async fn main() { // Start rpc service let rpc_server_handle = match config.rpc { - Some(rpc_config) => { + Some(config) => { if fiber_command_sender.is_none() && cch_actor.is_none() { error!("Rpc service requires ckb and cch service to be started. Exiting."); return; } info!("Starting rpc"); - let handle = start_rpc(rpc_config, fiber_command_sender, cch_actor, store).await; + let handle = start_rpc( + config, + fiber_command_sender, + cch_actor, + store, + node_public_key, + ) + .await; Some(handle) } None => None, diff --git a/src/rpc/invoice.rs b/src/rpc/invoice.rs index b3de8bb7..9ac6bdb8 100644 --- a/src/rpc/invoice.rs +++ b/src/rpc/invoice.rs @@ -7,8 +7,10 @@ use crate::invoice::{CkbInvoice, Currency, InvoiceBuilder, InvoiceStore}; use ckb_jsonrpc_types::Script; use jsonrpsee::types::error::CALL_EXECUTION_FAILED_CODE; use jsonrpsee::{core::async_trait, proc_macros::rpc, types::ErrorObjectOwned}; +use secp256k1::PublicKey as Publickey; use serde::{Deserialize, Serialize}; use serde_with::serde_as; +use tentacle::secio::PublicKey; #[serde_as] #[derive(Serialize, Deserialize)] @@ -62,11 +64,12 @@ pub trait InvoiceRpc { pub struct InvoiceRpcServerImpl { pub store: S, + pub public_key: Option, } impl InvoiceRpcServerImpl { - pub fn new(store: S) -> Self { - Self { store } + pub fn new(store: S, public_key: Option) -> Self { + Self { store, public_key } } } @@ -102,6 +105,12 @@ where invoice_builder = invoice_builder.hash_algorithm(hash_algorithm); }; + if let Some(public_key) = &self.public_key { + invoice_builder = invoice_builder.payee_pub_key( + Publickey::from_slice(public_key.inner_ref()).expect("public key must be valid"), + ); + } + match invoice_builder.build() { Ok(invoice) => match self .store diff --git a/src/rpc/mod.rs b/src/rpc/mod.rs index 396c11a4..74133f9e 100644 --- a/src/rpc/mod.rs +++ b/src/rpc/mod.rs @@ -17,6 +17,7 @@ use invoice::{InvoiceRpcServer, InvoiceRpcServerImpl}; use jsonrpsee::server::{Server, ServerHandle}; use peer::{PeerRpcServer, PeerRpcServerImpl}; use ractor::ActorRef; +use tentacle::secio::PublicKey; use tokio::sync::mpsc::Sender; pub type InvoiceCommandWithReply = (InvoiceCommand, Sender>); @@ -53,10 +54,11 @@ pub async fn start_rpc>, cch_actor: Option>, store: S, + node_publick_key: Option, ) -> ServerHandle { let listening_addr = config.listening_addr.as_deref().unwrap_or("[::]:0"); let server = build_server(listening_addr); - let mut methods = InvoiceRpcServerImpl::new(store.clone()).into_rpc(); + let mut methods = InvoiceRpcServerImpl::new(store.clone(), node_publick_key).into_rpc(); if let Some(network_actor) = network_actor { let peer = PeerRpcServerImpl::new(network_actor.clone()); let channel = ChannelRpcServerImpl::new(network_actor, store); diff --git a/tests/bruno/e2e/router-pay/13-node3-gen-invoice-later.bru b/tests/bruno/e2e/router-pay/13-node3-gen-invoice-later.bru new file mode 100644 index 00000000..beb6853a --- /dev/null +++ b/tests/bruno/e2e/router-pay/13-node3-gen-invoice-later.bru @@ -0,0 +1,65 @@ +meta { + name: generate a invoice + type: http + seq: 13 +} + +post { + url: {{NODE3_RPC_URL}} + body: json + auth: none +} + +headers { + Content-Type: application/json + Accept: application/json +} + +body:json { + { + "id": "42", + "jsonrpc": "2.0", + "method": "new_invoice", + "params": [ + { + "amount": "0x613", + "currency": "Fibb", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_cltv": "0x28", + "payment_preimage": "{{payment_preimage}}" + } + ] + } +} + +assert { + res.body.error: isUndefined + res.body.result: isDefined +} + +script:pre-request { + // generate random preimage + function generateRandomPreimage() { + let hash = '0x'; + for (let i = 0; i < 64; i++) { + hash += Math.floor(Math.random() * 16).toString(16); + } + return hash; + } + const payment_preimage = generateRandomPreimage(); + bru.setVar("payment_preimage", payment_preimage); + let hash_algorithm = bru.getEnvVar("HASH_ALGORITHM"); + if (hash_algorithm !== null) { + let body = req.getBody(); + body.params[0].hash_algorithm = hash_algorithm; + req.setBody(body); + } +} + +script:post-response { + // Sleep for sometime to make sure current operation finishes before next request starts. + await new Promise(r => setTimeout(r, 100)); + console.log("generated result: ", res.body.result); + bru.setVar("encoded_invoice", res.body.result.invoice_address); +} diff --git a/tests/bruno/e2e/router-pay/14-node1-send-payment-with-invoice.bru b/tests/bruno/e2e/router-pay/14-node1-send-payment-with-invoice.bru new file mode 100644 index 00000000..8ccd33fa --- /dev/null +++ b/tests/bruno/e2e/router-pay/14-node1-send-payment-with-invoice.bru @@ -0,0 +1,47 @@ +meta { + name: Node1 send payment with router only with invoice + type: http + seq: 14 +} + +post { + url: {{NODE1_RPC_URL}} + body: json + auth: none +} + +headers { + Content-Type: application/json + Accept: application/json +} + +body:json { + { + "id": "42", + "jsonrpc": "2.0", + "method": "send_payment", + "params": [ + { + "invoice": "{{encoded_invoice}}" + } + ] + } +} + +assert { + res.body.error: isUndefined +} + + +script:pre-request { + // sleep for a while + await new Promise(r => setTimeout(r, 1000)); +} + + +script:post-response { + // Sleep for sometime to make sure current operation finishes before next request starts. + await new Promise(r => setTimeout(r, 100)); + console.log("send payment result: ", res.body); + +} diff --git a/tests/bruno/e2e/router-pay/13-node1-send-payment-keysend.bru b/tests/bruno/e2e/router-pay/15-node1-send-payment-keysend.bru similarity index 87% rename from tests/bruno/e2e/router-pay/13-node1-send-payment-keysend.bru rename to tests/bruno/e2e/router-pay/15-node1-send-payment-keysend.bru index 7636baa3..085dd101 100644 --- a/tests/bruno/e2e/router-pay/13-node1-send-payment-keysend.bru +++ b/tests/bruno/e2e/router-pay/15-node1-send-payment-keysend.bru @@ -1,7 +1,7 @@ meta { name: Node1 send payment with router in keysend mode type: http - seq: 13 + seq: 15 } post { @@ -34,6 +34,11 @@ assert { res.body.error: isUndefined } +script:pre-request { + // sleep for a while + await new Promise(r => setTimeout(r, 1000)); +} + script:post-response { // Sleep for sometime to make sure current operation finishes before next request starts. await new Promise(r => setTimeout(r, 100));