Skip to content

Commit

Permalink
feat: integrate sphinx for onion packet
Browse files Browse the repository at this point in the history
  • Loading branch information
doitian committed Sep 29, 2024
1 parent c735c10 commit 042c425
Show file tree
Hide file tree
Showing 5 changed files with 369 additions and 146 deletions.
2 changes: 2 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ pub enum Error {
NetworkGraphError(#[from] GraphError),
#[error("Invalid peer message: {0}")]
InvalidPeerMessage(String),
#[error("Onion packet error: {0}")]
InvalidOnionPacket(crate::fiber::types::Error),
}

pub type Result<T> = std::result::Result<T, Error>;
60 changes: 34 additions & 26 deletions src/fiber/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ use secp256k1::XOnlyPublicKey;
use tracing::{debug, error, info, warn};

use crate::{
fiber::{
network::get_chain_hash,
types::{ChannelUpdate, OnionPacket},
},
fiber::{network::get_chain_hash, types::ChannelUpdate},
invoice::InvoiceStore,
};
use ckb_hash::{blake2b_256, new_blake2b};
Expand All @@ -26,8 +23,8 @@ use musig2::{
PubNonce, SecNonce,
};
use ractor::{
async_trait as rasync_trait, Actor, ActorProcessingErr, ActorRef, OutputPort, RpcReplyPort,
SpawnErr,
async_trait as rasync_trait, call, Actor, ActorProcessingErr, ActorRef, OutputPort,
RpcReplyPort, SpawnErr,
};

use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -454,26 +451,35 @@ where
// If there is a next hop, we should send the AddTlc message to the next hop.
// If this is the last hop, we should check the payment hash and amount and then
// try to fulfill the payment, find the corresponding payment preimage from payment hash.
let mut forward_to_next_hop = false;
let mut preimage = None;
if let Ok(onion_packet) = OnionPacket::deserialize(&add_tlc.onion_packet) {
if let Some(onion_info) = onion_packet.peek() {
if onion_info.next_hop.is_some() {
forward_to_next_hop = true;
} else {
// check the payment hash and amount
if onion_info.payment_hash != add_tlc.payment_hash
|| onion_info.amount != add_tlc.amount
{
return Err(ProcessingChannelError::InvalidParameter(
"Payment hash or amount mismatch".to_string(),
));
}
// TODO: check the expiry time, if expired, we should return an error.
let mut next_onion_packet: Option<Vec<u8>> = None;

if !add_tlc.onion_packet.is_empty() {
let peeled_packet = call!(self.network, |tx| NetworkActorMessage::Command(
NetworkActorCommand::PeelPaymentOnionPacket(
add_tlc.onion_packet.clone(),
add_tlc.payment_hash.clone(),
tx
)
))
.expect("call network")
.map_err(|err| ProcessingChannelError::PeelingOnionPacketError(err))?;

// if this is the last hop, store the preimage.
preimage = onion_info.preimage;
}
// check the payment hash and amount
if peeled_packet.current.payment_hash != add_tlc.payment_hash
|| peeled_packet.current.amount != add_tlc.amount
{
return Err(ProcessingChannelError::InvalidParameter(
"Payment hash or amount mismatch".to_string(),
));
}
// TODO: check the expiry time, if expired, we should return an error.

if peeled_packet.is_last() {
// if this is the last hop, store the preimage.
preimage = peeled_packet.current.preimage;
} else {
next_onion_packet = peeled_packet.next.map(|packet| packet.data);
}
}

Expand All @@ -494,11 +500,11 @@ where
// while we have crashed. We need a way to make sure that the peer will resend
// this message, and our processing of this message is idempotent.

if forward_to_next_hop {
if let Some(onion_packet) = next_onion_packet {
self.network
.send_message(NetworkActorMessage::Command(
NetworkActorCommand::SendPaymentOnionPacket(
add_tlc.onion_packet.clone(),
onion_packet,
Some((state.get_id(), tlc.get_id())),
),
))
Expand Down Expand Up @@ -1895,6 +1901,8 @@ pub enum ProcessingChannelError {
Musig2VerifyError(#[from] VerifyError),
#[error("Musig2 SigningError: {0}")]
Musig2SigningError(#[from] SigningError),
#[error("Failed to peel onion packet: {0}")]
PeelingOnionPacketError(String),
}

bitflags! {
Expand Down
8 changes: 4 additions & 4 deletions src/fiber/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use super::path::NodeHeap;
use super::types::Pubkey;
use super::types::{ChannelAnnouncement, ChannelUpdate, Hash256, NodeAnnouncement};
use crate::fiber::path::{NodeHeapElement, ProbabilityEvaluator};
use crate::fiber::types::PaymentOnionInfo;
use crate::fiber::types::PaymentHopData;
use crate::invoice::CkbInvoice;
use ckb_jsonrpc_types::JsonBytes;
use ckb_types::packed::{OutPoint, Script};
Expand Down Expand Up @@ -471,7 +471,7 @@ where
pub fn build_route(
&self,
payment_request: SendPaymentCommand,
) -> Result<Vec<PaymentOnionInfo>, GraphError> {
) -> Result<Vec<PaymentHopData>, GraphError> {
let source = self.get_source_pubkey();
let (target, amount, payment_hash, preimage, udt_type_script) = payment_request
.check_valid()
Expand Down Expand Up @@ -531,7 +531,7 @@ where

// make sure the final hop's amount is the same as the payment amount
// the last hop will check the amount from TLC and the amount from the onion packet
onion_infos.push(PaymentOnionInfo {
onion_infos.push(PaymentHopData {
amount: current_amount,
payment_hash,
next_hop,
Expand All @@ -549,7 +549,7 @@ where
} else {
None
};
onion_infos.push(PaymentOnionInfo {
onion_infos.push(PaymentHopData {
amount: current_amount,
payment_hash,
next_hop,
Expand Down
111 changes: 70 additions & 41 deletions src/fiber/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use ractor::{
RactorErr, RpcReplyPort, SupervisionEvent,
};
use rand::Rng;
use secp256k1::Message;
use secp256k1::{Message, Secp256k1};
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use std::borrow::Cow;
Expand Down Expand Up @@ -70,7 +70,11 @@ use crate::fiber::channel::{
AddTlcCommand, AddTlcResponse, TxCollaborationCommand, TxUpdateCommand,
};
use crate::fiber::graph::{ChannelInfo, NodeInfo, PaymentSession};
use crate::fiber::types::{secp256k1_instance, FiberChannelMessage, OnionPacket, TxSignatures};
use crate::fiber::types::{
secp256k1_instance, FiberChannelMessage, PaymentOnionPacket, PeeledPaymentOnionPacket,
TxSignatures,
};
use crate::fiber::KeyPair;
use crate::invoice::{CkbInvoice, InvoiceStore};
use crate::{unwrap_or_return, Error};

Expand Down Expand Up @@ -139,7 +143,14 @@ pub enum NetworkActorCommand {
),
// Send a command to a channel.
ControlFiberChannel(ChannelCommandWithId),
// SendPaymentOnionPacket(peeled_packet_buf, previous_tlc),
// The first parameter is the peeled onion in binary via `PeeledOnionPacket::serialize`.
SendPaymentOnionPacket(Vec<u8>, Option<(Hash256, u64)>),
PeelPaymentOnionPacket(
Vec<u8>, // onion_packet
Hash256, // payment_hash
RpcReplyPort<Result<PeeledPaymentOnionPacket, String>>,
),
UpdateChannelFunding(Hash256, Transaction, FundingRequest),
SignTx(PeerId, Hash256, Transaction, Option<Vec<Vec<u8>>>),
// Broadcast node/channel information.
Expand Down Expand Up @@ -1198,43 +1209,56 @@ where
}

// TODO: we should check the OnionPacket is valid or not, only the current node can decrypt it.
NetworkActorCommand::SendPaymentOnionPacket(packet, previous_tlc) => {
if let Ok(mut onion_packet) = OnionPacket::deserialize(&packet) {
if let Ok(info) = onion_packet.shift() {
debug!("Processing onion packet info: {:?}", info);
let channel_outpoint = &info
.channel_outpoint
.expect("valid onion packet contains channel outpoint");
let channel_id = match state.outpoint_channel_map.get(channel_outpoint) {
Some(channel_id) => channel_id,
None => {
// TODO: Return error to the sender. This is a payment failure.
error!("Failed to process onion packet: channel id not found for channel outpoint {:?}. Are we connected to the peer?", channel_outpoint);
return Ok(());
}
};
let (send, recv) = oneshot::channel::<Result<AddTlcResponse, String>>();
let rpc_reply = RpcReplyPort::from(send);
let command = ChannelCommand::AddTlc(
AddTlcCommand {
amount: info.amount,
preimage: None,
payment_hash: Some(info.payment_hash),
expiry: info.expiry.into(),
hash_algorithm: info.tlc_hash_algorithm,
onion_packet: onion_packet.serialize(),
previous_tlc,
},
rpc_reply,
);
state.send_command_to_channel(*channel_id, command).await?;
let res = recv.await.expect("recv add tlc response");
info!("send onion packet: {:?}", res);
}
} else {
info!("onion packet is empty, ignore it");
NetworkActorCommand::SendPaymentOnionPacket(peeled_packet_buf, previous_tlc) => {
if let Ok(peeled_packet) = PeeledPaymentOnionPacket::deserialize(&peeled_packet_buf)
{
let current_hop_info = peeled_packet.current;
debug!("Processing onion packet info: {:?}", current_hop_info);
let channel_outpoint = &current_hop_info
.channel_outpoint
.expect("valid onion packet contains channel outpoint");
let channel_id = match state.outpoint_channel_map.get(channel_outpoint) {
Some(channel_id) => channel_id,
None => {
// TODO: Return error to the sender. This is a payment failure.
error!("Failed to process onion packet: channel id not found for channel outpoint {:?}. Are we connected to the peer?", channel_outpoint);
return Ok(());
}
};
let (send, recv) = oneshot::channel::<Result<AddTlcResponse, String>>();
let rpc_reply = RpcReplyPort::from(send);
let command = ChannelCommand::AddTlc(
AddTlcCommand {
amount: current_hop_info.amount,
preimage: None,
payment_hash: Some(current_hop_info.payment_hash),
expiry: current_hop_info.expiry.into(),
hash_algorithm: current_hop_info.tlc_hash_algorithm,
onion_packet: peeled_packet
.next
.map(|next| next.data)
.unwrap_or_default(),
previous_tlc,
},
rpc_reply,
);
state.send_command_to_channel(*channel_id, command).await?;
let res = recv.await.expect("recv add tlc response");
info!("send onion packet: {:?}", res);
}
}
NetworkActorCommand::PeelPaymentOnionPacket(onion_packet, payment_hash, reply) => {
let response = PaymentOnionPacket::new(onion_packet)
.peel(
&state.private_key,
Some(payment_hash.as_ref()),
&Secp256k1::new(),
)
.map_err(|err| err.to_string());

let _ = reply.send(response);
}

NetworkActorCommand::UpdateChannelFunding(channel_id, transaction, request) => {
let old_tx = transaction.into_view();
let mut tx = FundingTx::new();
Expand Down Expand Up @@ -1811,12 +1835,17 @@ where
// handle the payment process
let payment_session = PaymentSession::new(payment_request.clone(), 3);

let onion_path = graph.build_route(payment_request.clone())?;
assert!(!onion_path.is_empty());
let onion_packet = OnionPacket::new(onion_path).serialize();
let hops_infos = graph.build_route(payment_request.clone())?;
assert!(!hops_infos.is_empty());

// generate session key
let session_key = Privkey::from_slice(KeyPair::generate_random_key().as_ref());
let peeled_packet =
PeeledPaymentOnionPacket::create(session_key, hops_infos, &Secp256k1::signing_only())
.map_err(|err| Error::InvalidOnionPacket(err))?;

let res = my_self.send_message(NetworkActorMessage::Command(
NetworkActorCommand::SendPaymentOnionPacket(onion_packet.clone(), None),
NetworkActorCommand::SendPaymentOnionPacket(peeled_packet.serialize(), None),
));
info!("send_payment: {:?} => result: {:?}", payment_request, res);
Ok(payment_session.payment_hash())
Expand Down
Loading

0 comments on commit 042c425

Please sign in to comment.