nwep-rs 0.1.8

Rust bindings for the NWEP (WEB/1) protocol library
Documentation
#![allow(unsafe_op_in_unsafe_fn)]

use crate::anchor::AnchorSet;
use crate::bls::BlsKeypair;
use crate::checkpoint::Checkpoint;
use crate::error::{Error, check};
use crate::ffi;
use crate::merkle::MerkleLog;
use crate::types::Tstamp;

/// `AnchorServerSettings` controls the behaviour of an [`AnchorServer`] instance.
///
/// All fields are optional.  The default value leaves `on_proposal` unset, which
/// causes the server to accept every incoming proposal automatically.
pub struct AnchorServerSettings {
    /// Optional callback invoked when another anchor proposes a new checkpoint.
    ///
    /// Receives the proposed (unsigned) [`Checkpoint`].  Return `Ok(())` to accept and
    /// proceed with signing, or `Err(e)` to refuse the proposal.  When `None`, all
    /// proposals are accepted.
    pub on_proposal: Option<Box<dyn Fn(&Checkpoint) -> Result<(), Error> + Send>>,
}

impl Default for AnchorServerSettings {
    fn default() -> Self {
        AnchorServerSettings { on_proposal: None }
    }
}

struct Callbacks {
    on_proposal: Option<Box<dyn Fn(&Checkpoint) -> Result<(), Error> + Send>>,
}

unsafe extern "C" fn proposal_cb(
    user_data: *mut std::ffi::c_void,
    cp: *const ffi::nwep_checkpoint,
) -> std::ffi::c_int {
    let cb = &*(user_data as *const Callbacks);
    if let Some(f) = &cb.on_proposal {
        let checkpoint = Checkpoint::from_ffi(&*cp);
        match f(&checkpoint) {
            Ok(()) => 0,
            Err(e) => e.code,
        }
    } else {
        0
    }
}

/// `AnchorServer` wraps the NWEP C library's anchor/checkpoint server.
///
/// `AnchorServer` stores and serves Merkle log checkpoints, participates in the
/// BLS multi-party signing protocol, and exposes endpoints that allow verifying
/// clients to obtain the latest or a historical checkpoint.
///
/// # Safety
///
/// The raw pointer `ptr` must remain valid for the lifetime of this value.
/// `AnchorServer` is `Send` because all mutable access is through `&mut self`.
pub struct AnchorServer {
    ptr: *mut ffi::nwep_anchor_server,
    _callbacks: Box<Callbacks>,
}

unsafe impl Send for AnchorServer {}

impl AnchorServer {
    /// `new` creates a new anchor server using the given BLS keypair and anchor set.
    ///
    /// `keypair` is this anchor's signing key, used when [`sign_proposal`] is called.
    /// `anchors` is the set of trusted anchor public keys used to validate incoming
    /// checkpoint signatures.  The server does not take ownership of either; both must
    /// outlive the returned `AnchorServer`.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the underlying C call fails (e.g. allocation failure).
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use nwep::{anchorserver::{AnchorServer, AnchorServerSettings}, bls::BlsKeypair, anchor::AnchorSet};
    /// let keypair = BlsKeypair::generate().unwrap();
    /// let mut anchors = AnchorSet::new(1).unwrap();
    /// let server = AnchorServer::new(&keypair, &mut anchors, AnchorServerSettings::default()).unwrap();
    /// ```
    ///
    /// [`sign_proposal`]: AnchorServer::sign_proposal
    pub fn new(
        keypair: &BlsKeypair,
        anchors: &mut AnchorSet,
        settings: AnchorServerSettings,
    ) -> Result<Self, Error> {
        let has_proposal = settings.on_proposal.is_some();
        let mut cb = Box::new(Callbacks {
            on_proposal: settings.on_proposal,
        });
        let cb_ptr = cb.as_mut() as *mut _ as *mut std::ffi::c_void;

        let ffi_settings = ffi::nwep_anchor_server_settings {
            on_proposal: if has_proposal {
                Some(proposal_cb)
            } else {
                None
            },
            user_data: cb_ptr,
        };

        let mut ptr: *mut ffi::nwep_anchor_server = std::ptr::null_mut();
        check(unsafe {
            ffi::nwep_anchor_server_new(
                &mut ptr,
                keypair.as_ffi(),
                anchors.as_mut_ptr(),
                &ffi_settings,
            )
        })?;
        Ok(AnchorServer {
            ptr,
            _callbacks: cb,
        })
    }

