1mod canonical;
2
3use anyhow::anyhow;
4use lightning_signer::bitcoin::secp256k1::PublicKey;
5use lightning_signer::chain::tracker::ChainTracker;
6use lightning_signer::channel::ChannelId;
7use lightning_signer::channel::ChannelStub;
8use lightning_signer::node::NodeConfig;
9use lightning_signer::node::NodeState;
10use lightning_signer::persist::ChainTrackerListenerEntry;
11use lightning_signer::persist::{Error, Persist, SignerId};
12use lightning_signer::policy::validator::ValidatorFactory;
13use lightning_signer::SendSync;
14use log::{trace, warn};
15use serde::de::{self, SeqAccess, Visitor};
16use serde::ser::SerializeSeq;
17use serde::{Deserialize, Deserializer, Serialize, Serializer};
18use std::collections::BTreeMap;
19use std::fmt;
20use std::str::FromStr;
21use std::sync::Arc;
22use std::sync::Mutex;
23
24use self::canonical::{canonical_json_bytes, CanonicalJsonValue};
25
26const NODE_PREFIX: &str = "nodes";
27const NODE_STATE_PREFIX: &str = "nodestates";
28const CHANNEL_PREFIX: &str = "channels";
29const ALLOWLIST_PREFIX: &str = "allowlists";
30const TRACKER_PREFIX: &str = "trackers";
31const TOMBSTONE_VERSION: u64 = u64::MAX;
32
33#[derive(Clone, Debug, PartialEq)]
34struct StateEntry {
35 version: u64,
36 value: serde_json::Value,
37 signature: Vec<u8>,
38}
39
40impl StateEntry {
41 fn new(version: u64, value: serde_json::Value) -> Self {
42 Self {
43 version,
44 value,
45 signature: vec![],
46 }
47 }
48
49 fn canonical_value_bytes(&self) -> anyhow::Result<Vec<u8>> {
50 canonical_json_bytes(&self.value)
51 }
52}
53
54impl Serialize for StateEntry {
55 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
56 where
57 S: Serializer,
58 {
59 let mut seq = if self.signature.is_empty() {
60 serializer.serialize_seq(Some(2))?
61 } else {
62 serializer.serialize_seq(Some(3))?
63 };
64
65 seq.serialize_element(&self.version)?;
66 seq.serialize_element(&CanonicalJsonValue(&self.value))?;
67 if !self.signature.is_empty() {
68 seq.serialize_element(&self.signature)?;
69 }
70 seq.end()
71 }
72}
73
74impl<'de> Deserialize<'de> for StateEntry {
75 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
76 where
77 D: Deserializer<'de>,
78 {
79 struct StateEntryVisitor;
80
81 impl<'de> Visitor<'de> for StateEntryVisitor {
82 type Value = StateEntry;
83
84 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
85 formatter.write_str("a tuple [version, value] or [version, value, signature]")
86 }
87
88 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
89 where
90 A: SeqAccess<'de>,
91 {
92 let version = seq
93 .next_element()?
94 .ok_or_else(|| de::Error::invalid_length(0, &self))?;
95 let value = seq
96 .next_element()?
97 .ok_or_else(|| de::Error::invalid_length(1, &self))?;
98 let signature = seq.next_element()?.unwrap_or_default();
99
100 if seq.next_element::<de::IgnoredAny>()?.is_some() {
101 return Err(de::Error::invalid_length(4, &self));
102 }
103
104 Ok(StateEntry {
105 version,
106 value,
107 signature,
108 })
109 }
110 }
111
112 deserializer.deserialize_seq(StateEntryVisitor)
113 }
114}
115
116#[derive(Clone, Serialize, Deserialize)]
117pub struct State {
118 values: BTreeMap<String, StateEntry>,
119}
120
121impl State {
122 fn insert_node(
123 &mut self,
124 key: &str,
125 node_entry: vls_persist::model::NodeEntry,
126 node_state_entry: vls_persist::model::NodeStateEntry,
127 ) -> Result<(), Error> {
128 let node_key = format!("{NODE_PREFIX}/{key}");
129 let state_key = format!("{NODE_STATE_PREFIX}/{key}");
130 self.ensure_not_tombstone(&node_key)?;
131 self.ensure_not_tombstone(&state_key)?;
132 let node_version = self.next_version(&node_key);
133 let state_version = self.next_version(&state_key);
134
135 self.values.insert(
136 node_key,
137 StateEntry::new(node_version, serde_json::to_value(node_entry).unwrap()),
138 );
139 self.values.insert(
140 state_key,
141 StateEntry::new(
142 state_version,
143 serde_json::to_value(node_state_entry).unwrap(),
144 ),
145 );
146 Ok(())
147 }
148
149 fn update_node(
150 &mut self,
151 key: &str,
152 node_state: vls_persist::model::NodeStateEntry,
153 ) -> Result<(), Error> {
154 trace!(
155 "Update node: {}",
156 serde_json::to_string(&node_state).unwrap()
157 );
158 let key = format!("{NODE_STATE_PREFIX}/{key}");
159 self.ensure_not_tombstone(&key)?;
160 let v = self
161 .values
162 .get_mut(&key)
163 .expect("attempting to update non-existent node");
164 *v = StateEntry::new(v.version + 1, serde_json::to_value(node_state).unwrap());
165 Ok(())
166 }
167
168 fn delete_node(&mut self, key: &str) -> Result<(), Error> {
169 let node_key = format!("{NODE_PREFIX}/{key}");
170 let state_key = format!("{NODE_STATE_PREFIX}/{key}");
171 let allowlist_key = format!("{ALLOWLIST_PREFIX}/{key}");
172 let tracker_key = format!("{TRACKER_PREFIX}/{key}");
173 let channel_prefix = format!("{CHANNEL_PREFIX}/{key}");
174 let channel_keys: Vec<String> = self
175 .values
176 .keys()
177 .filter(|k| k.starts_with(&channel_prefix))
178 .cloned()
179 .collect();
180
181 self.put_tombstone(&node_key);
182 self.put_tombstone(&state_key);
183 self.put_tombstone(&allowlist_key);
184 self.put_tombstone(&tracker_key);
185 for channel_key in channel_keys {
186 self.put_tombstone(&channel_key);
187 }
188 Ok(())
189 }
190
191 fn insert_channel(
192 &mut self,
193 key: &str,
194 channel_entry: vls_persist::model::ChannelEntry,
195 ) -> Result<(), Error> {
196 let key = format!("{CHANNEL_PREFIX}/{key}");
197 self.ensure_not_tombstone(&key)?;
198 let version = self.next_version(&key);
199 self.values.insert(
200 key,
201 StateEntry::new(version, serde_json::to_value(channel_entry).unwrap()),
202 );
203 Ok(())
204 }
205
206 fn delete_channel(&mut self, key: &str) {
207 let live_key = format!("{CHANNEL_PREFIX}/{key}");
208 self.put_tombstone(&live_key);
209 }
210
211 fn update_channel(
212 &mut self,
213 key: &str,
214 channel_entry: vls_persist::model::ChannelEntry,
215 ) -> Result<(), Error> {
216 trace!("Updating channel {key}");
217 let key = format!("{CHANNEL_PREFIX}/{key}");
218 self.ensure_not_tombstone(&key)?;
219 let v = self
220 .values
221 .get_mut(&key)
222 .expect("attempting to update non-existent channel");
223 *v = StateEntry::new(v.version + 1, serde_json::to_value(channel_entry).unwrap());
224 Ok(())
225 }
226
227 fn get_channel(
228 &self,
229 key: &str,
230 ) -> Result<lightning_signer::persist::model::ChannelEntry, Error> {
231 let key = format!("{CHANNEL_PREFIX}/{key}");
232 if self.is_tombstone(&key) {
233 return Err(Error::Internal(format!("channel {} has been deleted", key)));
234 }
235 let value = self
236 .values
237 .get(&key)
238 .ok_or_else(|| Error::Internal(format!("missing channel state for key {}", key)))?;
239 let entry: vls_persist::model::ChannelEntry =
240 serde_json::from_value(value.value.clone()).unwrap();
241 Ok(entry.into())
242 }
243
244 fn get_node_channels(
245 &self,
246 node_id: &PublicKey,
247 ) -> Result<
248 Vec<(
249 lightning_signer::channel::ChannelId,
250 lightning_signer::persist::model::ChannelEntry,
251 )>,
252 Error,
253 > {
254 let prefix = hex::encode(node_id.serialize());
255 let prefix = format!("{CHANNEL_PREFIX}/{prefix}");
256 Ok(self
257 .values
258 .iter()
259 .filter(|(k, _)| k.starts_with(&prefix))
260 .filter(|(k, _)| !self.is_tombstone(k))
261 .map(|(k, v)| {
262 let key = k.split('/').last().unwrap();
263 let key = vls_persist::model::NodeChannelId(hex::decode(&key).unwrap());
264
265 let value: vls_persist::model::ChannelEntry =
266 serde_json::from_value(v.value.clone()).unwrap();
267 (key.channel_id(), value.into())
268 })
269 .collect())
270 }
271
272 fn new_chain_tracker(
273 &mut self,
274 node_id: &PublicKey,
275 tracker: &ChainTracker<lightning_signer::monitor::ChainMonitor>,
276 ) -> Result<(), Error> {
277 let key = hex::encode(node_id.serialize());
278 let key = format!("{TRACKER_PREFIX}/{key}");
279 self.ensure_not_tombstone(&key)?;
280 let version = self.next_version(&key);
281
282 let tracker: vls_persist::model::ChainTrackerEntry = tracker.into();
283
284 self.values.insert(
285 key,
286 StateEntry::new(version, serde_json::to_value(tracker).unwrap()),
287 );
288 Ok(())
289 }
290
291 pub fn clear(&mut self) -> Result<(), Error> {
292 self.values.clear();
293 Ok(())
294 }
295
296 fn put_tombstone(&mut self, live_key: &str) {
297 self.values.insert(
298 live_key.to_owned(),
299 StateEntry::new(TOMBSTONE_VERSION, serde_json::Value::Null),
300 );
301 }
302
303 fn next_version(&self, live_key: &str) -> u64 {
304 self.values
305 .get(live_key)
306 .map(|v| v.version.saturating_add(1))
307 .unwrap_or(0)
308 }
309
310 fn is_tombstone(&self, live_key: &str) -> bool {
311 self.values
312 .get(live_key)
313 .map(|v| v.version == TOMBSTONE_VERSION)
314 .unwrap_or(false)
315 }
316
317 fn ensure_not_tombstone(&self, key: &str) -> Result<(), Error> {
318 if self.is_tombstone(key) {
319 return Err(Error::Internal(format!("key {} has been deleted", key)));
320 }
321 Ok(())
322 }
323}
324
325#[derive(Debug)]
326pub struct StateChange {
327 key: String,
328 old: Option<StateEntry>,
329 new: StateEntry,
330}
331
332#[derive(Debug, Default)]
333pub struct MergeResult {
334 pub changes: Vec<(String, Option<u64>, u64)>,
335 pub conflict_count: usize,
336}
337
338impl MergeResult {
339 pub fn has_conflicts(&self) -> bool {
340 self.conflict_count > 0
341 }
342}
343
344use core::fmt::Display;
345
346impl Display for StateChange {
347 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
348 match &self.old {
349 Some(o) => f.write_str(&format!(
350 "StateChange[{}]: old_version={}, new_version={}, old_value={}, new_value={}",
351 self.key,
352 o.version,
353 self.new.version,
354 serde_json::to_string(&o.value).unwrap(),
355 serde_json::to_string(&self.new.value).unwrap()
356 )),
357 None => f.write_str(&format!(
358 "StateChange[{}]: old_version=null, new_version={}, old_value=null, new_value={}",
359 self.key,
360 self.new.version,
361 serde_json::to_string(&self.new.value).unwrap()
362 )),
363 }
364 }
365}
366
367impl State {
368 pub fn new() -> Self {
369 State {
370 values: BTreeMap::new(),
371 }
372 }
373
374 pub fn len(&self) -> usize {
375 self.values.len()
376 }
377
378 pub fn merge(&mut self, other: &State) -> anyhow::Result<MergeResult> {
383 let mut res = MergeResult::default();
384 for (key, incoming) in other.values.iter() {
385 let newver = incoming.version;
386 let incoming_is_tombstone = newver == TOMBSTONE_VERSION;
387 match self.values.get_mut(key) {
388 None => {
389 trace!("Insert new key {}: version={}", key, newver);
390 res.changes.push((key.to_owned(), None, newver));
391 let mut inserted = incoming.clone();
392 if incoming_is_tombstone {
393 inserted.value = serde_json::Value::Null;
394 }
395 self.values.insert(key.clone(), inserted);
396 }
397 Some(v) => {
398 if incoming_is_tombstone {
399 if v.version == TOMBSTONE_VERSION {
400 continue;
401 }
402 trace!(
403 "Tombstoning key {}: version={} => version={}",
404 key,
405 v.version,
406 newver
407 );
408 res.changes.push((key.to_owned(), Some(v.version), newver));
409 *v = StateEntry {
410 version: newver,
411 value: serde_json::Value::Null,
412 signature: incoming.signature.clone(),
413 };
414 continue;
415 }
416
417 if v.version == TOMBSTONE_VERSION {
418 trace!(
419 "Ignoring live key {} version={} because local key is tombstoned",
420 key,
421 newver
422 );
423 res.conflict_count += 1;
424 continue;
425 }
426
427 if v.version == newver {
428 if v.value == incoming.value && v.signature != incoming.signature {
429 trace!("Updating signature for key {} at version={}", key, newver);
430 res.changes.push((key.to_owned(), Some(v.version), newver));
431 v.signature = incoming.signature.clone();
432 }
433 continue;
434 } else if v.version > newver {
435 warn!(
436 "Ignoring outdated state version newver={}, we have oldver={}: newval={:?} vs oldval={:?}",
437 newver,
438 v.version,
439 serde_json::to_string(&incoming.value),
440 serde_json::to_string(&v.value)
441 );
442 res.conflict_count += 1;
443 continue;
444 } else {
445 trace!(
446 "Updating key {}: version={} => version={}",
447 key,
448 v.version,
449 newver
450 );
451 res.changes.push((key.to_owned(), Some(v.version), newver));
452 *v = incoming.clone();
453 }
454 }
455 }
456 }
457 Ok(res)
458 }
459
460 pub fn diff(&self, other: &State) -> anyhow::Result<Vec<StateChange>> {
461 Ok(other
462 .values
463 .iter()
464 .map(|(key, new)| (key, self.values.get(key), new))
465 .map(|(key, old, new)| StateChange {
466 key: key.clone(),
467 old: old.map(|o| o.clone()),
468 new: new.clone(),
469 })
470 .filter(|c| match (&c.old, &c.new) {
471 (None, _) => true,
472 (Some(old), new) => old.version < new.version,
473 })
474 .collect())
475 }
476
477 pub fn diff_state(&self, other: &State) -> State {
480 let mut values = BTreeMap::new();
481 for (key, new_entry) in other.values.iter() {
482 match self.values.get(key) {
483 None => {
484 values.insert(key.clone(), new_entry.clone());
485 }
486 Some(old_entry) if old_entry.version < new_entry.version => {
487 values.insert(key.clone(), new_entry.clone());
488 }
489 Some(old_entry)
490 if old_entry.version == new_entry.version
491 && old_entry.signature != new_entry.signature =>
492 {
493 values.insert(key.clone(), new_entry.clone());
494 }
495 _ => {}
496 }
497 }
498 State { values }
499 }
500
501 pub fn resign_signatures<F>(&mut self, mut signer: F) -> anyhow::Result<usize>
503 where
504 F: FnMut(&str, u64, &[u8]) -> anyhow::Result<Vec<u8>>,
505 {
506 let mut changed = 0usize;
507 for (key, entry) in self.values.iter_mut() {
508 if !entry.signature.is_empty() {
509 continue;
510 }
511 let value = entry.canonical_value_bytes()?;
512 let signature = signer(key, entry.version, &value)?;
513 entry.signature = signature;
514 changed += 1;
515 }
516 Ok(changed)
517 }
518
519 pub fn sketch(&self) -> StateSketch {
520 StateSketch::from_state(self)
521 }
522
523 pub fn omit_tombstones(&self) -> State {
525 let values = self
526 .values
527 .iter()
528 .filter(|(_, value)| value.version != TOMBSTONE_VERSION)
529 .map(|(key, value)| (key.clone(), value.clone()))
530 .collect();
531 State { values }
532 }
533}
534
535#[derive(Clone, Serialize, Deserialize, Debug, Default)]
536struct SketchEntry {
537 version: u64,
538 signature: Vec<u8>,
539}
540
541#[derive(Clone, Serialize, Deserialize, Debug, Default)]
542pub struct StateSketch {
543 versions: BTreeMap<String, SketchEntry>,
544}
545
546impl StateSketch {
547 pub fn new() -> Self {
548 Self::default()
549 }
550
551 pub fn from_state(state: &State) -> Self {
552 let mut sketch = Self::new();
553 sketch.apply_state(state);
554 sketch
555 }
556
557 pub fn apply_state(&mut self, state: &State) {
559 for (key, value) in state.values.iter() {
560 self.versions.insert(
561 key.clone(),
562 SketchEntry {
563 version: value.version,
564 signature: value.signature.clone(),
565 },
566 );
567 }
568 }
569
570 pub fn diff_state(&self, state: &State) -> State {
572 let mut values = BTreeMap::new();
573 for (key, new_entry) in state.values.iter() {
574 match self.versions.get(key) {
575 None => {
576 values.insert(key.clone(), new_entry.clone());
577 }
578 Some(old) if old.version < new_entry.version => {
579 values.insert(key.clone(), new_entry.clone());
580 }
581 Some(old)
582 if old.version == new_entry.version && old.signature != new_entry.signature =>
583 {
584 values.insert(key.clone(), new_entry.clone());
585 }
586 _ => {}
587 }
588 }
589 State { values }
590 }
591}
592
593impl Into<Vec<crate::pb::SignerStateEntry>> for State {
594 fn into(self) -> Vec<crate::pb::SignerStateEntry> {
595 self.values
596 .iter()
597 .map(|(k, v)| crate::pb::SignerStateEntry {
598 key: k.to_owned(),
599 value: v
600 .canonical_value_bytes()
601 .expect("canonical signer state value"),
602 version: v.version,
603 signature: v.signature.clone(),
604 })
605 .collect()
606 }
607}
608
609impl TryFrom<&[crate::pb::SignerStateEntry]> for State {
610 type Error = anyhow::Error;
611
612 fn try_from(v: &[crate::pb::SignerStateEntry]) -> Result<State, Self::Error> {
613 let values = v
614 .iter()
615 .map(|entry| -> anyhow::Result<(String, StateEntry)> {
616 let value = serde_json::from_slice(&entry.value).map_err(|e| {
617 anyhow!(
618 "failed to decode signer state value for key {}: {}",
619 entry.key,
620 e
621 )
622 })?;
623
624 Ok((
625 entry.key.to_owned(),
626 StateEntry {
627 version: entry.version,
628 value,
629 signature: entry.signature.clone(),
630 },
631 ))
632 })
633 .collect::<anyhow::Result<BTreeMap<_, _>>>()?;
634
635 Ok(State { values })
636 }
637}
638
639impl From<Vec<crate::pb::SignerStateEntry>> for State {
640 fn from(v: Vec<crate::pb::SignerStateEntry>) -> State {
641 State::try_from(v.as_slice())
642 .expect("signer state entries must contain valid JSON payloads")
643 }
644}
645
646pub(crate) struct MemoryPersister {
647 state: Arc<Mutex<State>>,
648}
649
650impl MemoryPersister {
651 pub fn new() -> Self {
652 let state = Arc::new(Mutex::new(State {
653 values: BTreeMap::new(),
654 }));
655 MemoryPersister { state }
656 }
657
658 pub fn state(&self) -> Arc<Mutex<State>> {
659 self.state.clone()
660 }
661}
662
663impl SendSync for MemoryPersister {}
664
665impl Persist for MemoryPersister {
666 fn new_node(
667 &self,
668 node_id: &lightning_signer::bitcoin::secp256k1::PublicKey,
669 config: &NodeConfig,
670 state: &NodeState,
671 ) -> Result<(), Error> {
672 let key = hex::encode(node_id.serialize());
673 self.state.lock().unwrap().insert_node(
674 &key,
675 vls_persist::model::NodeEntry {
676 key_derivation_style: config.key_derivation_style as u8,
677 network: config.network.to_string(),
678 },
679 state.into(),
680 )
681 }
682
683 fn delete_channel(&self, node_id: &PublicKey, channel: &ChannelId) -> Result<(), Error> {
684 let node_channel_id = vls_persist::model::NodeChannelId::new(node_id, &channel);
685 let id = hex::encode(node_channel_id.0);
686 self.state.lock().unwrap().delete_channel(&id);
687 Ok(())
688 }
689
690 fn update_node(
691 &self,
692 node_id: &lightning_signer::bitcoin::secp256k1::PublicKey,
693 state: &NodeState,
694 ) -> Result<(), Error> {
695 let key = hex::encode(node_id.serialize());
696 self.state.lock().unwrap().update_node(&key, state.into())
697 }
698
699 fn delete_node(
700 &self,
701 node_id: &lightning_signer::bitcoin::secp256k1::PublicKey,
702 ) -> Result<(), Error> {
703 let key = hex::encode(node_id.serialize());
704 self.state.lock().unwrap().delete_node(&key)
705 }
706
707 fn new_channel(
708 &self,
709 node_id: &lightning_signer::bitcoin::secp256k1::PublicKey,
710 stub: &ChannelStub,
711 ) -> Result<(), Error> {
712 let id = vls_persist::model::NodeChannelId::new(node_id, &stub.id0);
713 let channel_value_satoshis = 0;
714 let enforcement_state = lightning_signer::policy::validator::EnforcementState::new(0);
715 let entry = vls_persist::model::ChannelEntry {
716 channel_value_satoshis,
717 channel_setup: None,
718 id: None,
719 enforcement_state,
720 blockheight: Some(stub.blockheight),
722 };
723 let id = hex::encode(id.0);
724
725 self.state.lock().unwrap().insert_channel(&id, entry)
726 }
727
728 fn update_channel(
729 &self,
730 node_id: &lightning_signer::bitcoin::secp256k1::PublicKey,
731 channel: &lightning_signer::channel::Channel,
732 ) -> Result<(), Error> {
733 let node_channel_id = vls_persist::model::NodeChannelId::new(node_id, &channel.id0);
734 let id = hex::encode(node_channel_id.0);
735 let channel_value_satoshis = channel.setup.channel_value_sat;
736 let entry = vls_persist::model::ChannelEntry {
737 channel_value_satoshis,
738 channel_setup: Some(channel.setup.clone()),
739 id: channel.id.clone(),
740 enforcement_state: channel.enforcement_state.clone(),
741 blockheight: None,
742 };
743 self.state.lock().unwrap().update_channel(&id, entry)
744 }
745
746 fn get_channel(
747 &self,
748 node_id: &PublicKey,
749 channel_id: &ChannelId,
750 ) -> Result<lightning_signer::persist::model::ChannelEntry, Error> {
751 let id = vls_persist::model::NodeChannelId::new(node_id, channel_id);
752 let id = hex::encode(id.0);
753 self.state.lock().unwrap().get_channel(&id)
754 }
755
756 fn new_tracker(
757 &self,
758 node_id: &PublicKey,
759 tracker: &ChainTracker<lightning_signer::monitor::ChainMonitor>,
760 ) -> Result<(), Error> {
761 self.state
762 .lock()
763 .unwrap()
764 .new_chain_tracker(node_id, tracker)
765 }
766
767 fn update_tracker(
768 &self,
769 node_id: &PublicKey,
770 tracker: &ChainTracker<lightning_signer::monitor::ChainMonitor>,
771 ) -> Result<(), Error> {
772 let key = hex::encode(node_id.serialize());
773 let key = format!("{TRACKER_PREFIX}/{key}");
774
775 let mut state = self.state.lock().unwrap();
776 state.ensure_not_tombstone(&key)?;
777 let v = state.values.get_mut(&key).unwrap();
778 let tracker: vls_persist::model::ChainTrackerEntry = tracker.into();
779 *v = StateEntry::new(v.version + 1, serde_json::to_value(tracker).unwrap());
780 Ok(())
781 }
782
783 fn get_tracker(
784 &self,
785 node_id: PublicKey,
786 validator_factory: Arc<dyn ValidatorFactory>,
787 ) -> Result<
788 (
789 ChainTracker<lightning_signer::monitor::ChainMonitor>,
790 Vec<ChainTrackerListenerEntry>,
791 ),
792 Error,
793 > {
794 let key = hex::encode(node_id.serialize());
795 let key = format!("{TRACKER_PREFIX}/{key}");
796
797 let state = self.state.lock().unwrap();
798 if state.is_tombstone(&key) {
799 return Err(Error::Internal(format!("tracker {} has been deleted", key)));
800 }
801 let v: vls_persist::model::ChainTrackerEntry = serde_json::from_value(
802 state
803 .values
804 .get(&key)
805 .ok_or_else(|| Error::Internal(format!("missing tracker state {}", key)))?
806 .value
807 .clone(),
808 )
809 .unwrap();
810
811 Ok(v.into_tracker(node_id, validator_factory))
812 }
813
814 fn get_node_channels(
815 &self,
816 node_id: &PublicKey,
817 ) -> Result<Vec<(ChannelId, lightning_signer::persist::model::ChannelEntry)>, Error> {
818 self.state.lock().unwrap().get_node_channels(node_id)
819 }
820
821 fn update_node_allowlist(
822 &self,
823 node_id: &PublicKey,
824 allowlist: Vec<std::string::String>,
825 ) -> Result<(), Error> {
826 let key = hex::encode(node_id.serialize());
827 let key = format!("{ALLOWLIST_PREFIX}/{key}");
828
829 let mut state = self.state.lock().unwrap();
830 state.ensure_not_tombstone(&key)?;
831 match state.values.get_mut(&key) {
832 Some(v) => {
833 *v = StateEntry::new(v.version + 1, serde_json::to_value(allowlist).unwrap());
834 }
835 None => {
836 let version = state.next_version(&key);
837 state.values.insert(
838 key,
839 StateEntry::new(version, serde_json::to_value(allowlist).unwrap()),
840 );
841 }
842 }
843 Ok(())
844 }
845
846 fn get_node_allowlist(&self, node_id: &PublicKey) -> Result<Vec<std::string::String>, Error> {
847 let state = self.state.lock().unwrap();
848 let key = hex::encode(node_id.serialize());
849 let key = format!("{ALLOWLIST_PREFIX}/{key}");
850 if state.is_tombstone(&key) {
851 return Ok(Vec::new());
852 }
853
854 let allowlist: Vec<String> = match state.values.get(&key) {
856 Some(value) => serde_json::from_value(value.value.clone()).unwrap_or_default(),
857 None => Vec::new(),
858 };
859
860 Ok(allowlist)
861 }
862
863 fn get_nodes(
864 &self,
865 ) -> Result<Vec<(PublicKey, lightning_signer::persist::model::NodeEntry)>, Error> {
866 use lightning_signer::node::NodeState as CoreNodeState;
867
868 let state = self.state.lock().unwrap();
869 let node_ids: Vec<&str> = state
870 .values
871 .keys()
872 .filter(|k| k.starts_with(&format!("{NODE_PREFIX}/")))
873 .filter(|k| !state.is_tombstone(k))
874 .filter_map(|k| k.split('/').last())
875 .collect();
876
877 let mut res = Vec::new();
878 for node_id in node_ids.iter() {
879 let node_key = format!("{NODE_PREFIX}/{node_id}");
880 let state_key = format!("{NODE_STATE_PREFIX}/{node_id}");
881 let allowlist_key = format!("{ALLOWLIST_PREFIX}/{node_id}");
882
883 if state.is_tombstone(&node_key) || state.is_tombstone(&state_key) {
884 continue;
885 }
886
887 let node: vls_persist::model::NodeEntry = match state.values.get(&node_key) {
888 Some(value) => serde_json::from_value(value.value.clone()).unwrap(),
889 None => continue,
890 };
891 let state_e: vls_persist::model::NodeStateEntry = match state.values.get(&state_key) {
892 Some(value) => serde_json::from_value(value.value.clone()).unwrap(),
893 None => continue,
894 };
895
896 let allowlist_strings: Vec<String> = if state.is_tombstone(&allowlist_key) {
898 Vec::new()
899 } else {
900 match state.values.get(&allowlist_key) {
901 Some(value) => serde_json::from_value(value.value.clone()).unwrap_or_default(),
902 None => Vec::new(),
903 }
904 };
905
906 use lightning_signer::node::Allowable;
908 let network = lightning_signer::bitcoin::Network::from_str(&node.network)
909 .map_err(|e| Error::Internal(format!("Invalid network: {}", e)))?;
910
911 let allowlist: Vec<Allowable> = allowlist_strings
912 .into_iter()
913 .filter_map(|s| match Allowable::from_str(&s, network) {
914 Ok(a) => Some(a),
915 Err(e) => {
916 warn!("Failed to parse allowlist entry '{}': {}", s, e);
917 None
918 }
919 })
920 .collect();
921
922 let state = CoreNodeState::restore(
923 state_e.invoices,
924 state_e.issued_invoices,
925 state_e.preimages,
926 0,
927 state_e.velocity_control.into(),
928 state_e.fee_velocity_control.into(),
929 0u64,
930 allowlist,
933 );
934
935 let entry = lightning_signer::persist::model::NodeEntry {
936 key_derivation_style: node.key_derivation_style,
937 network: node.network,
938 state,
939 };
940
941 let key: Vec<u8> = hex::decode(node_id).unwrap();
942 res.push((PublicKey::from_slice(key.as_slice()).unwrap(), entry));
943 }
944
945 let nodes = res;
946 Ok(nodes)
947 }
948 fn clear_database(&self) -> Result<(), Error> {
949 self.state.lock().unwrap().clear()
950 }
951
952 fn signer_id(&self) -> SignerId {
953 [0u8; 16]
957 }
958}
959
960#[cfg(test)]
961mod tests {
962 use crate::persist::TOMBSTONE_VERSION;
963
964 use super::{
965 State, StateEntry, StateSketch, ALLOWLIST_PREFIX, CHANNEL_PREFIX, NODE_PREFIX,
966 NODE_STATE_PREFIX, TRACKER_PREFIX,
967 };
968 use crate::pb::SignerStateEntry;
969 use serde_json::json;
970 use std::collections::BTreeMap;
971
972 fn mk_state(entries: Vec<(&str, u64, serde_json::Value)>) -> State {
973 let mut values = BTreeMap::new();
974 for (key, version, value) in entries {
975 values.insert(key.to_string(), StateEntry::new(version, value));
976 }
977 State { values }
978 }
979
980 fn assert_entry(
981 state: &State,
982 key: &str,
983 expected_version: u64,
984 expected_value: serde_json::Value,
985 ) {
986 let actual = state
987 .values
988 .get(key)
989 .unwrap_or_else(|| panic!("expected state to include key: {key}"));
990 assert_eq!(actual.version, expected_version);
991 assert_eq!(actual.value, expected_value);
992 }
993
994 fn assert_entry_absent(state: &State, key: &str) {
995 assert!(
996 state.values.get(key).is_none(),
997 "expected state to omit key {key}"
998 );
999 }
1000
1001 fn assert_tombstone(state: &State, key: &str) {
1002 assert_entry(state, key, TOMBSTONE_VERSION, serde_json::Value::Null);
1003 }
1004
1005 #[test]
1006 fn state_deserialize_legacy_tuple_defaults_empty_signature() {
1007 let raw = r#"{"values":{"k":[1,{"v":1}]}}"#;
1008 let state: State = serde_json::from_str(raw).unwrap();
1009 let entry = state.values.get("k").unwrap();
1010 assert_eq!(entry.version, 1);
1011 assert_eq!(entry.value, json!({"v": 1}));
1012 assert!(entry.signature.is_empty());
1013 }
1014
1015 #[test]
1016 fn state_deserialize_extended_tuple_preserves_signature() {
1017 let raw = r#"{"values":{"k":[2,{"v":2},[1,2,3]]}}"#;
1018 let state: State = serde_json::from_str(raw).unwrap();
1019 let entry = state.values.get("k").unwrap();
1020 assert_eq!(entry.version, 2);
1021 assert_eq!(entry.value, json!({"v": 2}));
1022 assert_eq!(entry.signature, vec![1, 2, 3]);
1023 }
1024
1025 #[test]
1026 fn state_serialize_empty_signature_emits_legacy_tuple() {
1027 let state = mk_state(vec![("k", 3, json!({"v": 3}))]);
1028 let v: serde_json::Value =
1029 serde_json::from_slice(&serde_json::to_vec(&state).unwrap()).unwrap();
1030 let values = v.get("values").unwrap().as_object().unwrap();
1031 let tuple = values.get("k").unwrap().as_array().unwrap();
1032 assert_eq!(tuple.len(), 2);
1033 assert_eq!(tuple[0], json!(3));
1034 assert_eq!(tuple[1], json!({"v": 3}));
1035 }
1036
1037 #[test]
1038 fn state_serialize_non_empty_signature_emits_extended_tuple() {
1039 let mut values = BTreeMap::new();
1040 values.insert(
1041 "k".to_string(),
1042 StateEntry {
1043 version: 4,
1044 value: json!({"v": 4}),
1045 signature: vec![7, 8, 9],
1046 },
1047 );
1048 let state = State { values };
1049 let v: serde_json::Value =
1050 serde_json::from_slice(&serde_json::to_vec(&state).unwrap()).unwrap();
1051 let values = v.get("values").unwrap().as_object().unwrap();
1052 let tuple = values.get("k").unwrap().as_array().unwrap();
1053 assert_eq!(tuple.len(), 3);
1054 assert_eq!(tuple[0], json!(4));
1055 assert_eq!(tuple[1], json!({"v": 4}));
1056 assert_eq!(tuple[2], json!([7, 8, 9]));
1057 }
1058
1059 #[test]
1060 fn state_entry_canonical_value_bytes_sorts_nested_object_keys() {
1061 let entry = StateEntry::new(0, json!({
1062 "z": {"b": 1, "a": 2},
1063 "a": [{"d": 4, "c": 3}]
1064 }));
1065
1066 let bytes = entry.canonical_value_bytes().unwrap();
1067
1068 assert_eq!(bytes, br#"{"a":[{"c":3,"d":4}],"z":{"a":2,"b":1}}"#);
1069 }
1070
1071 #[test]
1072 fn signer_state_entry_conversions_preserve_signature() {
1073 let entries = vec![SignerStateEntry {
1074 version: 5,
1075 key: "k".to_string(),
1076 value: serde_json::to_vec(&json!({"v": 5})).unwrap(),
1077 signature: vec![11, 12],
1078 }];
1079
1080 let state: State = entries.clone().into();
1081 let entry = state.values.get("k").unwrap();
1082 assert_eq!(entry.version, 5);
1083 assert_eq!(entry.value, json!({"v": 5}));
1084 assert_eq!(entry.signature, vec![11, 12]);
1085
1086 let roundtrip: Vec<SignerStateEntry> = state.into();
1087 assert_eq!(roundtrip, entries);
1088 }
1089
1090 #[test]
1091 fn signer_state_entry_conversions_emit_canonical_value_bytes() {
1092 let entries = vec![SignerStateEntry {
1093 version: 6,
1094 key: "k".to_string(),
1095 value: br#"{ "b": 1, "a": 2 }"#.to_vec(),
1096 signature: vec![11, 12],
1097 }];
1098
1099 let state: State = entries.into();
1100 let roundtrip: Vec<SignerStateEntry> = state.into();
1101 assert_eq!(roundtrip.len(), 1);
1102 assert_eq!(roundtrip[0].value, br#"{"a":2,"b":1}"#);
1103 assert_eq!(roundtrip[0].signature, vec![11, 12]);
1104 }
1105
1106 #[test]
1107 fn merge_newer_entry_propagates_signature() {
1108 let mut base = mk_state(vec![("k", 1, json!({"v": 1}))]);
1109 let mut incoming_values = BTreeMap::new();
1110 incoming_values.insert(
1111 "k".to_string(),
1112 StateEntry {
1113 version: 2,
1114 value: json!({"v": 2}),
1115 signature: vec![21, 22, 23],
1116 },
1117 );
1118 let incoming = State {
1119 values: incoming_values,
1120 };
1121
1122 let res = base.merge(&incoming).unwrap();
1123 assert_eq!(res.conflict_count, 0);
1124 let merged = base.values.get("k").unwrap();
1125 assert_eq!(merged.version, 2);
1126 assert_eq!(merged.value, json!({"v": 2}));
1127 assert_eq!(merged.signature, vec![21, 22, 23]);
1128 }
1129
1130 #[test]
1131 fn merge_same_version_updates_signature_when_value_matches() {
1132 let mut base_values = BTreeMap::new();
1133 base_values.insert(
1134 "k".to_string(),
1135 StateEntry {
1136 version: 2,
1137 value: json!({"v": 1}),
1138 signature: vec![1],
1139 },
1140 );
1141 let mut base = State {
1142 values: base_values,
1143 };
1144
1145 let mut incoming_values = BTreeMap::new();
1146 incoming_values.insert(
1147 "k".to_string(),
1148 StateEntry {
1149 version: 2,
1150 value: json!({"v": 1}),
1151 signature: vec![9, 9],
1152 },
1153 );
1154 let incoming = State {
1155 values: incoming_values,
1156 };
1157
1158 let res = base.merge(&incoming).unwrap();
1159 assert_eq!(res.conflict_count, 0);
1160 let merged = base.values.get("k").unwrap();
1161 assert_eq!(merged.version, 2);
1162 assert_eq!(merged.value, json!({"v": 1}));
1163 assert_eq!(merged.signature, vec![9, 9]);
1164 }
1165
1166 #[test]
1167 fn merge_same_version_does_not_overwrite_value() {
1168 let mut base_values = BTreeMap::new();
1169 base_values.insert(
1170 "k".to_string(),
1171 StateEntry {
1172 version: 2,
1173 value: json!({"v": 1}),
1174 signature: vec![1],
1175 },
1176 );
1177 let mut base = State {
1178 values: base_values,
1179 };
1180
1181 let mut incoming_values = BTreeMap::new();
1182 incoming_values.insert(
1183 "k".to_string(),
1184 StateEntry {
1185 version: 2,
1186 value: json!({"v": 999}),
1187 signature: vec![9, 9],
1188 },
1189 );
1190 let incoming = State {
1191 values: incoming_values,
1192 };
1193
1194 let _ = base.merge(&incoming).unwrap();
1195 let merged = base.values.get("k").unwrap();
1196 assert_eq!(merged.version, 2);
1197 assert_eq!(merged.value, json!({"v": 1}));
1198 assert_eq!(merged.signature, vec![1]);
1199 }
1200
1201 #[test]
1202 fn omit_tombstones_omits_tombstoned_entries() {
1203 let state = mk_state(vec![
1204 ("k1", 1, json!({"v": 1})),
1205 ("k2", TOMBSTONE_VERSION, serde_json::Value::Null),
1206 ]);
1207
1208 let filtered = state.omit_tombstones();
1209
1210 assert_eq!(filtered.values.len(), 1);
1211 assert_entry(&filtered, "k1", 1, json!({"v": 1}));
1212 assert_entry_absent(&filtered, "k2");
1213 }
1214
1215 #[test]
1216 fn diff_state_only_includes_new_or_newer_entries() {
1217 let old = mk_state(vec![
1218 ("k1", 1, json!({"v": 1})),
1219 ("k2", 2, json!({"v": 2})),
1220 ("k3", 3, json!({"v": 3})),
1221 ]);
1222 let new = mk_state(vec![
1223 ("k1", 1, json!({"v": 999})),
1225 ("k2", 3, json!({"v": 22})),
1227 ("k3", 2, json!({"v": 33})),
1229 ("k4", 0, json!({"v": 4})),
1231 ]);
1232
1233 let diff = old.diff_state(&new);
1234
1235 assert_eq!(diff.values.len(), 2);
1236 assert_entry(&diff, "k2", 3, json!({"v": 22}));
1237 assert_entry(&diff, "k4", 0, json!({"v": 4}));
1238 assert_entry_absent(&diff, "k1");
1239 assert_entry_absent(&diff, "k3");
1240 }
1241
1242 #[test]
1243 fn diff_state_includes_signature_only_changes() {
1244 let mut old_values = BTreeMap::new();
1245 old_values.insert(
1246 "k".to_string(),
1247 StateEntry {
1248 version: 5,
1249 value: json!({"v": 5}),
1250 signature: vec![1],
1251 },
1252 );
1253 let old = State { values: old_values };
1254
1255 let mut new_values = BTreeMap::new();
1256 new_values.insert(
1257 "k".to_string(),
1258 StateEntry {
1259 version: 5,
1260 value: json!({"v": 5}),
1261 signature: vec![2, 3],
1262 },
1263 );
1264 let new = State { values: new_values };
1265
1266 let diff = old.diff_state(&new);
1267 assert_eq!(diff.values.len(), 1);
1268 let entry = diff.values.get("k").unwrap();
1269 assert_eq!(entry.version, 5);
1270 assert_eq!(entry.signature, vec![2, 3]);
1271 }
1272
1273 #[test]
1274 fn sate_diff_with_empty_old_state_includes_all_entries() {
1275 let old = State::new();
1276 let new = mk_state(vec![("k1", 1, json!({"v": 1})), ("k2", 2, json!({"v": 2}))]);
1277
1278 let diff = old.diff_state(&new);
1279
1280 assert_eq!(diff.values.len(), 2);
1281 assert_entry(&diff, "k1", 1, json!({"v": 1}));
1282 assert_entry(&diff, "k2", 2, json!({"v": 2}));
1283 }
1284
1285 #[test]
1286 fn sketch_diff_matches_state_diff() {
1287 let old = mk_state(vec![
1288 ("a", 5, json!(1)),
1289 ("b", 2, json!(2)),
1290 ("c", 7, json!(3)),
1291 ]);
1292 let new = mk_state(vec![
1293 ("a", 5, json!(10)),
1294 ("b", 3, json!(20)),
1295 ("c", 6, json!(30)),
1296 ("d", 1, json!(40)),
1297 ]);
1298
1299 let state_diff = old.diff_state(&new);
1300 let sketch_diff = old.sketch().diff_state(&new);
1301
1302 assert_eq!(state_diff.values, sketch_diff.values);
1303 }
1304
1305 #[test]
1306 fn sketch_diff_with_empty_sketch_includes_all_entries() {
1307 let state = mk_state(vec![("a", 1, json!(1)), ("b", 2, json!(2))]);
1308 let sketch = StateSketch::new();
1309
1310 let diff = sketch.diff_state(&state);
1311
1312 assert_eq!(diff.values.len(), 2);
1313 assert_entry(&diff, "a", 1, json!(1));
1314 assert_entry(&diff, "b", 2, json!(2));
1315 }
1316
1317 #[test]
1318 fn sketch_apply_follow_version_updates() {
1319 let base = mk_state(vec![("a", 1, json!(1)), ("b", 2, json!(2))]);
1320 let mut sketch = StateSketch::new();
1321 sketch.apply_state(&base);
1322
1323 let next = mk_state(vec![
1324 ("a", 2, json!(10)),
1325 ("b", 2, json!(20)),
1326 ("c", 0, json!(30)),
1327 ]);
1328 let first_diff = sketch.diff_state(&next);
1329 assert_eq!(first_diff.values.len(), 2);
1330 assert_entry(&first_diff, "a", 2, json!(10));
1331 assert_entry(&first_diff, "c", 0, json!(30));
1332 assert_entry_absent(&first_diff, "b");
1333
1334 sketch.apply_state(&first_diff);
1335 let second_diff = sketch.diff_state(&next);
1336 assert!(second_diff.values.is_empty());
1337 }
1338
1339 #[test]
1340 fn sketch_diff_includes_signature_only_changes() {
1341 let mut old_values = BTreeMap::new();
1342 old_values.insert(
1343 "k".to_string(),
1344 StateEntry {
1345 version: 8,
1346 value: json!({"v": 8}),
1347 signature: vec![1],
1348 },
1349 );
1350 let old = State { values: old_values };
1351
1352 let mut new_values = BTreeMap::new();
1353 new_values.insert(
1354 "k".to_string(),
1355 StateEntry {
1356 version: 8,
1357 value: json!({"v": 8}),
1358 signature: vec![4, 5, 6],
1359 },
1360 );
1361 let new = State { values: new_values };
1362
1363 let diff = old.sketch().diff_state(&new);
1364 assert_eq!(diff.values.len(), 1);
1365 let entry = diff.values.get("k").unwrap();
1366 assert_eq!(entry.version, 8);
1367 assert_eq!(entry.signature, vec![4, 5, 6]);
1368 }
1369
1370 #[test]
1371 fn merge_tombstone_deletes_older_live_entry() {
1372 let live_key = format!("{CHANNEL_PREFIX}/abc");
1373 let mut state = mk_state(vec![(live_key.as_str(), 2, json!({"v": 1}))]);
1374 let incoming = mk_state(vec![(live_key.as_str(), u64::MAX, serde_json::Value::Null)]);
1375
1376 let res = state.merge(&incoming).unwrap();
1377
1378 assert_tombstone(&state, &live_key);
1379 assert_eq!(res.conflict_count, 0);
1380 }
1381
1382 #[test]
1383 fn merge_ignores_live_entry_if_key_is_tombstone() {
1384 let live_key = format!("{CHANNEL_PREFIX}/abc");
1385 let mut state = mk_state(vec![(live_key.as_str(), u64::MAX, serde_json::Value::Null)]);
1386 let incoming = mk_state(vec![(live_key.as_str(), 4, json!({"v": 1}))]);
1387
1388 let res = state.merge(&incoming).unwrap();
1389
1390 assert_tombstone(&state, &live_key);
1391 assert_eq!(res.conflict_count, 1);
1392 }
1393
1394 #[test]
1395 fn merge_ignores_newer_live_entry_if_key_is_tombstone() {
1396 let live_key = format!("{CHANNEL_PREFIX}/abc");
1397 let mut state = mk_state(vec![(live_key.as_str(), u64::MAX, serde_json::Value::Null)]);
1398 let incoming = mk_state(vec![(live_key.as_str(), 6, json!({"v": 2}))]);
1399
1400 let res = state.merge(&incoming).unwrap();
1401
1402 assert_tombstone(&state, &live_key);
1403 assert_eq!(res.conflict_count, 1);
1404 }
1405
1406 #[test]
1407 fn safe_merge_reports_conflict_for_outdated_live_version() {
1408 let mut state = mk_state(vec![("k1", 5, json!({"v": 5}))]);
1409 let incoming = mk_state(vec![("k1", 4, json!({"v": 4}))]);
1410
1411 let res = state.merge(&incoming).unwrap();
1412
1413 assert_eq!(res.conflict_count, 1);
1414 assert_entry(&state, "k1", 5, json!({"v": 5}));
1415 }
1416
1417 #[test]
1418 fn signer_state_entry_try_from_rejects_invalid_json_value() {
1419 let entries = vec![SignerStateEntry {
1420 version: 1,
1421 key: "bad".to_string(),
1422 value: b"not-json".to_vec(),
1423 signature: vec![],
1424 }];
1425
1426 let res = State::try_from(entries.as_slice());
1427 assert!(res.is_err());
1428 }
1429
1430 #[test]
1431 fn resign_signatures_only_signs_missing_entries() {
1432 let mut values = BTreeMap::new();
1433 values.insert(
1434 "signed".to_string(),
1435 StateEntry {
1436 version: 1,
1437 value: json!({"v": 1}),
1438 signature: vec![9, 9],
1439 },
1440 );
1441 values.insert(
1442 "unsigned".to_string(),
1443 StateEntry {
1444 version: 2,
1445 value: json!({"v": 2}),
1446 signature: vec![],
1447 },
1448 );
1449
1450 let mut state = State { values };
1451 let mut calls = 0usize;
1452 let changed = state
1453 .resign_signatures(|_, _, _| {
1454 calls += 1;
1455 Ok(vec![1, 2, 3, 4])
1456 })
1457 .unwrap();
1458
1459 assert_eq!(calls, 1);
1460 assert_eq!(changed, 1);
1461 assert_eq!(state.values.get("signed").unwrap().signature, vec![9, 9]);
1462 assert_eq!(
1463 state.values.get("unsigned").unwrap().signature,
1464 vec![1, 2, 3, 4]
1465 );
1466 }
1467
1468 #[test]
1469 fn delete_channel_marks_channel_with_tombstone_version() {
1470 let live_key = format!("{CHANNEL_PREFIX}/abc");
1471 let mut state = mk_state(vec![(live_key.as_str(), 7, json!({"v": 1}))]);
1472
1473 state.delete_channel("abc");
1474
1475 assert_tombstone(&state, &live_key);
1476 }
1477
1478 #[test]
1479 fn delete_node_creates_tombstones_for_node_related_keys() {
1480 let node_id = "deadbeef";
1481 let node_key = format!("{NODE_PREFIX}/{node_id}");
1482 let node_state_key = format!("{NODE_STATE_PREFIX}/{node_id}");
1483 let allowlist_key = format!("{ALLOWLIST_PREFIX}/{node_id}");
1484 let tracker_key = format!("{TRACKER_PREFIX}/{node_id}");
1485 let channel_key = format!("{CHANNEL_PREFIX}/{node_id}cafebabe");
1486
1487 let mut state = mk_state(vec![
1488 (node_key.as_str(), 1, json!({"n": 1})),
1489 (node_state_key.as_str(), 2, json!({"s": 1})),
1490 (allowlist_key.as_str(), 3, json!(["127.0.0.1"])),
1491 (tracker_key.as_str(), 4, json!({"t": 1})),
1492 (channel_key.as_str(), 5, json!({"c": 1})),
1493 ]);
1494
1495 state.delete_node(node_id).unwrap();
1496
1497 for (live_key, old_version) in vec![
1498 (node_key, 1u64),
1499 (node_state_key, 2u64),
1500 (allowlist_key, 3u64),
1501 (tracker_key, 4u64),
1502 (channel_key, 5u64),
1503 ] {
1504 assert_tombstone(&state, &live_key);
1505 assert!(old_version < u64::MAX);
1506 }
1507 }
1508}