use miniscript::bitcoin::{self, address::NetworkUnchecked, Script, ScriptBuf};
use serde::{Deserialize, Serialize};
use std::{collections::BTreeMap, sync::mpsc};
use crate::{
account::{AddrAccount, AddressStatus, Notification, RustAddress},
config::Config,
derivator::Derivator,
};
#[derive(Debug, Clone, Copy)]
pub struct AddressTip {
pub recv: u32,
pub change: u32,
}
#[derive(Debug)]
pub struct AddressStore {
store: BTreeMap<ScriptBuf, AddressEntry>,
recv_generated_tip: u32,
change_generated_tip: u32,
derivator: Derivator,
notification: mpsc::Sender<Notification>,
tx_listener: Option<mpsc::Sender<AddressTip>>,
look_ahead: u32,
config: Option<Config>,
}
impl AddressStore {
pub fn new(
derivator: Derivator,
notification: mpsc::Sender<Notification>,
recv_tip: u32,
change_tip: u32,
look_ahead: u32,
config: Option<Config>,
) -> Self {
let store = Self {
derivator,
store: BTreeMap::new(),
recv_generated_tip: recv_tip,
change_generated_tip: change_tip,
notification,
tx_listener: None,
look_ahead,
config,
};
store.update_watch_tip();
store
}
fn notify(&self) {
if let Err(e) = self.notification.send(Notification::AddressTipChanged) {
log::error!("AddressStore::notify() fail to send notification: {e:?}");
}
self.update_watch_tip();
}
fn update_watch_tip(&self) {
if let Some(tx_listener) = &self.tx_listener {
let recv = self.recv_watch_tip();
let change = self.change_watch_tip();
let _ = tx_listener.send(AddressTip { recv, change });
}
if let Some(config) = &self.config {
config.persist_tip(self.recv_generated_tip, self.change_generated_tip);
}
}
pub fn recv_coin_at(&mut self, spk: &ScriptBuf) {
let AddressEntry { account, index, .. } = self.store.get(spk).expect("must be there");
match *account {
AddrAccount::Receive => self.update_recv(*index),
AddrAccount::Change => self.update_change(*index),
}
}
pub fn populate_maybe(&mut self) {
for i in 0..self.recv_watch_tip() + 1 {
let addr = self.derivator.receive_at(i);
let script = addr.script_pubkey();
self.store.entry(script).or_insert_with(|| {
let address = addr.as_unchecked().clone();
AddressEntry {
status: AddressStatus::NotUsed,
address,
account: AddrAccount::Receive,
index: i,
}
});
}
for i in 0..self.change_watch_tip() + 1 {
let addr = self.derivator.change_at(i);
let script = addr.script_pubkey();
self.store.entry(script).or_insert_with(|| {
let address = addr.as_unchecked().clone();
AddressEntry {
status: AddressStatus::NotUsed,
address,
account: AddrAccount::Change,
index: i,
}
});
}
}
pub fn update_recv(&mut self, index: u32) {
if index > self.recv_generated_tip {
self.recv_generated_tip = index;
}
self.populate_maybe();
self.notify();
}
pub fn update_change(&mut self, index: u32) {
if index > self.change_generated_tip {
self.change_generated_tip = index;
}
self.populate_maybe();
self.notify();
}
pub fn new_recv_addr(&mut self) -> bitcoin::Address {
self.recv_generated_tip += 1;
self.update_recv(self.recv_generated_tip);
self.derivator.receive_at(self.recv_generated_tip)
}
pub fn new_change_addr(&mut self) -> bitcoin::Address {
self.change_generated_tip += 1;
self.update_change(self.change_generated_tip);
self.derivator.change_at(self.change_generated_tip)
}
pub fn change_watch_tip(&self) -> u32 {
self.change_generated_tip + self.look_ahead + 1
}
pub fn recv_watch_tip(&self) -> u32 {
self.recv_generated_tip + self.look_ahead + 1
}
pub fn recv_tip(&self) -> u32 {
self.recv_generated_tip
}
pub fn init(&mut self, tx_listener: mpsc::Sender<AddressTip>) {
self.populate_maybe();
self.tx_listener = Some(tx_listener);
self.notify();
}
pub fn get_entry(&self, spk: &Script) -> Option<AddressEntry> {
self.store.get(spk).cloned()
}
pub fn get_entry_mut(&mut self, spk: &Script) -> Option<&mut AddressEntry> {
self.store.get_mut(spk)
}
pub fn get_unused(&self) -> Vec<RustAddress> {
let mut addrs = self
.store
.clone()
.into_iter()
.filter_map(|(_, entry)| {
if entry.status == AddressStatus::NotUsed && entry.account == AddrAccount::Receive {
Some(entry.clone())
} else {
None
}
})
.collect::<Vec<_>>();
addrs.sort_by(|a, b| {
a.account
.cmp(&b.account)
.then_with(|| a.index.cmp(&b.index))
});
addrs.into_iter().map(Into::into).collect()
}
pub fn get(&self, account: AddrAccount) -> Vec<RustAddress> {
let mut addrs = self
.store
.clone()
.into_iter()
.filter_map(|(_, entry)| {
if entry.account == account {
Some(entry.clone())
} else {
None
}
})
.collect::<Vec<_>>();
addrs.sort_by(|a, b| {
a.account
.cmp(&b.account)
.then_with(|| a.index.cmp(&b.index))
});
addrs.into_iter().map(Into::into).collect()
}
pub fn dump(&self) -> Result<serde_json::Value, serde_json::Error> {
serde_json::to_value(&self.store)
}
pub fn restore(&mut self, value: serde_json::Value) -> Result<(), serde_json::Error> {
self.store = serde_json::from_value(value)?;
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddressEntry {
pub status: AddressStatus,
pub address: bitcoin::Address<NetworkUnchecked>,
pub account: AddrAccount,
pub index: u32,
}
impl AddressEntry {
pub fn script(&self) -> ScriptBuf {
self.address.clone().assume_checked().script_pubkey()
}
pub fn set_status(&mut self, status: AddressStatus) {
self.status = status;
}
pub fn status(&self) -> AddressStatus {
self.status
}
pub fn value(&self) -> String {
self.address.clone().assume_checked().to_string()
}
pub fn account(&self) -> AddrAccount {
self.account
}
pub fn account_u32(&self) -> u32 {
match self.account {
AddrAccount::Receive => 0,
AddrAccount::Change => 1,
}
}
pub fn index(&self) -> u32 {
self.index
}
pub fn address(&self) -> bitcoin::Address<NetworkUnchecked> {
self.address.clone()
}
pub fn clone_boxed(&self) -> Box<Self> {
Box::new(self.clone())
}
}
impl From<AddressEntry> for RustAddress {
fn from(value: AddressEntry) -> Self {
RustAddress {
address: value.address().assume_checked().to_string(),
status: value.status(),
account: value.account(),
index: value.index(),
}
}
}