    /// `handle_request` dispatches a single incoming NWEP request to the anchor server.
    ///
    /// Both `stream` and `req` are raw C pointers supplied by the NWEP server callback
    /// infrastructure.  Callers do not normally invoke this method directly — it is
    /// called automatically when the server is registered with `ServerBuilder`.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the C library reports a protocol or I/O error.
    pub fn handle_request(
        &mut self,
        stream: *mut ffi::nwep_stream,
        req: *const ffi::nwep_request,
    ) -> Result<(), Error> {
        check(unsafe { ffi::nwep_anchor_server_handle_request(self.ptr, stream, req) })
    }

    /// `add_checkpoint` stores a finalized, quorum-signed checkpoint in the server.
    ///
    /// The checkpoint must have accumulated enough BLS signatures to satisfy the anchor
    /// threshold before being stored.  Once stored, it becomes available to clients
    /// via the `/checkpoint/latest` and `/checkpoint/<epoch>` endpoints.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the checkpoint is malformed, insufficiently signed, or if the
    /// underlying C call fails.
    pub fn add_checkpoint(&mut self, cp: &Checkpoint) -> Result<(), Error> {
        let ffi_cp = cp.to_ffi();
        check(unsafe { ffi::nwep_anchor_server_add_checkpoint(self.ptr, &ffi_cp) })
    }

    /// `get_latest` returns the most recently stored checkpoint.
    ///
    /// # Errors
    ///
    /// Returns `Err` if no checkpoint has been stored yet or if the underlying C call
    /// fails.
    pub fn get_latest(&self) -> Result<Checkpoint, Error> {
        let mut cp = unsafe { std::mem::zeroed::<ffi::nwep_checkpoint>() };
        check(unsafe { ffi::nwep_anchor_server_get_latest(self.ptr, &mut cp) })?;
        Ok(Checkpoint::from_ffi(&cp))
    }

    pub fn get_checkpoint(&self, epoch: u64) -> Result<Checkpoint, Error> {
        let mut cp = unsafe { std::mem::zeroed::<ffi::nwep_checkpoint>() };
        check(unsafe { ffi::nwep_anchor_server_get_checkpoint(self.ptr, epoch, &mut cp) })?;
        Ok(Checkpoint::from_ffi(&cp))
    }

    pub fn create_proposal(
        &mut self,
        log: &mut MerkleLog,
        epoch: u64,
        timestamp: Tstamp,
    ) -> Result<Checkpoint, Error> {
        let mut cp = unsafe { std::mem::zeroed::<ffi::nwep_checkpoint>() };
        check(unsafe {
            ffi::nwep_anchor_server_create_proposal(
                self.ptr,
                log.as_ptr(),
                epoch,
                timestamp,
                &mut cp,
            )
        })?;
        Ok(Checkpoint::from_ffi(&cp))
    }

    pub fn sign_proposal(&mut self, cp: &mut Checkpoint) -> Result<(), Error> {
        let mut ffi_cp = cp.to_ffi();
        check(unsafe { ffi::nwep_anchor_server_sign_proposal(self.ptr, &mut ffi_cp) })?;
        *cp = Checkpoint::from_ffi(&ffi_cp);
        Ok(())
    }
}

impl Drop for AnchorServer {
    fn drop(&mut self) {
        if !self.ptr.is_null() {
            unsafe { ffi::nwep_anchor_server_free(self.ptr) }
        }
    }
}