1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#![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) }
}
}
}