use std::sync::{Arc, Mutex, PoisonError};
use std::time::Duration;
use alloy::dyn_abi::Eip712Domain;
use alloy::primitives::{Address, U256};
use alloy::signers::local::PrivateKeySigner;
use decdn_incentive::buyer_pool::{AdvanceOutcome, BuyerPoolStore};
use decdn_incentive::payment_pool::PaymentPool;
use decdn_incentive::{CapabilityGrant, LaneKey, PoolId};
use iroh::{Endpoint, EndpointAddr, RelayUrl};
use crate::buyer_pool::{ProgressWrite, WatermarkRow};
use crate::discovery::{self, NodeCandidate};
use crate::funding::{RunFunding, build_ctx_for_fetch};
use crate::ordered_writes::OrderedWrites;
use crate::session::{ChainConfig, DepositPolicy, FetchMarks, PullSite};
use crate::{
Connections, CredentialSlot, Cumulative, LaneHandle, LaneLedgers, PeerSource, PoolContext,
PoolLedger, PullDeadlines, VoucherProgress,
};
pub struct SettleOnDrop<F: FnOnce()> {
settle: Option<F>,
}
impl<F: FnOnce()> std::fmt::Debug for SettleOnDrop<F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SettleOnDrop")
.field("armed", &self.settle.is_some())
.finish()
}
}
impl<F: FnOnce()> SettleOnDrop<F> {
pub const fn new(settle: F) -> Self {
Self {
settle: Some(settle),
}
}
pub fn disarm(mut self) {
self.settle = None;
}
}
impl<F: FnOnce()> Drop for SettleOnDrop<F> {
fn drop(&mut self) {
if let Some(settle) = self.settle.take() {
settle();
}
}
}
fn persist_watermark(
store: &dyn BuyerPoolStore,
row: WatermarkRow,
lane: LaneKey,
progress: &VoucherProgress,
) {
let Some(write) = ProgressWrite::of(progress) else {
return;
};
let label = match write {
ProgressWrite::Rebase { .. } => "rebased",
ProgressWrite::Advance { .. } => "advanced",
};
let outcome = write.apply(store, row, lane);
let (bytes_delivered, amount) = progress.totals();
let pool_id = lane.pool_id;
match outcome {
Ok(AdvanceOutcome::Advanced) => {}
Ok(other) => tracing::warn!(
write = label,
%bytes_delivered,
%amount,
"{label} voucher watermark not persisted for pool {pool_id} (provider {}): \
{other:?}; the next reuse may re-sign a stale watermark, which that provider \
rejects",
lane.provider
),
Err(e) => tracing::warn!(
write = label,
%bytes_delivered,
%amount,
"failed to persist {label} voucher watermark for pool {pool_id} (provider {}): \
{e}; the next reuse may re-sign a stale watermark, which that provider rejects",
lane.provider
),
}
}
pub const STALL_WINDOW: Duration = Duration::from_secs(30);
#[derive(Debug, Clone, Copy)]
pub enum LanePayer<'a> {
Owned(&'a Arc<PrivateKeySigner>),
Delegated(&'a CredentialSlot),
}
pub struct LaneDeps<'a, P> {
pub endpoint: &'a Endpoint,
pub store: &'a Arc<dyn BuyerPoolStore>,
pub contract: &'a PaymentPool::PaymentPoolInstance<P>,
pub rpc: &'a P,
pub slash_dom: &'a Eip712Domain,
pub self_address: Address,
pub token: Address,
pub chain: &'a ChainConfig,
pub site: &'a PullSite,
pub deposit: DepositPolicy,
pub namespace_id: [u8; 32],
pub max_rate_per_mb: u64,
pub max_blob_bytes: u64,
pub deadlines: PullDeadlines,
pub connections: &'a Connections,
pub writes: &'a OrderedWrites,
pub funding: &'a RunFunding,
pub marks: Option<&'a dyn FetchMarks>,
}
impl<P> std::fmt::Debug for LaneDeps<'_, P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LaneDeps")
.field("self_address", &self.self_address)
.field("chain", &self.chain)
.field("deposit", &self.deposit)
.finish_non_exhaustive()
}
}
fn lane_ledger(
ledgers: Option<&LaneLedgers>,
lane: LaneKey,
ctx: PoolContext,
) -> (Arc<PoolLedger>, Arc<Mutex<PoolContext>>) {
let seed = Cumulative {
bytes: ctx.prior_bytes_delivered,
amount: ctx.prior_amount,
};
let fresh_deposit = ctx.deposit;
match ledgers {
Some(reg) => {
let h = reg.get_or_insert(lane, || LaneHandle {
ledger: Arc::new(PoolLedger::new(seed)),
ctx: Arc::new(Mutex::new(ctx)),
});
{
let mut g = h.ctx.lock().unwrap_or_else(PoisonError::into_inner);
g.deposit = g.deposit.max(fresh_deposit);
}
(h.ledger, h.ctx)
}
None => (Arc::new(PoolLedger::new(seed)), Arc::new(Mutex::new(ctx))),
}
}
pub(crate) struct MultiLane<'a> {
pub(crate) provider: Address,
pub(crate) signer: Address,
pub(crate) pool_id: PoolId,
pub(crate) prior_amount: U256,
pub(crate) ctx: Arc<Mutex<PoolContext>>,
pub(crate) ledger: Arc<PoolLedger>,
pub(crate) source: PeerSource<'a>,
}
pub(crate) fn lane_target(
candidate: &NodeCandidate,
addr: Option<std::net::SocketAddr>,
relays: &[RelayUrl],
) -> EndpointAddr {
let mut target = EndpointAddr::new(candidate.node_id);
if let Some(addr) = addr {
target = target.with_ip_addr(addr);
}
if let Some(url) = relays.first() {
target = target.with_relay_url(url.clone());
}
discovery::with_dial_addrs(target, candidate)
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn build_multi_lane<'a, P>(
deps: &LaneDeps<'a, P>,
payer: LanePayer<'_>,
voucher_dom: &Eip712Domain,
provider: Address,
target: EndpointAddr,
open_lock: Option<&tokio::sync::Mutex<()>>,
ledgers: Option<&LaneLedgers>,
) -> anyhow::Result<MultiLane<'a>>
where
P: alloy::providers::Provider + Clone,
{
let (signer, grant) = match payer {
LanePayer::Owned(signer) => (Arc::clone(signer), None),
LanePayer::Delegated(slot) => {
let credential = slot.current();
let grant = CapabilityGrant::from_signed_capability(&credential.capability);
(credential.signer, Some(grant))
}
};
let (ctx, spend) = {
let _open_guard = match (open_lock, &grant) {
(Some(lock), None) => Some(lock.lock().await),
_ => None,
};
build_ctx_for_fetch(deps, grant.as_ref(), &signer, voucher_dom, provider).await?
};
let pool_id = ctx.pool_id;
let prior_amount = ctx.prior_amount;
let lane = LaneKey {
pool_id,
signer: signer.address(),
provider,
};
let (ledger, ctx) = lane_ledger(ledgers, lane, ctx);
if let Some(spend) = spend {
deps.funding.join_lane(lane, spend, &ledger);
}
let source = lane_source(
deps,
target,
Arc::clone(&ctx),
Arc::clone(&ledger),
provider,
);
if let Some(marks) = deps.marks {
marks.lane_built();
}
Ok(MultiLane {
provider,
signer: lane.signer,
pool_id,
prior_amount,
ctx,
ledger,
source,
})
}
fn lane_source<'a, P>(
deps: &LaneDeps<'a, P>,
target: EndpointAddr,
ctx: Arc<Mutex<PoolContext>>,
ledger: Arc<PoolLedger>,
provider: Address,
) -> PeerSource<'a> {
PeerSource::new(
deps.endpoint,
target,
ctx,
ledger,
deps.slash_dom,
provider,
deps.namespace_id,
deps.max_blob_bytes,
deps.max_rate_per_mb,
deps.deadlines,
Some(deps.connections.clone()),
)
}
pub(crate) struct FaceLaneHandle {
pub(crate) pool_id: PoolId,
pub(crate) provider: Address,
pub(crate) signer: Address,
pub(crate) prior_amount: U256,
pub(crate) ledger: Arc<PoolLedger>,
pub(crate) ctx: Arc<Mutex<PoolContext>>,
}
fn stream_lane_watermarks(lanes: &[FaceLaneHandle]) -> Vec<LaneWatermark> {
let mut per_ledger: Vec<(&FaceLaneHandle, U256)> = Vec::new();
for l in lanes {
match per_ledger
.iter_mut()
.find(|(first, _)| Arc::ptr_eq(&first.ledger, &l.ledger))
{
Some((_, prior)) => *prior = (*prior).min(l.prior_amount),
None => per_ledger.push((l, l.prior_amount)),
}
}
per_ledger
.into_iter()
.map(|(l, prior_amount)| LaneWatermark {
pool_id: l.pool_id,
signer: l.signer,
provider: l.provider,
prior_amount,
rebase_anchor: l.ledger.take_unsaved_rebase(),
settlement: l.ledger.settlement(),
})
.collect()
}
type LaneWrite = (LaneKey, VoucherProgress);
fn face_watermark_writes(handles: &[FaceLaneHandle]) -> Vec<LaneWrite> {
multi_lane_watermarks(&stream_lane_watermarks(handles))
}
fn write_watermarks(store: &dyn BuyerPoolStore, row: WatermarkRow, writes: Vec<LaneWrite>) {
for (lane, vprogress) in writes {
persist_watermark(store, row, lane, &vprogress);
}
}
pub(crate) fn queue_face_watermarks(
writes: &OrderedWrites,
store: &Arc<dyn BuyerPoolStore>,
row: WatermarkRow,
handles: &[FaceLaneHandle],
) -> tokio::sync::oneshot::Receiver<()> {
queue_watermark_writes(writes, store, row, face_watermark_writes(handles))
}
fn queue_watermark_writes(
writes: &OrderedWrites,
store: &Arc<dyn BuyerPoolStore>,
row: WatermarkRow,
lane_writes: Vec<LaneWrite>,
) -> tokio::sync::oneshot::Receiver<()> {
let store = Arc::clone(store);
writes.queue_awaitable(move || write_watermarks(store.as_ref(), row, lane_writes))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct LaneWatermark {
pool_id: PoolId,
signer: Address,
provider: Address,
prior_amount: U256,
settlement: Cumulative,
rebase_anchor: Option<Cumulative>,
}
fn multi_lane_watermarks(lanes: &[LaneWatermark]) -> Vec<(LaneKey, VoucherProgress)> {
lanes
.iter()
.map(|l| {
(
LaneKey {
pool_id: l.pool_id,
signer: l.signer,
provider: l.provider,
},
VoucherProgress::from_cumulative(l.settlement, l.prior_amount)
.with_rebase_anchor(l.rebase_anchor),
)
})
.collect()
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::panic
)]
mod tests;