mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-18 10:49:44 -07:00
global: snapshot
This commit is contained in:
@@ -1495,35 +1495,35 @@ impl Vecs {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mmap = addresstypeindex_to_anyaddressindex_reader_opt
|
||||
let reader = addresstypeindex_to_anyaddressindex_reader_opt
|
||||
.get_unwrap(address_type)
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
|
||||
let anyaddressindex = match address_type {
|
||||
OutputType::P2PK33 => {
|
||||
p2pk33addressindex_to_anyaddressindex.get_or_read(typeindex.into(), mmap)
|
||||
p2pk33addressindex_to_anyaddressindex.get_or_read(typeindex.into(), reader)
|
||||
}
|
||||
OutputType::P2PK65 => {
|
||||
p2pk65addressindex_to_anyaddressindex.get_or_read(typeindex.into(), mmap)
|
||||
p2pk65addressindex_to_anyaddressindex.get_or_read(typeindex.into(), reader)
|
||||
}
|
||||
OutputType::P2PKH => {
|
||||
p2pkhaddressindex_to_anyaddressindex.get_or_read(typeindex.into(), mmap)
|
||||
p2pkhaddressindex_to_anyaddressindex.get_or_read(typeindex.into(), reader)
|
||||
}
|
||||
OutputType::P2SH => {
|
||||
p2shaddressindex_to_anyaddressindex.get_or_read(typeindex.into(), mmap)
|
||||
p2shaddressindex_to_anyaddressindex.get_or_read(typeindex.into(), reader)
|
||||
}
|
||||
OutputType::P2TR => {
|
||||
p2traddressindex_to_anyaddressindex.get_or_read(typeindex.into(), mmap)
|
||||
p2traddressindex_to_anyaddressindex.get_or_read(typeindex.into(), reader)
|
||||
}
|
||||
OutputType::P2WPKH => {
|
||||
p2wpkhaddressindex_to_anyaddressindex.get_or_read(typeindex.into(), mmap)
|
||||
p2wpkhaddressindex_to_anyaddressindex.get_or_read(typeindex.into(), reader)
|
||||
}
|
||||
OutputType::P2WSH => {
|
||||
p2wshaddressindex_to_anyaddressindex.get_or_read(typeindex.into(), mmap)
|
||||
p2wshaddressindex_to_anyaddressindex.get_or_read(typeindex.into(), reader)
|
||||
}
|
||||
OutputType::P2A => {
|
||||
p2aaddressindex_to_anyaddressindex.get_or_read(typeindex.into(), mmap)
|
||||
p2aaddressindex_to_anyaddressindex.get_or_read(typeindex.into(), reader)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
@@ -261,4 +261,12 @@ impl<'a> Interface<'a> {
|
||||
pub fn get_vecid_to_indexes(&self, id: String) -> Option<&Vec<&'static str>> {
|
||||
self.vecs.id_to_indexes(id)
|
||||
}
|
||||
|
||||
pub fn indexer(&self) -> &Indexer {
|
||||
self.indexer
|
||||
}
|
||||
|
||||
pub fn computer(&self) -> &Computer {
|
||||
self.computer
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
axum = { workspace = true }
|
||||
bitcoin = { workspace = true }
|
||||
bitcoincore-rpc = { workspace = true }
|
||||
brk_computer = { workspace = true }
|
||||
brk_error = { workspace = true }
|
||||
@@ -20,6 +21,7 @@ brk_interface = { workspace = true }
|
||||
brk_logger = { workspace = true }
|
||||
brk_mcp = { workspace = true }
|
||||
brk_parser = { workspace = true }
|
||||
brk_structs = { workspace = true }
|
||||
vecdb = { workspace = true }
|
||||
jiff = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use axum::{
|
||||
Json, Router,
|
||||
extract::{Path, Query, State},
|
||||
@@ -5,12 +7,20 @@ use axum::{
|
||||
response::{IntoResponse, Redirect, Response},
|
||||
routing::get,
|
||||
};
|
||||
use bitcoin::{Address, Network, absolute::LockTime};
|
||||
use bitcoincore_rpc::bitcoin;
|
||||
use brk_interface::{IdParam, Index, PaginatedIndexParam, PaginationParam, Params, ParamsOpt};
|
||||
use brk_structs::{
|
||||
AddressBytesHash, AnyAddressDataIndexEnum, Bitcoin, OutputType, Txid, TxidPrefix,
|
||||
};
|
||||
use serde_json::Number;
|
||||
use tracing::info;
|
||||
use vecdb::{AnyIterableVec, VecIterator};
|
||||
|
||||
use super::AppState;
|
||||
|
||||
mod explorer;
|
||||
mod interface;
|
||||
mod vecs;
|
||||
|
||||
pub trait ApiRoutes {
|
||||
fn add_api_routes(self) -> Self;
|
||||
@@ -21,6 +31,160 @@ const TO_SEPARATOR: &str = "_to_";
|
||||
impl ApiRoutes for Router<AppState> {
|
||||
fn add_api_routes(self) -> Self {
|
||||
self.route(
|
||||
"/api/address/{address}",
|
||||
get(
|
||||
async |Path(address): Path<String>, state: State<AppState>| -> Response {
|
||||
info!(address);
|
||||
let address = Address::from_str(&address).unwrap();
|
||||
if !address.is_valid_for_network(Network::Bitcoin) {
|
||||
return "Invalid address".into_response();
|
||||
}
|
||||
let address = address.assume_checked();
|
||||
let interface = state.interface;
|
||||
let indexer = interface.indexer();
|
||||
let computer = interface.computer();
|
||||
let stores = &indexer.stores;
|
||||
let hash = AddressBytesHash::from(&address);
|
||||
dbg!(&hash);
|
||||
dbg!(
|
||||
&address,
|
||||
address.address_type(),
|
||||
address.script_pubkey(),
|
||||
OutputType::from(&address)
|
||||
);
|
||||
let addri = stores
|
||||
.addressbyteshash_to_typeindex
|
||||
.get(&hash)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into_owned();
|
||||
println!("Script pubkey: {}", address.script_pubkey());
|
||||
println!("Address type: {:?}", address.address_type());
|
||||
let output_type = OutputType::from(&address);
|
||||
let stateful = &computer.stateful;
|
||||
let price = computer.price.as_ref().map(|v| {
|
||||
*v.timeindexes_to_price_close
|
||||
.dateindex
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.last()
|
||||
.unwrap()
|
||||
.1
|
||||
.into_owned()
|
||||
});
|
||||
|
||||
let anyaddri = match output_type {
|
||||
OutputType::P2PK33 => stateful
|
||||
.p2pk33addressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(addri.into()),
|
||||
OutputType::P2PK65 => stateful
|
||||
.p2pk65addressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(addri.into()),
|
||||
OutputType::P2PKH => stateful
|
||||
.p2pkhaddressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(addri.into()),
|
||||
OutputType::P2SH => stateful
|
||||
.p2shaddressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(addri.into()),
|
||||
OutputType::P2TR => stateful
|
||||
.p2traddressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(addri.into()),
|
||||
OutputType::P2WPKH => stateful
|
||||
.p2wpkhaddressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(addri.into()),
|
||||
OutputType::P2WSH => stateful
|
||||
.p2wshaddressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(addri.into()),
|
||||
OutputType::P2A => stateful
|
||||
.p2aaddressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(addri.into()),
|
||||
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let addr_data = match anyaddri.to_enum() {
|
||||
AnyAddressDataIndexEnum::Loaded(loadedi) => stateful
|
||||
.loadedaddressindex_to_loadedaddressdata
|
||||
.iter()
|
||||
.unwrap_get_inner(loadedi),
|
||||
AnyAddressDataIndexEnum::Empty(emptyi) => stateful
|
||||
.emptyaddressindex_to_emptyaddressdata
|
||||
.iter()
|
||||
.unwrap_get_inner(emptyi)
|
||||
.into(),
|
||||
};
|
||||
|
||||
let amount = addr_data.amount();
|
||||
Json(serde_json::json!({
|
||||
"address": address,
|
||||
"type": output_type,
|
||||
"index": addri,
|
||||
"chain_stats": {
|
||||
"funded_txo_count": serde_json::Value::Null,
|
||||
"funded_txo_sum": addr_data.received,
|
||||
"spent_txo_count": serde_json::Value::Null,
|
||||
"spent_txo_sum": addr_data.sent,
|
||||
"utxo_count": addr_data.utxos,
|
||||
"balance": amount,
|
||||
"balance_usd": price.map_or(serde_json::Value::Null, |p| serde_json::Value::Number(Number::from_f64( *(p * Bitcoin::from(amount))).unwrap())),
|
||||
"realized_value": addr_data.realized_cap,
|
||||
"tx_count": serde_json::Value::Null,
|
||||
"avg_cost_basis": addr_data.realized_price()
|
||||
},
|
||||
"mempool_stats": serde_json::Value::Null
|
||||
}))
|
||||
.into_response()
|
||||
},
|
||||
),
|
||||
)
|
||||
.route(
|
||||
"/api/tx/{txid}",
|
||||
get(
|
||||
async |Path(txid): Path<String>, state: State<AppState>| -> Response {
|
||||
let txid = bitcoin::Txid::from_str(&txid).unwrap();
|
||||
let txid = Txid::from(txid);
|
||||
let prefix = TxidPrefix::from(&txid);
|
||||
let interface = state.interface;
|
||||
let indexer = interface.indexer();
|
||||
let txindex = indexer
|
||||
.stores
|
||||
.txidprefix_to_txindex
|
||||
.get(&prefix)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into_owned();
|
||||
let version = indexer
|
||||
.vecs
|
||||
.txindex_to_txversion
|
||||
.iter()
|
||||
.unwrap_get_inner(txindex);
|
||||
let rawlocktime = indexer
|
||||
.vecs
|
||||
.txindex_to_rawlocktime
|
||||
.iter()
|
||||
.unwrap_get_inner(txindex);
|
||||
let locktime = LockTime::from(rawlocktime);
|
||||
|
||||
Json(serde_json::json!({
|
||||
"txid": txid,
|
||||
"index": txindex,
|
||||
"version": version,
|
||||
"locktime": locktime
|
||||
}))
|
||||
.into_response()
|
||||
},
|
||||
),
|
||||
)
|
||||
.route(
|
||||
"/api/vecs/index-count",
|
||||
get(async |State(app_state): State<AppState>| -> Response {
|
||||
Json(app_state.interface.get_index_count()).into_response()
|
||||
@@ -81,7 +245,7 @@ impl ApiRoutes for Router<AppState> {
|
||||
),
|
||||
)
|
||||
// .route("/api/vecs/variants", get(variants_handler))
|
||||
.route("/api/vecs/query", get(interface::handler))
|
||||
.route("/api/vecs/query", get(vecs::handler))
|
||||
.route(
|
||||
"/api/vecs/{variant}",
|
||||
get(
|
||||
@@ -99,7 +263,7 @@ impl ApiRoutes for Router<AppState> {
|
||||
(index, split.collect::<Vec<_>>().join(TO_SEPARATOR)),
|
||||
params_opt,
|
||||
));
|
||||
interface::handler(uri, headers, Query(params), state).await
|
||||
vecs::handler(uri, headers, Query(params), state).await
|
||||
} else {
|
||||
"Bad variant".into_response()
|
||||
}
|
||||
|
||||
@@ -17,3 +17,4 @@ brk_structs = { workspace = true }
|
||||
byteview = { workspace = true }
|
||||
fjall = { workspace = true }
|
||||
log = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
|
||||
+15
-10
@@ -6,6 +6,7 @@ use std::{
|
||||
fmt::Debug,
|
||||
fs, mem,
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use brk_error::Result;
|
||||
@@ -22,13 +23,13 @@ mod meta;
|
||||
pub use any::*;
|
||||
use log::info;
|
||||
use meta::*;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
pub struct Store<Key, Value> {
|
||||
meta: StoreMeta,
|
||||
name: &'static str,
|
||||
keyspace: TransactionalKeyspace,
|
||||
// Arc it
|
||||
partition: Option<TransactionalPartitionHandle>,
|
||||
partition: Arc<RwLock<Option<TransactionalPartitionHandle>>>,
|
||||
rtx: ReadTransaction,
|
||||
puts: BTreeMap<Key, Value>,
|
||||
dels: BTreeSet<Key>,
|
||||
@@ -77,7 +78,7 @@ where
|
||||
meta,
|
||||
name: Box::leak(Box::new(name.to_string())),
|
||||
keyspace: keyspace.clone(),
|
||||
partition: Some(partition),
|
||||
partition: Arc::new(RwLock::new(Some(partition))),
|
||||
rtx,
|
||||
puts: BTreeMap::new(),
|
||||
dels: BTreeSet::new(),
|
||||
@@ -90,7 +91,7 @@ where
|
||||
Ok(Some(Cow::Borrowed(v)))
|
||||
} else if let Some(slice) = self
|
||||
.rtx
|
||||
.get(self.partition.as_ref().unwrap(), ByteView::from(key))?
|
||||
.get(self.partition.read().as_ref().unwrap(), ByteView::from(key))?
|
||||
{
|
||||
Ok(Some(Cow::Owned(V::from(ByteView::from(slice)))))
|
||||
} else {
|
||||
@@ -100,7 +101,7 @@ where
|
||||
|
||||
pub fn is_empty(&self) -> Result<bool> {
|
||||
self.rtx
|
||||
.is_empty(self.partition.as_ref().unwrap())
|
||||
.is_empty(self.partition.read().as_ref().unwrap())
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
@@ -128,7 +129,7 @@ where
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (K, V)> {
|
||||
self.rtx
|
||||
.iter(self.partition.as_ref().unwrap())
|
||||
.iter(self.partition.read().as_ref().unwrap())
|
||||
.map(|res| res.unwrap())
|
||||
.map(|(k, v)| (K::from(ByteView::from(k)), V::from(ByteView::from(v))))
|
||||
}
|
||||
@@ -203,7 +204,9 @@ where
|
||||
|
||||
let mut wtx = self.keyspace.write_tx();
|
||||
|
||||
let partition = self.partition.as_ref().unwrap();
|
||||
let partition = self.partition.read();
|
||||
|
||||
let partition = partition.as_ref().unwrap();
|
||||
|
||||
remove.for_each(|key| wtx.remove(partition, ByteView::from(key)));
|
||||
|
||||
@@ -262,7 +265,9 @@ where
|
||||
fn reset(&mut self) -> Result<()> {
|
||||
info!("Resetting {}...", self.name);
|
||||
|
||||
let partition: TransactionalPartitionHandle = self.partition.take().unwrap();
|
||||
let mut opt = self.partition.write();
|
||||
|
||||
let partition = opt.take().unwrap();
|
||||
|
||||
self.keyspace.delete_partition(partition)?;
|
||||
|
||||
@@ -270,7 +275,7 @@ where
|
||||
|
||||
let partition = Self::open_partition_handle(&self.keyspace, self.name, self.bloom_filters)?;
|
||||
|
||||
self.partition.replace(partition);
|
||||
opt.replace(partition);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -301,7 +306,7 @@ where
|
||||
meta: self.meta.clone(),
|
||||
name: self.name,
|
||||
keyspace: self.keyspace.clone(),
|
||||
partition: None,
|
||||
partition: self.partition.clone(),
|
||||
rtx: self.keyspace.read_tx(),
|
||||
puts: self.puts.clone(),
|
||||
dels: self.dels.clone(),
|
||||
|
||||
@@ -59,6 +59,12 @@ impl AddressBytes {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Address> for AddressBytes {
|
||||
fn from(value: &Address) -> Self {
|
||||
Self::try_from((&value.script_pubkey(), OutputType::from(value))).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<(&ScriptBuf, OutputType)> for AddressBytes {
|
||||
type Error = Error;
|
||||
fn try_from(tuple: (&ScriptBuf, OutputType)) -> Result<Self, Self::Error> {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use bitcoin::Address;
|
||||
use byteview::ByteView;
|
||||
use derive_deref::Deref;
|
||||
use zerocopy::{FromBytes, IntoBytes};
|
||||
@@ -21,6 +22,12 @@ use super::{AddressBytes, OutputType};
|
||||
)]
|
||||
pub struct AddressBytesHash([u8; 8]);
|
||||
|
||||
impl From<&Address> for AddressBytesHash {
|
||||
fn from(value: &Address) -> Self {
|
||||
Self::from((&AddressBytes::from(value), OutputType::from(value)))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(&AddressBytes, OutputType)> for AddressBytesHash {
|
||||
fn from((address_bytes, outputtype): (&AddressBytes, OutputType)) -> Self {
|
||||
let mut slice = rapidhash::v3::rapidhash_v3(address_bytes.as_slice()).to_le_bytes();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use bitcoin::{ScriptBuf, opcodes::all::OP_PUSHBYTES_2};
|
||||
use bitcoin::{Address, AddressType, ScriptBuf, opcodes::all::OP_PUSHBYTES_2};
|
||||
use brk_error::Error;
|
||||
use serde::Serialize;
|
||||
use zerocopy_derive::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
@@ -380,3 +381,38 @@ impl From<&ScriptBuf> for OutputType {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Address> for OutputType {
|
||||
fn from(value: &Address) -> Self {
|
||||
Self::from(&value.script_pubkey())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AddressType> for OutputType {
|
||||
fn from(value: AddressType) -> Self {
|
||||
match value {
|
||||
AddressType::P2a => Self::P2A,
|
||||
AddressType::P2pkh => Self::P2PKH,
|
||||
AddressType::P2sh => Self::P2SH,
|
||||
AddressType::P2tr => Self::P2TR,
|
||||
AddressType::P2wpkh => Self::P2WPKH,
|
||||
AddressType::P2wsh => Self::P2WSH,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<OutputType> for AddressType {
|
||||
type Error = Error;
|
||||
fn try_from(value: OutputType) -> Result<Self, Self::Error> {
|
||||
Ok(match value {
|
||||
OutputType::P2A => Self::P2a,
|
||||
OutputType::P2PKH => Self::P2pkh,
|
||||
OutputType::P2SH => Self::P2sh,
|
||||
OutputType::P2TR => Self::P2tr,
|
||||
OutputType::P2WPKH => Self::P2wpkh,
|
||||
OutputType::P2WSH => Self::P2wsh,
|
||||
_ => return Err(Error::Str("Bad output format")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user