mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
computer: store part 10
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -512,6 +512,7 @@ dependencies = [
|
||||
"brk_vec",
|
||||
"color-eyre",
|
||||
"derive_deref",
|
||||
"fjall",
|
||||
"jiff",
|
||||
"log",
|
||||
"rayon",
|
||||
@@ -575,6 +576,7 @@ dependencies = [
|
||||
"brk_store",
|
||||
"brk_vec",
|
||||
"color-eyre",
|
||||
"fjall",
|
||||
"log",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
@@ -13,6 +13,10 @@ lto = "fat"
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
[profile.profiling]
|
||||
inherits = "release"
|
||||
debug = true
|
||||
|
||||
[profile.dist]
|
||||
inherits = "release"
|
||||
|
||||
|
||||
2
crates/brk/.gitignore
vendored
Normal file
2
crates/brk/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
profile.json.gz
|
||||
flamegraph.svg
|
||||
2
crates/brk/flamegraph.sh
Executable file
2
crates/brk/flamegraph.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
cargo build --profile profiling
|
||||
flamegraph -- ../../target/profiling/brk
|
||||
2
crates/brk/samply.sh
Executable file
2
crates/brk/samply.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
cargo build --profile profiling
|
||||
samply record ../../target/profiling/brk
|
||||
@@ -21,6 +21,7 @@ brk_store = { workspace = true }
|
||||
brk_vec = { workspace = true }
|
||||
color-eyre = { workspace = true }
|
||||
derive_deref = { workspace = true }
|
||||
fjall = { workspace = true }
|
||||
jiff = { workspace = true }
|
||||
log = { workspace = true }
|
||||
rayon = { workspace = true }
|
||||
|
||||
@@ -53,6 +53,7 @@ impl Computer {
|
||||
// TODO: Give self.path, join inside import
|
||||
&outputs_dir.join("stores"),
|
||||
VERSION + Version::ZERO,
|
||||
&indexer.stores.keyspace,
|
||||
)?,
|
||||
fetcher,
|
||||
})
|
||||
|
||||
@@ -6,6 +6,7 @@ use brk_core::{
|
||||
P2WPKHAddressIndex, P2WSHAddressIndex, Result, TypeIndex, Version,
|
||||
};
|
||||
use brk_store::{AnyStore, Store};
|
||||
use fjall::{PersistMode, TransactionalKeyspace};
|
||||
use log::info;
|
||||
|
||||
use crate::vecs::stateful::{AddressTypeToTypeIndexTree, WithAddressDataSource};
|
||||
@@ -14,6 +15,8 @@ const VERSION: Version = Version::ZERO;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Stores {
|
||||
keyspace: TransactionalKeyspace,
|
||||
|
||||
pub p2aaddressindex_to_addressdata: Store<P2AAddressIndex, AddressData>,
|
||||
pub p2aaddressindex_to_emptyaddressdata: Store<P2AAddressIndex, EmptyAddressData>,
|
||||
pub p2pk33addressindex_to_addressdata: Store<P2PK33AddressIndex, AddressData>,
|
||||
@@ -33,7 +36,11 @@ pub struct Stores {
|
||||
}
|
||||
|
||||
impl Stores {
|
||||
pub fn import(path: &Path, version: Version) -> color_eyre::Result<Self> {
|
||||
pub fn import(
|
||||
path: &Path,
|
||||
version: Version,
|
||||
keyspace: &TransactionalKeyspace,
|
||||
) -> color_eyre::Result<Self> {
|
||||
let (
|
||||
(p2aaddressindex_to_addressdata, p2aaddressindex_to_emptyaddressdata),
|
||||
(p2pk33addressindex_to_addressdata, p2pk33addressindex_to_emptyaddressdata),
|
||||
@@ -47,6 +54,7 @@ impl Stores {
|
||||
let p2a = scope.spawn(|| {
|
||||
(
|
||||
Store::import(
|
||||
keyspace,
|
||||
path,
|
||||
"p2aaddressindex_to_addressdata",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -54,6 +62,7 @@ impl Stores {
|
||||
)
|
||||
.unwrap(),
|
||||
Store::import(
|
||||
keyspace,
|
||||
path,
|
||||
"p2aaddressindex_to_emptyaddressdata",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -66,6 +75,7 @@ impl Stores {
|
||||
let p2pk33 = scope.spawn(|| {
|
||||
(
|
||||
Store::import(
|
||||
keyspace,
|
||||
path,
|
||||
"p2pk33addressindex_to_addressdata",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -73,6 +83,7 @@ impl Stores {
|
||||
)
|
||||
.unwrap(),
|
||||
Store::import(
|
||||
keyspace,
|
||||
path,
|
||||
"p2pk33addressindex_to_emptyaddressdata",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -85,6 +96,7 @@ impl Stores {
|
||||
let p2pk65 = scope.spawn(|| {
|
||||
(
|
||||
Store::import(
|
||||
keyspace,
|
||||
path,
|
||||
"p2pk65addressindex_to_addressdata",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -92,6 +104,7 @@ impl Stores {
|
||||
)
|
||||
.unwrap(),
|
||||
Store::import(
|
||||
keyspace,
|
||||
path,
|
||||
"p2pk65addressindex_to_emptyaddressdata",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -104,6 +117,7 @@ impl Stores {
|
||||
let p2pkh = scope.spawn(|| {
|
||||
(
|
||||
Store::import(
|
||||
keyspace,
|
||||
path,
|
||||
"p2pkhaddressindex_to_addressdata",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -111,6 +125,7 @@ impl Stores {
|
||||
)
|
||||
.unwrap(),
|
||||
Store::import(
|
||||
keyspace,
|
||||
path,
|
||||
"p2pkhaddressindex_to_emptyaddressdata",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -123,6 +138,7 @@ impl Stores {
|
||||
let p2sh = scope.spawn(|| {
|
||||
(
|
||||
Store::import(
|
||||
keyspace,
|
||||
path,
|
||||
"p2shaddressindex_to_addressdata",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -130,6 +146,7 @@ impl Stores {
|
||||
)
|
||||
.unwrap(),
|
||||
Store::import(
|
||||
keyspace,
|
||||
path,
|
||||
"p2shaddressindex_to_emptyaddressdata",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -142,6 +159,7 @@ impl Stores {
|
||||
let p2tr = scope.spawn(|| {
|
||||
(
|
||||
Store::import(
|
||||
keyspace,
|
||||
path,
|
||||
"p2traddressindex_to_addressdata",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -149,6 +167,7 @@ impl Stores {
|
||||
)
|
||||
.unwrap(),
|
||||
Store::import(
|
||||
keyspace,
|
||||
path,
|
||||
"p2traddressindex_to_emptyaddressdata",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -161,6 +180,7 @@ impl Stores {
|
||||
let p2wpkh = scope.spawn(|| {
|
||||
(
|
||||
Store::import(
|
||||
keyspace,
|
||||
path,
|
||||
"p2wpkhaddressindex_to_addressdata",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -168,6 +188,7 @@ impl Stores {
|
||||
)
|
||||
.unwrap(),
|
||||
Store::import(
|
||||
keyspace,
|
||||
path,
|
||||
"p2wpkhaddressindex_to_emptyaddressdata",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -180,6 +201,7 @@ impl Stores {
|
||||
let p2wsh = scope.spawn(|| {
|
||||
(
|
||||
Store::import(
|
||||
keyspace,
|
||||
path,
|
||||
"p2wshaddressindex_to_addressdata",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -187,6 +209,7 @@ impl Stores {
|
||||
)
|
||||
.unwrap(),
|
||||
Store::import(
|
||||
keyspace,
|
||||
path,
|
||||
"p2wshaddressindex_to_emptyaddressdata",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -209,6 +232,8 @@ impl Stores {
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
keyspace: keyspace.clone(),
|
||||
|
||||
p2aaddressindex_to_addressdata,
|
||||
p2aaddressindex_to_emptyaddressdata,
|
||||
|
||||
@@ -249,7 +274,11 @@ impl Stores {
|
||||
|
||||
self.as_mut_slice()
|
||||
.into_iter()
|
||||
.try_for_each(|store| store.reset())
|
||||
.try_for_each(|store| store.reset())?;
|
||||
|
||||
self.keyspace
|
||||
.persist(PersistMode::SyncAll)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub fn get_addressdata(
|
||||
@@ -563,7 +592,9 @@ impl Stores {
|
||||
});
|
||||
});
|
||||
|
||||
Ok(())
|
||||
self.keyspace
|
||||
.persist(PersistMode::SyncAll)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub fn rotate_memtables(&self) {
|
||||
|
||||
@@ -11,7 +11,10 @@ use crate::{
|
||||
states::AddressCohortState,
|
||||
vecs::{
|
||||
Indexes, fetched, indexes, market,
|
||||
stateful::{common, r#trait::CohortVecs},
|
||||
stateful::{
|
||||
common,
|
||||
r#trait::{CohortVecs, DynCohortVecs},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -28,9 +31,9 @@ pub struct Vecs {
|
||||
pub inner: common::Vecs,
|
||||
}
|
||||
|
||||
impl CohortVecs for Vecs {
|
||||
impl Vecs {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn forced_import(
|
||||
pub fn forced_import(
|
||||
path: &Path,
|
||||
cohort_name: Option<&str>,
|
||||
computation: Computation,
|
||||
@@ -68,7 +71,9 @@ impl CohortVecs for Vecs {
|
||||
)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DynCohortVecs for Vecs {
|
||||
fn starting_height(&self) -> Height {
|
||||
[
|
||||
self.state.height().map_or(Height::MAX, |h| h.incremented()),
|
||||
@@ -146,6 +151,25 @@ impl CohortVecs for Vecs {
|
||||
.safe_flush_stateful_vecs(height, exit, &mut self.state.inner)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn compute_rest_part1(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
indexes: &indexes::Vecs,
|
||||
fetched: Option<&fetched::Vecs>,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> color_eyre::Result<()> {
|
||||
self.inner
|
||||
.compute_rest_part1(indexer, indexes, fetched, starting_indexes, exit)
|
||||
}
|
||||
|
||||
fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
|
||||
[self.inner.vecs(), vec![&self.height_to_address_count]].concat()
|
||||
}
|
||||
}
|
||||
|
||||
impl CohortVecs for Vecs {
|
||||
fn compute_from_stateful(
|
||||
&mut self,
|
||||
starting_indexes: &Indexes,
|
||||
@@ -168,19 +192,6 @@ impl CohortVecs for Vecs {
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn compute_rest_part1(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
indexes: &indexes::Vecs,
|
||||
fetched: Option<&fetched::Vecs>,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> color_eyre::Result<()> {
|
||||
self.inner
|
||||
.compute_rest_part1(indexer, indexes, fetched, starting_indexes, exit)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn compute_rest_part2(
|
||||
&mut self,
|
||||
@@ -208,10 +219,6 @@ impl CohortVecs for Vecs {
|
||||
exit,
|
||||
)
|
||||
}
|
||||
|
||||
fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
|
||||
[self.inner.vecs(), vec![&self.height_to_address_count]].concat()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Vecs {
|
||||
|
||||
@@ -11,7 +11,10 @@ use rayon::prelude::*;
|
||||
|
||||
use crate::vecs::{
|
||||
Indexes, fetched,
|
||||
stateful::{address_cohort, r#trait::CohortVecs},
|
||||
stateful::{
|
||||
address_cohort,
|
||||
r#trait::{CohortVecs, DynCohortVecs},
|
||||
},
|
||||
};
|
||||
|
||||
const VERSION: Version = Version::new(0);
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
use std::mem;
|
||||
|
||||
use brk_core::TypeIndex;
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
|
||||
use super::GroupedByAddressType;
|
||||
|
||||
#[derive(Debug, Default, Deref, DerefMut)]
|
||||
pub struct AddressTypeToTypeIndexVec<T>(GroupedByAddressType<Vec<(TypeIndex, T)>>);
|
||||
pub struct AddressTypeToVec<T>(GroupedByAddressType<Vec<T>>);
|
||||
|
||||
impl<T> AddressTypeToTypeIndexVec<T> {
|
||||
impl<T> AddressTypeToVec<T> {
|
||||
pub fn merge(&mut self, mut other: Self) {
|
||||
Self::merge_(&mut self.p2pk65, &mut other.p2pk65);
|
||||
Self::merge_(&mut self.p2pk33, &mut other.p2pk33);
|
||||
@@ -20,7 +19,7 @@ impl<T> AddressTypeToTypeIndexVec<T> {
|
||||
Self::merge_(&mut self.p2a, &mut other.p2a);
|
||||
}
|
||||
|
||||
fn merge_(own: &mut Vec<(TypeIndex, T)>, other: &mut Vec<(TypeIndex, T)>) {
|
||||
fn merge_(own: &mut Vec<T>, other: &mut Vec<T>) {
|
||||
if own.len() >= other.len() {
|
||||
own.append(other);
|
||||
} else {
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{cmp::Ordering, collections::BTreeMap, mem, path::Path, thread};
|
||||
|
||||
use brk_core::{
|
||||
AddressData, CheckedSub, DateIndex, Dollars, EmptyAddressData, GroupedByAddressType, Height,
|
||||
InputIndex, OutputIndex, OutputType, Result, Sats, Version,
|
||||
InputIndex, OutputIndex, OutputType, Result, Sats, TypeIndex, Version,
|
||||
};
|
||||
use brk_exit::Exit;
|
||||
use brk_indexer::Indexer;
|
||||
@@ -20,7 +20,7 @@ use crate::{
|
||||
market,
|
||||
stateful::{
|
||||
addresstype_to_addresscount::AddressTypeToAddressCount,
|
||||
addresstype_to_addresscount_vec::AddressTypeToAddressCountVec,
|
||||
addresstype_to_addresscount_vec::AddressTypeToAddressCountVec, r#trait::DynCohortVecs,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -269,6 +269,14 @@ impl Vecs {
|
||||
) -> color_eyre::Result<()> {
|
||||
let height_to_first_outputindex = &indexer.vecs.height_to_first_outputindex;
|
||||
let height_to_first_inputindex = &indexer.vecs.height_to_first_inputindex;
|
||||
let height_to_first_p2aaddressindex = &indexer.vecs.height_to_first_p2aaddressindex;
|
||||
let height_to_first_p2pk33addressindex = &indexer.vecs.height_to_first_p2pk33addressindex;
|
||||
let height_to_first_p2pk65addressindex = &indexer.vecs.height_to_first_p2pk65addressindex;
|
||||
let height_to_first_p2pkhaddressindex = &indexer.vecs.height_to_first_p2pkhaddressindex;
|
||||
let height_to_first_p2shaddressindex = &indexer.vecs.height_to_first_p2shaddressindex;
|
||||
let height_to_first_p2traddressindex = &indexer.vecs.height_to_first_p2traddressindex;
|
||||
let height_to_first_p2wpkhaddressindex = &indexer.vecs.height_to_first_p2wpkhaddressindex;
|
||||
let height_to_first_p2wshaddressindex = &indexer.vecs.height_to_first_p2wshaddressindex;
|
||||
let height_to_output_count = transactions.indexes_to_output_count.height.unwrap_sum();
|
||||
let height_to_input_count = transactions.indexes_to_input_count.height.unwrap_sum();
|
||||
let inputindex_to_outputindex = &indexer.vecs.inputindex_to_outputindex;
|
||||
@@ -300,11 +308,24 @@ impl Vecs {
|
||||
let outputindex_to_typeindex_mmap = outputindex_to_typeindex.mmap().load();
|
||||
let outputindex_to_txindex_mmap = outputindex_to_txindex.mmap().load();
|
||||
let txindex_to_height_mmap = txindex_to_height.mmap().load();
|
||||
let height_to_close_mmap = height_to_close.map(|v| v.mmap().load());
|
||||
let height_to_timestamp_fixed_mmap = height_to_timestamp_fixed.mmap().load();
|
||||
|
||||
let mut height_to_first_outputindex_iter = height_to_first_outputindex.into_iter();
|
||||
let mut height_to_first_inputindex_iter = height_to_first_inputindex.into_iter();
|
||||
let mut height_to_first_p2aaddressindex_iter = height_to_first_p2aaddressindex.into_iter();
|
||||
let mut height_to_first_p2pk33addressindex_iter =
|
||||
height_to_first_p2pk33addressindex.into_iter();
|
||||
let mut height_to_first_p2pk65addressindex_iter =
|
||||
height_to_first_p2pk65addressindex.into_iter();
|
||||
let mut height_to_first_p2pkhaddressindex_iter =
|
||||
height_to_first_p2pkhaddressindex.into_iter();
|
||||
let mut height_to_first_p2shaddressindex_iter =
|
||||
height_to_first_p2shaddressindex.into_iter();
|
||||
let mut height_to_first_p2traddressindex_iter =
|
||||
height_to_first_p2traddressindex.into_iter();
|
||||
let mut height_to_first_p2wpkhaddressindex_iter =
|
||||
height_to_first_p2wpkhaddressindex.into_iter();
|
||||
let mut height_to_first_p2wshaddressindex_iter =
|
||||
height_to_first_p2wshaddressindex.into_iter();
|
||||
let mut height_to_output_count_iter = height_to_output_count.into_iter();
|
||||
let mut height_to_input_count_iter = height_to_input_count.into_iter();
|
||||
let mut height_to_close_iter = height_to_close.as_ref().map(|v| v.into_iter());
|
||||
@@ -318,6 +339,14 @@ impl Vecs {
|
||||
let base_version = Version::ZERO
|
||||
+ height_to_first_outputindex.version()
|
||||
+ height_to_first_inputindex.version()
|
||||
+ height_to_first_p2aaddressindex.version()
|
||||
+ height_to_first_p2pk33addressindex.version()
|
||||
+ height_to_first_p2pk65addressindex.version()
|
||||
+ height_to_first_p2pkhaddressindex.version()
|
||||
+ height_to_first_p2shaddressindex.version()
|
||||
+ height_to_first_p2traddressindex.version()
|
||||
+ height_to_first_p2wpkhaddressindex.version()
|
||||
+ height_to_first_p2wshaddressindex.version()
|
||||
+ height_to_timestamp_fixed.version()
|
||||
+ height_to_output_count.version()
|
||||
+ height_to_input_count.version()
|
||||
@@ -438,6 +467,11 @@ impl Vecs {
|
||||
.par_iter_mut()
|
||||
.for_each(|(_, v)| v.init(starting_height));
|
||||
|
||||
let height_to_close_vec =
|
||||
height_to_close.map(|height_to_close| height_to_close.collect().unwrap());
|
||||
|
||||
let height_to_timestamp_fixed_vec = height_to_timestamp_fixed.collect().unwrap();
|
||||
|
||||
let mut unspendable_supply = if let Some(prev_height) = starting_height.decremented() {
|
||||
self.height_to_unspendable_supply
|
||||
.into_iter()
|
||||
@@ -498,6 +532,34 @@ impl Vecs {
|
||||
let output_count = height_to_output_count_iter.unwrap_get_inner(height);
|
||||
let input_count = height_to_input_count_iter.unwrap_get_inner(height);
|
||||
|
||||
let first_addressindexes: GroupedByAddressType<TypeIndex> =
|
||||
GroupedByAddressType {
|
||||
p2a: height_to_first_p2aaddressindex_iter
|
||||
.unwrap_get_inner(height)
|
||||
.into(),
|
||||
p2pk33: height_to_first_p2pk33addressindex_iter
|
||||
.unwrap_get_inner(height)
|
||||
.into(),
|
||||
p2pk65: height_to_first_p2pk65addressindex_iter
|
||||
.unwrap_get_inner(height)
|
||||
.into(),
|
||||
p2pkh: height_to_first_p2pkhaddressindex_iter
|
||||
.unwrap_get_inner(height)
|
||||
.into(),
|
||||
p2sh: height_to_first_p2shaddressindex_iter
|
||||
.unwrap_get_inner(height)
|
||||
.into(),
|
||||
p2tr: height_to_first_p2traddressindex_iter
|
||||
.unwrap_get_inner(height)
|
||||
.into(),
|
||||
p2wpkh: height_to_first_p2wpkhaddressindex_iter
|
||||
.unwrap_get_inner(height)
|
||||
.into(),
|
||||
p2wsh: height_to_first_p2wshaddressindex_iter
|
||||
.unwrap_get_inner(height)
|
||||
.into(),
|
||||
};
|
||||
|
||||
let (
|
||||
(mut height_to_sent, addresstype_to_typedindex_to_sent_data),
|
||||
(mut received, addresstype_to_typedindex_to_received_data),
|
||||
@@ -532,31 +594,6 @@ impl Vecs {
|
||||
.unwrap()
|
||||
.into_owned();
|
||||
|
||||
let typeindex = outputindex_to_typeindex
|
||||
.get_or_read(outputindex, &outputindex_to_typeindex_mmap)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into_owned();
|
||||
|
||||
if input_type.is_unspendable() {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
let addressdata_opt = if input_type.is_address()
|
||||
&& !addresstype_to_typeindex_to_addressdata
|
||||
.get(input_type)
|
||||
.unwrap()
|
||||
.contains_key(&typeindex)
|
||||
&& let Some(address_data) =
|
||||
stores.get_addressdata(input_type, typeindex).unwrap()
|
||||
{
|
||||
Some(WithAddressDataSource::FromAddressDataStore(
|
||||
address_data,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let input_txindex = outputindex_to_txindex
|
||||
.get_or_read(outputindex, &outputindex_to_txindex_mmap)
|
||||
.unwrap()
|
||||
@@ -569,22 +606,49 @@ impl Vecs {
|
||||
.unwrap()
|
||||
.into_owned();
|
||||
|
||||
let prev_price = height_to_close.map(|m| {
|
||||
*m.get_or_read(
|
||||
if input_type.is_unspendable() {
|
||||
unreachable!()
|
||||
} else if input_type.is_not_address() {
|
||||
return (
|
||||
prev_height,
|
||||
height_to_close_mmap.as_ref().unwrap(),
|
||||
value,
|
||||
input_type,
|
||||
None
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into_owned()
|
||||
});
|
||||
}
|
||||
|
||||
let prev_timestamp = height_to_timestamp_fixed
|
||||
.get_or_read(prev_height, &height_to_timestamp_fixed_mmap)
|
||||
let typeindex = outputindex_to_typeindex
|
||||
.get_or_read(outputindex, &outputindex_to_typeindex_mmap)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into_owned();
|
||||
|
||||
let addressdata_opt = if input_type.is_address()
|
||||
&& *first_addressindexes.get(input_type).unwrap()
|
||||
> typeindex
|
||||
&& !addresstype_to_typeindex_to_addressdata
|
||||
.get(input_type)
|
||||
.unwrap()
|
||||
.contains_key(&typeindex)
|
||||
&& let Some(address_data) =
|
||||
stores.get_addressdata(input_type, typeindex).unwrap()
|
||||
// Otherwise it was empty and got funds in the same block before sending them
|
||||
{
|
||||
Some(WithAddressDataSource::FromAddressDataStore(
|
||||
address_data,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let prev_price = height_to_close_vec
|
||||
.as_ref()
|
||||
.map(|v| **v.get(prev_height.unwrap_to_usize()).unwrap());
|
||||
|
||||
let prev_timestamp = *height_to_timestamp_fixed_vec
|
||||
.get(prev_height.unwrap_to_usize())
|
||||
.unwrap();
|
||||
|
||||
let blocks_old =
|
||||
height.unwrap_to_usize() - prev_height.unwrap_to_usize();
|
||||
|
||||
@@ -600,25 +664,27 @@ impl Vecs {
|
||||
prev_height,
|
||||
value,
|
||||
input_type,
|
||||
typeindex,
|
||||
addressdata_opt,
|
||||
prev_price,
|
||||
blocks_old,
|
||||
days_old,
|
||||
older_than_hour,
|
||||
Some((typeindex,
|
||||
addressdata_opt,
|
||||
prev_price,
|
||||
blocks_old,
|
||||
days_old,
|
||||
older_than_hour
|
||||
))
|
||||
)
|
||||
})
|
||||
.fold(
|
||||
|| {
|
||||
(
|
||||
BTreeMap::<Height, Transacted>::default(),
|
||||
AddressTypeToTypeIndexVec::<(
|
||||
AddressTypeToVec::<(
|
||||
TypeIndex,
|
||||
Sats,
|
||||
Option<WithAddressDataSource<AddressData>>,
|
||||
Option<Dollars>,
|
||||
usize,
|
||||
f64,
|
||||
bool,
|
||||
bool
|
||||
)>::default(
|
||||
),
|
||||
)
|
||||
@@ -628,25 +694,23 @@ impl Vecs {
|
||||
height,
|
||||
value,
|
||||
input_type,
|
||||
typeindex,
|
||||
address_data_opt
|
||||
)| {
|
||||
tree.entry(height).or_default().iterate(value, input_type);
|
||||
if let Some((typeindex,
|
||||
addressdata_opt,
|
||||
prev_price,
|
||||
blocks_old,
|
||||
days_old,
|
||||
older_than_hour,
|
||||
)| {
|
||||
tree.entry(height).or_default().iterate(value, input_type);
|
||||
if let Some(vec) = vecs.get_mut(input_type) {
|
||||
vec.push((
|
||||
older_than_hour)) = address_data_opt {
|
||||
vecs.get_mut(input_type).unwrap().push((
|
||||
typeindex,
|
||||
(
|
||||
value,
|
||||
addressdata_opt,
|
||||
prev_price,
|
||||
blocks_old,
|
||||
days_old,
|
||||
older_than_hour,
|
||||
),
|
||||
value,
|
||||
addressdata_opt,
|
||||
prev_price,
|
||||
blocks_old,
|
||||
days_old,
|
||||
older_than_hour,
|
||||
));
|
||||
}
|
||||
(tree, vecs)
|
||||
@@ -656,15 +720,15 @@ impl Vecs {
|
||||
|| {
|
||||
(
|
||||
BTreeMap::<Height, Transacted>::default(),
|
||||
AddressTypeToTypeIndexVec::<(
|
||||
AddressTypeToVec::<(
|
||||
TypeIndex,
|
||||
Sats,
|
||||
Option<WithAddressDataSource<AddressData>>,
|
||||
Option<Dollars>,
|
||||
usize,
|
||||
f64,
|
||||
bool,
|
||||
)>::default(
|
||||
),
|
||||
)>::default(),
|
||||
)
|
||||
},
|
||||
|(first_tree, mut source_vecs), (second_tree, other_vecs)| {
|
||||
@@ -683,111 +747,119 @@ impl Vecs {
|
||||
)
|
||||
});
|
||||
|
||||
let received_handle = s.spawn(|| {
|
||||
(first_outputindex..first_outputindex + *output_count)
|
||||
.into_par_iter()
|
||||
.map(OutputIndex::from)
|
||||
.map(|outputindex| {
|
||||
let value = outputindex_to_value
|
||||
.get_or_read(outputindex, &outputindex_to_value_mmap)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into_owned();
|
||||
// let received_handle = s.spawn(|| {
|
||||
let received_output = (first_outputindex..first_outputindex + *output_count)
|
||||
.into_par_iter()
|
||||
.map(OutputIndex::from)
|
||||
.map(|outputindex| {
|
||||
let value = outputindex_to_value
|
||||
.get_or_read(outputindex, &outputindex_to_value_mmap)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into_owned();
|
||||
|
||||
let output_type = outputindex_to_outputtype
|
||||
.get_or_read(outputindex, &outputindex_to_outputtype_mmap)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into_owned();
|
||||
let output_type = outputindex_to_outputtype
|
||||
.get_or_read(outputindex, &outputindex_to_outputtype_mmap)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into_owned();
|
||||
|
||||
let typeindex = outputindex_to_typeindex
|
||||
.get_or_read(outputindex, &outputindex_to_typeindex_mmap)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into_owned();
|
||||
if output_type.is_not_address() {
|
||||
return (value, output_type, None);
|
||||
}
|
||||
|
||||
let addressdata_opt = if output_type.is_address()
|
||||
&& !addresstype_to_typeindex_to_addressdata
|
||||
.get(output_type)
|
||||
let typeindex = outputindex_to_typeindex
|
||||
.get_or_read(outputindex, &outputindex_to_typeindex_mmap)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into_owned();
|
||||
|
||||
let addressdata_opt = if *first_addressindexes.get(output_type).unwrap()
|
||||
<= typeindex {
|
||||
Some(WithAddressDataSource::New(AddressData::default()))
|
||||
} else if !addresstype_to_typeindex_to_addressdata
|
||||
.get(output_type)
|
||||
.unwrap()
|
||||
.contains_key(&typeindex)
|
||||
&& !addresstype_to_typeindex_to_emptyaddressdata
|
||||
.get(output_type)
|
||||
.unwrap()
|
||||
.contains_key(&typeindex) {
|
||||
Some(
|
||||
if let Some(addressdata) = stores
|
||||
.get_addressdata(output_type, typeindex)
|
||||
.unwrap()
|
||||
.contains_key(&typeindex)
|
||||
&& !addresstype_to_typeindex_to_emptyaddressdata
|
||||
.get(output_type)
|
||||
{
|
||||
WithAddressDataSource::FromAddressDataStore(
|
||||
addressdata,
|
||||
)
|
||||
} else if let Some(emptyaddressdata) = stores
|
||||
.get_emptyaddressdata(output_type, typeindex)
|
||||
.unwrap()
|
||||
.contains_key(&typeindex)
|
||||
{
|
||||
Some(
|
||||
if let Some(addressdata) = stores
|
||||
.get_addressdata(output_type, typeindex)
|
||||
.unwrap()
|
||||
{
|
||||
WithAddressDataSource::FromAddressDataStore(
|
||||
addressdata,
|
||||
)
|
||||
} else if let Some(emptyaddressdata) = stores
|
||||
.get_emptyaddressdata(output_type, typeindex)
|
||||
.unwrap()
|
||||
{
|
||||
WithAddressDataSource::FromEmptyAddressDataStore(
|
||||
emptyaddressdata.into(),
|
||||
)
|
||||
} else {
|
||||
WithAddressDataSource::New(AddressData::default())
|
||||
},
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
{
|
||||
WithAddressDataSource::FromEmptyAddressDataStore(
|
||||
emptyaddressdata.into(),
|
||||
)
|
||||
} else {
|
||||
WithAddressDataSource::New(AddressData::default())
|
||||
},
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
(value, output_type, typeindex, addressdata_opt)
|
||||
})
|
||||
.fold(
|
||||
|| {
|
||||
(
|
||||
Transacted::default(),
|
||||
AddressTypeToTypeIndexVec::<(
|
||||
Sats,
|
||||
Option<WithAddressDataSource<AddressData>>,
|
||||
)>::default(
|
||||
),
|
||||
)
|
||||
},
|
||||
|(mut transacted, mut vecs),
|
||||
(
|
||||
value,
|
||||
output_type,
|
||||
typeindex,
|
||||
addressdata_opt,
|
||||
)| {
|
||||
transacted.iterate(value, output_type);
|
||||
if let Some(vec) = vecs.get_mut(output_type) {
|
||||
vec.push((
|
||||
typeindex,
|
||||
(value, addressdata_opt),
|
||||
));
|
||||
}
|
||||
(transacted, vecs)
|
||||
},
|
||||
)
|
||||
.reduce(
|
||||
|| {
|
||||
(
|
||||
Transacted::default(),
|
||||
AddressTypeToTypeIndexVec::<(
|
||||
Sats,
|
||||
Option<WithAddressDataSource<AddressData>>,
|
||||
)>::default(
|
||||
),
|
||||
)
|
||||
},
|
||||
|(transacted, mut vecs), (other_transacted, other_vecs)| {
|
||||
vecs.merge(other_vecs);
|
||||
(transacted + other_transacted, vecs)
|
||||
},
|
||||
)
|
||||
});
|
||||
(value, output_type, Some((typeindex, addressdata_opt)))
|
||||
})
|
||||
.fold(
|
||||
|| {
|
||||
(
|
||||
Transacted::default(),
|
||||
AddressTypeToVec::<(
|
||||
TypeIndex,
|
||||
Sats,
|
||||
Option<WithAddressDataSource<AddressData>>,
|
||||
)>::default(
|
||||
),
|
||||
)
|
||||
},
|
||||
|(mut transacted, mut vecs),
|
||||
(
|
||||
value,
|
||||
output_type,
|
||||
typeindex_with_addressdata_opt,
|
||||
)| {
|
||||
transacted.iterate(value, output_type);
|
||||
if let Some(vec) = vecs.get_mut(output_type) {
|
||||
let (typeindex,
|
||||
addressdata_opt) = typeindex_with_addressdata_opt.unwrap();
|
||||
vec.push((
|
||||
typeindex,
|
||||
value,
|
||||
addressdata_opt,
|
||||
));
|
||||
}
|
||||
(transacted, vecs)
|
||||
},
|
||||
)
|
||||
.reduce(
|
||||
|| {
|
||||
(
|
||||
Transacted::default(),
|
||||
AddressTypeToVec::<(
|
||||
TypeIndex,
|
||||
Sats,
|
||||
Option<WithAddressDataSource<AddressData>>,
|
||||
)>::default(),
|
||||
)
|
||||
},
|
||||
|(transacted, mut vecs), (other_transacted, other_vecs)| {
|
||||
vecs.merge(other_vecs);
|
||||
(transacted + other_transacted, vecs)
|
||||
},
|
||||
);
|
||||
// });
|
||||
|
||||
(sent_handle.join().unwrap(), received_handle.join().unwrap())
|
||||
(sent_handle.join().unwrap(), received_output)
|
||||
});
|
||||
|
||||
if chain_state_starting_height > height {
|
||||
@@ -904,36 +976,22 @@ impl Vecs {
|
||||
.as_mut()
|
||||
.map(|v| is_date_last_height.then(|| *v.unwrap_get_inner(dateindex)));
|
||||
|
||||
thread::scope(|scope| {
|
||||
scope.spawn(|| {
|
||||
separate_utxo_vecs
|
||||
.par_iter_mut()
|
||||
.try_for_each(|(_, v)| {
|
||||
v.compute_then_force_push_unrealized_states(
|
||||
height,
|
||||
price,
|
||||
is_date_last_height.then_some(dateindex),
|
||||
date_price,
|
||||
exit,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
scope.spawn(|| {
|
||||
separate_utxo_vecs
|
||||
.into_par_iter()
|
||||
.map(|(_, v)| v as &mut dyn DynCohortVecs).chain(
|
||||
separate_address_vecs
|
||||
.par_iter_mut()
|
||||
.try_for_each(|(_, v)| {
|
||||
v.compute_then_force_push_unrealized_states(
|
||||
height,
|
||||
price,
|
||||
is_date_last_height.then_some(dateindex),
|
||||
date_price,
|
||||
exit,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
});
|
||||
.into_par_iter()
|
||||
.map(|(_, v)| v as &mut dyn DynCohortVecs)
|
||||
)
|
||||
.try_for_each(|v| {
|
||||
v.compute_then_force_push_unrealized_states(
|
||||
height,
|
||||
price,
|
||||
is_date_last_height.then_some(dateindex),
|
||||
date_price,
|
||||
exit,
|
||||
)
|
||||
})?;
|
||||
|
||||
if height != Height::ZERO && height.unwrap_to_usize() % 10_000 == 0 {
|
||||
info!("Flushing...");
|
||||
@@ -1159,7 +1217,7 @@ impl Vecs {
|
||||
}
|
||||
}
|
||||
|
||||
impl AddressTypeToTypeIndexVec<(Sats, Option<WithAddressDataSource<AddressData>>)> {
|
||||
impl AddressTypeToVec<(TypeIndex, Sats, Option<WithAddressDataSource<AddressData>>)> {
|
||||
fn process_received(
|
||||
mut self,
|
||||
vecs: &mut address_cohorts::Vecs,
|
||||
@@ -1175,7 +1233,7 @@ impl AddressTypeToTypeIndexVec<(Sats, Option<WithAddressDataSource<AddressData>>
|
||||
) {
|
||||
self.into_typed_vec().into_iter().for_each(|(_type, vec)| {
|
||||
vec.into_iter()
|
||||
.for_each(|(type_index, (value, addressdata_opt))| {
|
||||
.for_each(|(type_index, value, addressdata_opt)| {
|
||||
let mut is_new = false;
|
||||
let mut from_any_empty = false;
|
||||
|
||||
@@ -1242,7 +1300,8 @@ impl AddressTypeToTypeIndexVec<(Sats, Option<WithAddressDataSource<AddressData>>
|
||||
}
|
||||
|
||||
impl
|
||||
AddressTypeToTypeIndexVec<(
|
||||
AddressTypeToVec<(
|
||||
TypeIndex,
|
||||
Sats,
|
||||
Option<WithAddressDataSource<AddressData>>,
|
||||
Option<Dollars>,
|
||||
@@ -1270,7 +1329,12 @@ impl
|
||||
vec.into_iter().try_for_each(
|
||||
|(
|
||||
type_index,
|
||||
(value, addressdata_opt, prev_price, blocks_old, days_old, older_than_hour),
|
||||
value,
|
||||
addressdata_opt,
|
||||
prev_price,
|
||||
blocks_old,
|
||||
days_old,
|
||||
older_than_hour,
|
||||
)| {
|
||||
let typeindex_to_addressdata = addresstype_to_typeindex_to_addressdata
|
||||
.get_mut(_type)
|
||||
|
||||
@@ -1,25 +1,11 @@
|
||||
use std::path::Path;
|
||||
|
||||
use brk_core::{Bitcoin, DateIndex, Dollars, Height, Result, Version};
|
||||
use brk_exit::Exit;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_vec::{AnyCollectableVec, AnyIterableVec, Computation, Format};
|
||||
use brk_vec::{AnyCollectableVec, AnyIterableVec};
|
||||
|
||||
use crate::vecs::{Indexes, fetched, indexes, market};
|
||||
|
||||
pub trait CohortVecs: Sized {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn forced_import(
|
||||
path: &Path,
|
||||
cohort_name: Option<&str>,
|
||||
computation: Computation,
|
||||
format: Format,
|
||||
version: Version,
|
||||
fetched: Option<&fetched::Vecs>,
|
||||
states_path: &Path,
|
||||
compute_relative_to_all: bool,
|
||||
) -> color_eyre::Result<Self>;
|
||||
|
||||
pub trait DynCohortVecs: Send + Sync {
|
||||
fn starting_height(&self) -> Height;
|
||||
|
||||
fn init(&mut self, starting_height: Height);
|
||||
@@ -39,13 +25,6 @@ pub trait CohortVecs: Sized {
|
||||
|
||||
fn safe_flush_stateful_vecs(&mut self, height: Height, exit: &Exit) -> Result<()>;
|
||||
|
||||
fn compute_from_stateful(
|
||||
&mut self,
|
||||
starting_indexes: &Indexes,
|
||||
others: &[&Self],
|
||||
exit: &Exit,
|
||||
) -> Result<()>;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn compute_rest_part1(
|
||||
&mut self,
|
||||
@@ -56,6 +35,17 @@ pub trait CohortVecs: Sized {
|
||||
exit: &Exit,
|
||||
) -> color_eyre::Result<()>;
|
||||
|
||||
fn vecs(&self) -> Vec<&dyn AnyCollectableVec>;
|
||||
}
|
||||
|
||||
pub trait CohortVecs: DynCohortVecs {
|
||||
fn compute_from_stateful(
|
||||
&mut self,
|
||||
starting_indexes: &Indexes,
|
||||
others: &[&Self],
|
||||
exit: &Exit,
|
||||
) -> Result<()>;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn compute_rest_part2(
|
||||
&mut self,
|
||||
@@ -70,6 +60,4 @@ pub trait CohortVecs: Sized {
|
||||
dateindex_to_realized_cap: Option<&impl AnyIterableVec<DateIndex, Dollars>>,
|
||||
exit: &Exit,
|
||||
) -> color_eyre::Result<()>;
|
||||
|
||||
fn vecs(&self) -> Vec<&dyn AnyCollectableVec>;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,10 @@ use crate::{
|
||||
UTXOCohortState,
|
||||
vecs::{
|
||||
Indexes, fetched, indexes, market,
|
||||
stateful::{common, r#trait::CohortVecs},
|
||||
stateful::{
|
||||
common,
|
||||
r#trait::{CohortVecs, DynCohortVecs},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -22,9 +25,9 @@ pub struct Vecs {
|
||||
inner: common::Vecs,
|
||||
}
|
||||
|
||||
impl CohortVecs for Vecs {
|
||||
impl Vecs {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn forced_import(
|
||||
pub fn forced_import(
|
||||
path: &Path,
|
||||
cohort_name: Option<&str>,
|
||||
computation: Computation,
|
||||
@@ -56,7 +59,9 @@ impl CohortVecs for Vecs {
|
||||
)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DynCohortVecs for Vecs {
|
||||
fn starting_height(&self) -> Height {
|
||||
[
|
||||
self.state.height().map_or(Height::MAX, |h| h.incremented()),
|
||||
@@ -112,19 +117,6 @@ impl CohortVecs for Vecs {
|
||||
.safe_flush_stateful_vecs(height, exit, &mut self.state)
|
||||
}
|
||||
|
||||
fn compute_from_stateful(
|
||||
&mut self,
|
||||
starting_indexes: &Indexes,
|
||||
others: &[&Self],
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.inner.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| &v.inner).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn compute_rest_part1(
|
||||
&mut self,
|
||||
@@ -138,6 +130,25 @@ impl CohortVecs for Vecs {
|
||||
.compute_rest_part1(indexer, indexes, fetched, starting_indexes, exit)
|
||||
}
|
||||
|
||||
fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
|
||||
self.inner.vecs()
|
||||
}
|
||||
}
|
||||
|
||||
impl CohortVecs for Vecs {
|
||||
fn compute_from_stateful(
|
||||
&mut self,
|
||||
starting_indexes: &Indexes,
|
||||
others: &[&Self],
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.inner.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| &v.inner).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn compute_rest_part2(
|
||||
&mut self,
|
||||
@@ -165,10 +176,6 @@ impl CohortVecs for Vecs {
|
||||
exit,
|
||||
)
|
||||
}
|
||||
|
||||
fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
|
||||
self.inner.vecs()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Vecs {
|
||||
|
||||
@@ -13,7 +13,7 @@ use rayon::prelude::*;
|
||||
|
||||
use crate::{
|
||||
states::{BlockState, Transacted},
|
||||
vecs::{Indexes, fetched},
|
||||
vecs::{Indexes, fetched, stateful::r#trait::DynCohortVecs},
|
||||
};
|
||||
|
||||
use super::{r#trait::CohortVecs, utxo_cohort};
|
||||
|
||||
@@ -28,6 +28,8 @@ use super::Vout;
|
||||
pub struct OutputIndex(u64);
|
||||
|
||||
impl OutputIndex {
|
||||
pub const ZERO: Self = Self(0);
|
||||
|
||||
pub const COINBASE: Self = Self(u64::MAX);
|
||||
|
||||
pub fn incremented(self) -> Self {
|
||||
|
||||
@@ -67,6 +67,10 @@ impl OutputType {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_not_address(&self) -> bool {
|
||||
!self.is_address()
|
||||
}
|
||||
|
||||
pub fn is_unspendable(&self) -> bool {
|
||||
!self.is_spendable()
|
||||
}
|
||||
|
||||
@@ -31,6 +31,11 @@ impl From<TypeIndex> for P2AAddressIndex {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl From<P2AAddressIndex> for TypeIndex {
|
||||
fn from(value: P2AAddressIndex) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
impl From<P2AAddressIndex> for u32 {
|
||||
fn from(value: P2AAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
|
||||
@@ -31,6 +31,11 @@ impl From<TypeIndex> for P2PK33AddressIndex {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl From<P2PK33AddressIndex> for TypeIndex {
|
||||
fn from(value: P2PK33AddressIndex) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
impl From<P2PK33AddressIndex> for u32 {
|
||||
fn from(value: P2PK33AddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
|
||||
@@ -31,6 +31,11 @@ impl From<TypeIndex> for P2PK65AddressIndex {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl From<P2PK65AddressIndex> for TypeIndex {
|
||||
fn from(value: P2PK65AddressIndex) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
impl From<P2PK65AddressIndex> for usize {
|
||||
fn from(value: P2PK65AddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
|
||||
@@ -31,6 +31,11 @@ impl From<TypeIndex> for P2PKHAddressIndex {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl From<P2PKHAddressIndex> for TypeIndex {
|
||||
fn from(value: P2PKHAddressIndex) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
impl From<P2PKHAddressIndex> for usize {
|
||||
fn from(value: P2PKHAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
|
||||
@@ -31,6 +31,11 @@ impl From<TypeIndex> for P2SHAddressIndex {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl From<P2SHAddressIndex> for TypeIndex {
|
||||
fn from(value: P2SHAddressIndex) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
impl From<P2SHAddressIndex> for u32 {
|
||||
fn from(value: P2SHAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
|
||||
@@ -31,6 +31,11 @@ impl From<TypeIndex> for P2TRAddressIndex {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl From<P2TRAddressIndex> for TypeIndex {
|
||||
fn from(value: P2TRAddressIndex) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
impl From<P2TRAddressIndex> for u32 {
|
||||
fn from(value: P2TRAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
|
||||
@@ -31,6 +31,11 @@ impl From<TypeIndex> for P2WPKHAddressIndex {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl From<P2WPKHAddressIndex> for TypeIndex {
|
||||
fn from(value: P2WPKHAddressIndex) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
impl From<P2WPKHAddressIndex> for usize {
|
||||
fn from(value: P2WPKHAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
|
||||
@@ -31,6 +31,11 @@ impl From<TypeIndex> for P2WSHAddressIndex {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl From<P2WSHAddressIndex> for TypeIndex {
|
||||
fn from(value: P2WSHAddressIndex) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
impl From<P2WSHAddressIndex> for u32 {
|
||||
fn from(value: P2WSHAddressIndex) -> Self {
|
||||
Self::from(*value)
|
||||
|
||||
@@ -17,5 +17,6 @@ brk_parser = { workspace = true }
|
||||
brk_store = { workspace = true }
|
||||
brk_vec = { workspace = true }
|
||||
color-eyre = { workspace = true }
|
||||
fjall = { workspace = true }
|
||||
log = { workspace = true }
|
||||
rayon = { workspace = true }
|
||||
|
||||
@@ -56,6 +56,9 @@ impl Indexer {
|
||||
let starting_indexes = Indexes::try_from((&mut self.vecs, &self.stores, rpc))
|
||||
.unwrap_or_else(|_report| Indexes::default());
|
||||
|
||||
// dbg!(starting_indexes);
|
||||
// panic!();
|
||||
|
||||
exit.block();
|
||||
self.stores
|
||||
.rollback_if_needed(&mut self.vecs, &starting_indexes)?;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use std::{borrow::Cow, fs, path::Path, thread};
|
||||
|
||||
use brk_core::{
|
||||
AddressBytes, AddressBytesHash, BlockHashPrefix, GroupedByAddressType, Height, OutputType,
|
||||
Result, TxIndex, TxidPrefix, TypeIndex, TypeIndexWithOutputindex, Unit, Version,
|
||||
AddressBytes, AddressBytesHash, BlockHashPrefix, GroupedByAddressType, Height, OutputIndex,
|
||||
OutputType, Result, TxIndex, TxidPrefix, TypeIndex, TypeIndexWithOutputindex, Unit, Version,
|
||||
};
|
||||
use brk_store::{AnyStore, Store};
|
||||
use brk_vec::AnyIterableVec;
|
||||
use brk_vec::{AnyIterableVec, VecIterator};
|
||||
use fjall::{PersistMode, TransactionalKeyspace};
|
||||
use rayon::prelude::*;
|
||||
|
||||
use crate::Indexes;
|
||||
@@ -14,6 +15,8 @@ use super::Vecs;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Stores {
|
||||
pub keyspace: TransactionalKeyspace,
|
||||
|
||||
pub addressbyteshash_to_typeindex: Store<AddressBytesHash, TypeIndex>,
|
||||
pub blockhashprefix_to_height: Store<BlockHashPrefix, Height>,
|
||||
pub txidprefix_to_txindex: Store<TxidPrefix, TxIndex>,
|
||||
@@ -27,9 +30,18 @@ impl Stores {
|
||||
pub fn forced_import(path: &Path, version: Version) -> color_eyre::Result<Self> {
|
||||
fs::create_dir_all(path)?;
|
||||
|
||||
let keyspace = match brk_store::open_keyspace(path) {
|
||||
Ok(keyspace) => keyspace,
|
||||
Err(_) => {
|
||||
fs::remove_dir_all(path)?;
|
||||
return Self::forced_import(path, version);
|
||||
}
|
||||
};
|
||||
|
||||
thread::scope(|scope| {
|
||||
let addressbyteshash_to_typeindex = scope.spawn(|| {
|
||||
Store::import(
|
||||
&keyspace,
|
||||
path,
|
||||
"addressbyteshash_to_typeindex",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -38,6 +50,7 @@ impl Stores {
|
||||
});
|
||||
let blockhashprefix_to_height = scope.spawn(|| {
|
||||
Store::import(
|
||||
&keyspace,
|
||||
path,
|
||||
"blockhashprefix_to_height",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -46,6 +59,7 @@ impl Stores {
|
||||
});
|
||||
let txidprefix_to_txindex = scope.spawn(|| {
|
||||
Store::import(
|
||||
&keyspace,
|
||||
path,
|
||||
"txidprefix_to_txindex",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -54,6 +68,7 @@ impl Stores {
|
||||
});
|
||||
let p2aaddressindex_with_outputindex = scope.spawn(|| {
|
||||
Store::import(
|
||||
&keyspace,
|
||||
path,
|
||||
"p2aaddressindex_with_outputindex",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -62,6 +77,7 @@ impl Stores {
|
||||
});
|
||||
let p2pk33addressindex_with_outputindex = scope.spawn(|| {
|
||||
Store::import(
|
||||
&keyspace,
|
||||
path,
|
||||
"p2pk33addressindex_with_outputindex",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -70,6 +86,7 @@ impl Stores {
|
||||
});
|
||||
let p2pk65addressindex_with_outputindex = scope.spawn(|| {
|
||||
Store::import(
|
||||
&keyspace,
|
||||
path,
|
||||
"p2pk65addressindex_with_outputindex",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -78,6 +95,7 @@ impl Stores {
|
||||
});
|
||||
let p2pkhaddressindex_with_outputindex = scope.spawn(|| {
|
||||
Store::import(
|
||||
&keyspace,
|
||||
path,
|
||||
"p2pkhaddressindex_with_outputindex",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -86,6 +104,7 @@ impl Stores {
|
||||
});
|
||||
let p2shaddressindex_with_outputindex = scope.spawn(|| {
|
||||
Store::import(
|
||||
&keyspace,
|
||||
path,
|
||||
"p2shaddressindex_with_outputindex",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -94,6 +113,7 @@ impl Stores {
|
||||
});
|
||||
let p2traddressindex_with_outputindex = scope.spawn(|| {
|
||||
Store::import(
|
||||
&keyspace,
|
||||
path,
|
||||
"p2traddressindex_with_outputindex",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -102,6 +122,7 @@ impl Stores {
|
||||
});
|
||||
let p2wpkhaddressindex_with_outputindex = scope.spawn(|| {
|
||||
Store::import(
|
||||
&keyspace,
|
||||
path,
|
||||
"p2wpkhaddressindex_with_outputindex",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -110,6 +131,7 @@ impl Stores {
|
||||
});
|
||||
let p2wshaddressindex_with_outputindex = scope.spawn(|| {
|
||||
Store::import(
|
||||
&keyspace,
|
||||
path,
|
||||
"p2wshaddressindex_with_outputindex",
|
||||
version + VERSION + Version::ZERO,
|
||||
@@ -118,6 +140,8 @@ impl Stores {
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
keyspace: keyspace.clone(),
|
||||
|
||||
addressbyteshash_to_typeindex: addressbyteshash_to_typeindex.join().unwrap()?,
|
||||
blockhashprefix_to_height: blockhashprefix_to_height.join().unwrap()?,
|
||||
txidprefix_to_txindex: txidprefix_to_txindex.join().unwrap()?,
|
||||
@@ -135,6 +159,66 @@ impl Stores {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn starting_height(&self) -> Height {
|
||||
self.as_slice()
|
||||
.into_iter()
|
||||
.map(|store| {
|
||||
let height = store.height().map(Height::incremented).unwrap_or_default();
|
||||
// dbg!((height, store.name()));
|
||||
height
|
||||
})
|
||||
.min()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn commit(&mut self, height: Height) -> Result<()> {
|
||||
self.as_mut_slice()
|
||||
.into_par_iter()
|
||||
.try_for_each(|store| store.commit(height))?;
|
||||
|
||||
self.keyspace
|
||||
.persist(PersistMode::SyncAll)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub fn rotate_memtables(&self) {
|
||||
self.as_slice()
|
||||
.into_iter()
|
||||
.for_each(|store| store.rotate_memtable());
|
||||
}
|
||||
|
||||
fn as_slice(&self) -> [&(dyn AnyStore + Send + Sync); 11] {
|
||||
[
|
||||
&self.addressbyteshash_to_typeindex,
|
||||
&self.blockhashprefix_to_height,
|
||||
&self.txidprefix_to_txindex,
|
||||
&self.addresstype_to_typeindex_with_outputindex.p2a,
|
||||
&self.addresstype_to_typeindex_with_outputindex.p2pk33,
|
||||
&self.addresstype_to_typeindex_with_outputindex.p2pk65,
|
||||
&self.addresstype_to_typeindex_with_outputindex.p2pkh,
|
||||
&self.addresstype_to_typeindex_with_outputindex.p2sh,
|
||||
&self.addresstype_to_typeindex_with_outputindex.p2tr,
|
||||
&self.addresstype_to_typeindex_with_outputindex.p2wpkh,
|
||||
&self.addresstype_to_typeindex_with_outputindex.p2wsh,
|
||||
]
|
||||
}
|
||||
|
||||
fn as_mut_slice(&mut self) -> [&mut (dyn AnyStore + Send + Sync); 11] {
|
||||
[
|
||||
&mut self.addressbyteshash_to_typeindex,
|
||||
&mut self.blockhashprefix_to_height,
|
||||
&mut self.txidprefix_to_txindex,
|
||||
&mut self.addresstype_to_typeindex_with_outputindex.p2a,
|
||||
&mut self.addresstype_to_typeindex_with_outputindex.p2pk33,
|
||||
&mut self.addresstype_to_typeindex_with_outputindex.p2pk65,
|
||||
&mut self.addresstype_to_typeindex_with_outputindex.p2pkh,
|
||||
&mut self.addresstype_to_typeindex_with_outputindex.p2sh,
|
||||
&mut self.addresstype_to_typeindex_with_outputindex.p2tr,
|
||||
&mut self.addresstype_to_typeindex_with_outputindex.p2wpkh,
|
||||
&mut self.addresstype_to_typeindex_with_outputindex.p2wsh,
|
||||
]
|
||||
}
|
||||
|
||||
pub fn rollback_if_needed(
|
||||
&mut self,
|
||||
vecs: &mut Vecs,
|
||||
@@ -372,60 +456,48 @@ impl Stores {
|
||||
self.txidprefix_to_txindex.reset()?;
|
||||
}
|
||||
|
||||
if starting_indexes.outputindex != OutputIndex::ZERO {
|
||||
let mut outputindex_to_typeindex_iter = vecs.outputindex_to_typeindex.into_iter();
|
||||
vecs.outputindex_to_outputtype
|
||||
.iter_at(starting_indexes.outputindex)
|
||||
.filter(|(_, outputtype)| outputtype.is_address())
|
||||
.for_each(|(outputindex, outputtype)| {
|
||||
let outputtype = outputtype.into_owned();
|
||||
|
||||
let typeindex = outputindex_to_typeindex_iter.unwrap_get_inner(outputindex);
|
||||
|
||||
self.addresstype_to_typeindex_with_outputindex
|
||||
.get_mut(outputtype)
|
||||
.unwrap()
|
||||
.remove(TypeIndexWithOutputindex::from((typeindex, outputindex)));
|
||||
});
|
||||
} else {
|
||||
self.addresstype_to_typeindex_with_outputindex.p2a.reset()?;
|
||||
self.addresstype_to_typeindex_with_outputindex
|
||||
.p2pk33
|
||||
.reset()?;
|
||||
self.addresstype_to_typeindex_with_outputindex
|
||||
.p2pk65
|
||||
.reset()?;
|
||||
self.addresstype_to_typeindex_with_outputindex
|
||||
.p2pkh
|
||||
.reset()?;
|
||||
self.addresstype_to_typeindex_with_outputindex
|
||||
.p2sh
|
||||
.reset()?;
|
||||
self.addresstype_to_typeindex_with_outputindex
|
||||
.p2tr
|
||||
.reset()?;
|
||||
self.addresstype_to_typeindex_with_outputindex
|
||||
.p2wpkh
|
||||
.reset()?;
|
||||
self.addresstype_to_typeindex_with_outputindex
|
||||
.p2wsh
|
||||
.reset()?;
|
||||
}
|
||||
|
||||
self.commit(starting_indexes.height.decremented().unwrap_or_default())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn starting_height(&self) -> Height {
|
||||
self.as_slice()
|
||||
.into_iter()
|
||||
.map(|store| store.height().map(Height::incremented).unwrap_or_default())
|
||||
.min()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn commit(&mut self, height: Height) -> Result<()> {
|
||||
self.as_mut_slice()
|
||||
.into_par_iter()
|
||||
.try_for_each(|store| store.commit(height))
|
||||
}
|
||||
|
||||
pub fn rotate_memtables(&self) {
|
||||
self.as_slice()
|
||||
.into_iter()
|
||||
.for_each(|store| store.rotate_memtable());
|
||||
}
|
||||
|
||||
fn as_slice(&self) -> [&(dyn AnyStore + Send + Sync); 11] {
|
||||
[
|
||||
&self.addressbyteshash_to_typeindex,
|
||||
&self.blockhashprefix_to_height,
|
||||
&self.txidprefix_to_txindex,
|
||||
&self.addresstype_to_typeindex_with_outputindex.p2a,
|
||||
&self.addresstype_to_typeindex_with_outputindex.p2pk33,
|
||||
&self.addresstype_to_typeindex_with_outputindex.p2pk65,
|
||||
&self.addresstype_to_typeindex_with_outputindex.p2pkh,
|
||||
&self.addresstype_to_typeindex_with_outputindex.p2sh,
|
||||
&self.addresstype_to_typeindex_with_outputindex.p2tr,
|
||||
&self.addresstype_to_typeindex_with_outputindex.p2wpkh,
|
||||
&self.addresstype_to_typeindex_with_outputindex.p2wsh,
|
||||
]
|
||||
}
|
||||
|
||||
fn as_mut_slice(&mut self) -> [&mut (dyn AnyStore + Send + Sync); 11] {
|
||||
[
|
||||
&mut self.addressbyteshash_to_typeindex,
|
||||
&mut self.blockhashprefix_to_height,
|
||||
&mut self.txidprefix_to_txindex,
|
||||
&mut self.addresstype_to_typeindex_with_outputindex.p2a,
|
||||
&mut self.addresstype_to_typeindex_with_outputindex.p2pk33,
|
||||
&mut self.addresstype_to_typeindex_with_outputindex.p2pk65,
|
||||
&mut self.addresstype_to_typeindex_with_outputindex.p2pkh,
|
||||
&mut self.addresstype_to_typeindex_with_outputindex.p2sh,
|
||||
&mut self.addresstype_to_typeindex_with_outputindex.p2tr,
|
||||
&mut self.addresstype_to_typeindex_with_outputindex.p2wpkh,
|
||||
&mut self.addresstype_to_typeindex_with_outputindex.p2wsh,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,16 @@ use brk_store::{AnyStore, Store};
|
||||
fn main() -> Result<()> {
|
||||
let p = Path::new("./examples/_fjall");
|
||||
|
||||
let mut store: Store<Dollars, Sats> = brk_store::Store::import(p, "n", Version::ZERO, None)?;
|
||||
let keyspace = brk_store::open_keyspace(p)?;
|
||||
|
||||
let mut store: Store<Dollars, Sats> =
|
||||
brk_store::Store::import(&keyspace, p, "n", Version::ZERO, None)?;
|
||||
|
||||
store.insert_if_needed(Dollars::from(10.0), Sats::FIFTY_BTC, Height::ZERO);
|
||||
|
||||
store.commit(Height::ZERO)?;
|
||||
|
||||
store.persist()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -42,6 +42,12 @@ const DEFAULT_BLOOM_FILTER_BITS: Option<u8> = Some(5);
|
||||
// const CHECK_COLLISIONS: bool = true;
|
||||
const MAJOR_FJALL_VERSION: Version = Version::TWO;
|
||||
|
||||
pub fn open_keyspace(path: &Path) -> fjall::Result<TransactionalKeyspace> {
|
||||
fjall::Config::new(path.join("fjall"))
|
||||
.max_write_buffer_size(32 * 1024 * 1024)
|
||||
.open_transactional()
|
||||
}
|
||||
|
||||
impl<'a, K, V> Store<K, V>
|
||||
where
|
||||
K: Debug + Clone + From<ByteView> + Ord + 'a,
|
||||
@@ -49,32 +55,20 @@ where
|
||||
ByteView: From<K> + From<&'a K> + From<V>,
|
||||
{
|
||||
pub fn import(
|
||||
path_: &Path,
|
||||
keyspace: &TransactionalKeyspace,
|
||||
path: &Path,
|
||||
name: &str,
|
||||
version: Version,
|
||||
bloom_filter_bits: Option<Option<u8>>,
|
||||
) -> Result<Self> {
|
||||
let path = path_.join(name);
|
||||
|
||||
fs::create_dir_all(&path)?;
|
||||
|
||||
let keyspace = match fjall::Config::new(path.join("fjall"))
|
||||
.max_write_buffer_size(32 * 1024 * 1024)
|
||||
.open_transactional()
|
||||
{
|
||||
Ok(keyspace) => keyspace,
|
||||
Err(_) => {
|
||||
fs::remove_dir_all(path)?;
|
||||
return Self::import(path_, name, version, bloom_filter_bits);
|
||||
}
|
||||
};
|
||||
fs::create_dir_all(path)?;
|
||||
|
||||
let (meta, partition) = StoreMeta::checked_open(
|
||||
&keyspace,
|
||||
&path.join("meta"),
|
||||
keyspace,
|
||||
&path.join(format!("meta/{name}")),
|
||||
MAJOR_FJALL_VERSION + version,
|
||||
|| {
|
||||
Self::open_partition_handle(&keyspace, bloom_filter_bits).inspect_err(|e| {
|
||||
Self::open_partition_handle(keyspace, name, bloom_filter_bits).inspect_err(|e| {
|
||||
eprintln!("{e}");
|
||||
eprintln!("Delete {path:?} and try again");
|
||||
})
|
||||
@@ -185,11 +179,12 @@ where
|
||||
|
||||
fn open_partition_handle(
|
||||
keyspace: &TransactionalKeyspace,
|
||||
name: &str,
|
||||
bloom_filter_bits: Option<Option<u8>>,
|
||||
) -> Result<TransactionalPartitionHandle> {
|
||||
keyspace
|
||||
.open_partition(
|
||||
"partition",
|
||||
name,
|
||||
PartitionCreateOptions::default()
|
||||
.bloom_filter_bits(bloom_filter_bits.unwrap_or(DEFAULT_BLOOM_FILTER_BITS))
|
||||
.max_memtable_size(8 * 1024 * 1024)
|
||||
@@ -234,8 +229,6 @@ where
|
||||
|
||||
wtx.commit()?;
|
||||
|
||||
self.keyspace.persist(PersistMode::SyncAll)?;
|
||||
|
||||
self.rtx = self.keyspace.read_tx();
|
||||
|
||||
Ok(())
|
||||
@@ -250,6 +243,7 @@ where
|
||||
{
|
||||
fn commit(&mut self, height: Height) -> Result<()> {
|
||||
if self.puts.is_empty() && self.dels.is_empty() {
|
||||
self.meta.export(height)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -259,6 +253,16 @@ where
|
||||
self.commit_(height, dels.into_iter(), puts.into_iter())
|
||||
}
|
||||
|
||||
fn persist(&self) -> Result<()> {
|
||||
self.keyspace
|
||||
.persist(PersistMode::SyncAll)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
self.name
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Result<()> {
|
||||
info!("Resetting {}...", self.name);
|
||||
|
||||
@@ -268,12 +272,11 @@ where
|
||||
|
||||
self.meta.reset();
|
||||
|
||||
let partition = Self::open_partition_handle(&self.keyspace, self.bloom_filter_bits)?;
|
||||
let partition =
|
||||
Self::open_partition_handle(&self.keyspace, self.name, self.bloom_filter_bits)?;
|
||||
|
||||
self.partition.replace(partition);
|
||||
|
||||
self.keyspace.persist(PersistMode::SyncAll)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::{
|
||||
};
|
||||
|
||||
use brk_core::{Result, Version};
|
||||
use fjall::{TransactionalKeyspace, TransactionalPartitionHandle};
|
||||
use fjall::{PersistMode, TransactionalKeyspace, TransactionalPartitionHandle};
|
||||
|
||||
use super::Height;
|
||||
|
||||
@@ -39,7 +39,7 @@ impl StoreMeta {
|
||||
fs::remove_dir_all(path)?;
|
||||
fs::create_dir(path)?;
|
||||
keyspace.delete_partition(partition)?;
|
||||
keyspace.persist(fjall::PersistMode::SyncAll)?;
|
||||
keyspace.persist(PersistMode::SyncAll)?;
|
||||
partition = open_partition_handle()?;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,12 @@ use brk_core::{Height, Result, Version};
|
||||
pub trait AnyStore {
|
||||
fn commit(&mut self, height: Height) -> Result<()>;
|
||||
|
||||
fn persist(&self) -> Result<()>;
|
||||
|
||||
fn reset(&mut self) -> Result<()>;
|
||||
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
fn rotate_memtable(&self);
|
||||
|
||||
fn height(&self) -> Option<Height>;
|
||||
|
||||
@@ -10,6 +10,10 @@ where
|
||||
I: StoredIndex,
|
||||
T: StoredType,
|
||||
{
|
||||
fn collect(&self) -> Result<Vec<T>> {
|
||||
self.collect_range(None, None)
|
||||
}
|
||||
|
||||
fn collect_range(&self, from: Option<usize>, to: Option<usize>) -> Result<Vec<T>> {
|
||||
let len = self.len();
|
||||
let from = from.unwrap_or_default();
|
||||
|
||||
@@ -36,8 +36,9 @@
|
||||
line-height: 1.5;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
tab-size: 4;
|
||||
font-family: "Geist mono", ui-monospace, SFMono-Regular, Menlo, Monaco,
|
||||
Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-family:
|
||||
"Geist mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||
"Liberation Mono", "Courier New", monospace;
|
||||
font-feature-settings: "ss03";
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
@@ -80,9 +81,9 @@
|
||||
kbd,
|
||||
samp,
|
||||
pre {
|
||||
font-family: var(--default-mono-font-family), ui-monospace,
|
||||
SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||
"Courier New", monospace;
|
||||
font-family:
|
||||
var(--default-mono-font-family), ui-monospace, SFMono-Regular, Menlo,
|
||||
Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-feature-settings: var(
|
||||
--default-mono-font-feature-settings,
|
||||
normal
|
||||
@@ -273,6 +274,8 @@
|
||||
--default-main-width: 25rem;
|
||||
|
||||
--bottom-area: 69vh;
|
||||
|
||||
--cube: 50px;
|
||||
}
|
||||
|
||||
[data-resize] {
|
||||
@@ -1365,6 +1368,46 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#explorer {
|
||||
#chain {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(var(--cube) * 1.1);
|
||||
padding: 1rem;
|
||||
|
||||
.cube {
|
||||
width: var(--cube);
|
||||
height: var(--cube);
|
||||
overflow: hidden;
|
||||
|
||||
.face {
|
||||
transform-origin: 0 0;
|
||||
position: absolute;
|
||||
width: var(--cube);
|
||||
height: var(--cube);
|
||||
}
|
||||
.front {
|
||||
background-color: var(--orange);
|
||||
transform: rotate(-30deg) skewX(-30deg)
|
||||
translate(calc(var(--cube) * 1.3), calc(var(--cube) * 1.725))
|
||||
scaleY(0.864);
|
||||
}
|
||||
.top {
|
||||
background-color: var(--yellow);
|
||||
transform: rotate(30deg) skew(-30deg)
|
||||
translate(calc(var(--cube) * 0.99), calc(var(--cube) * -0.265))
|
||||
scaleY(0.864);
|
||||
}
|
||||
.side {
|
||||
background-color: var(--amber);
|
||||
transform: rotate(30deg) skewX(30deg)
|
||||
translate(calc(var(--cube) * 0.3), calc(var(--cube) * 0.6))
|
||||
scaleY(0.864);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- ------- -->
|
||||
@@ -1678,6 +1721,7 @@
|
||||
</footer>
|
||||
</main>
|
||||
<aside id="aside">
|
||||
<div id="explorer" hidden></div>
|
||||
<div id="charts" hidden></div>
|
||||
<div id="table" hidden></div>
|
||||
<div id="simulation" hidden></div>
|
||||
|
||||
@@ -141,7 +141,7 @@ function createChartElement({
|
||||
}
|
||||
: {}),
|
||||
// ..._options,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
ichart.priceScale("right").applyOptions({
|
||||
@@ -173,7 +173,7 @@ function createChartElement({
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
signals.createEffect(index, (index) => {
|
||||
@@ -181,12 +181,12 @@ function createChartElement({
|
||||
index === /** @satisfies {MonthIndex} */ (7)
|
||||
? 1
|
||||
: index === /** @satisfies {QuarterIndex} */ (19)
|
||||
? 2
|
||||
: index === /** @satisfies {YearIndex} */ (23)
|
||||
? 6
|
||||
: index === /** @satisfies {DecadeIndex} */ (1)
|
||||
? 60
|
||||
: 0.5;
|
||||
? 2
|
||||
: index === /** @satisfies {YearIndex} */ (23)
|
||||
? 6
|
||||
: index === /** @satisfies {DecadeIndex} */ (1)
|
||||
? 60
|
||||
: 0.5;
|
||||
|
||||
ichart.applyOptions({
|
||||
timeScale: {
|
||||
@@ -209,7 +209,7 @@ function createChartElement({
|
||||
activeResources.forEach((v) => {
|
||||
v.fetch();
|
||||
});
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
if (fitContent) {
|
||||
@@ -240,7 +240,8 @@ function createChartElement({
|
||||
|
||||
const children = Array.from(parent.childNodes).filter(
|
||||
(element) =>
|
||||
/** @type {HTMLElement} */ (element).dataset.position === position
|
||||
/** @type {HTMLElement} */ (element).dataset.position ===
|
||||
position,
|
||||
);
|
||||
|
||||
if (children.length === 1) {
|
||||
@@ -262,7 +263,7 @@ function createChartElement({
|
||||
|
||||
fieldset.append(createChild(pane));
|
||||
}),
|
||||
paneIndex ? 50 : 0
|
||||
paneIndex ? 50 : 0,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -346,7 +347,7 @@ function createChartElement({
|
||||
// Or remove ?
|
||||
iseries.applyOptions({
|
||||
visible: active,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
iseries.setSeriesOrder(order);
|
||||
@@ -376,8 +377,8 @@ function createChartElement({
|
||||
const timeResource = vecsResources.getOrCreate(
|
||||
index,
|
||||
index === /** @satisfies {Height} */ (5)
|
||||
? "timestamp-fixed"
|
||||
: "timestamp"
|
||||
? "timestamp_fixed"
|
||||
: "timestamp",
|
||||
);
|
||||
timeResource.fetch();
|
||||
|
||||
@@ -497,7 +498,7 @@ function createChartElement({
|
||||
});
|
||||
|
||||
console.timeEnd(consoleTimeLabel);
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
activeResources.delete(valuesResource);
|
||||
@@ -590,7 +591,7 @@ function createChartElement({
|
||||
borderVisible: false,
|
||||
visible: defaultActive !== false,
|
||||
},
|
||||
paneIndex
|
||||
paneIndex,
|
||||
)
|
||||
);
|
||||
|
||||
@@ -646,7 +647,7 @@ function createChartElement({
|
||||
color: color(),
|
||||
...options,
|
||||
},
|
||||
paneIndex
|
||||
paneIndex,
|
||||
)
|
||||
);
|
||||
|
||||
@@ -714,7 +715,7 @@ function createChartElement({
|
||||
topFillColor2: "transparent",
|
||||
lineVisible: true,
|
||||
},
|
||||
paneIndex
|
||||
paneIndex,
|
||||
)
|
||||
);
|
||||
|
||||
@@ -877,7 +878,7 @@ function createLegend({ signals, utils }) {
|
||||
} else {
|
||||
spanColor.style.backgroundColor = tameColor(color);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1037,17 +1038,17 @@ function numberToShortUSFormat(value, digits) {
|
||||
if (modulused === 0) {
|
||||
return `${numberToUSFormat(
|
||||
value / (1_000_000 * 1_000 ** letterIndex),
|
||||
3
|
||||
3,
|
||||
)}${letter}`;
|
||||
} else if (modulused === 1) {
|
||||
return `${numberToUSFormat(
|
||||
value / (1_000_000 * 1_000 ** letterIndex),
|
||||
2
|
||||
2,
|
||||
)}${letter}`;
|
||||
} else {
|
||||
return `${numberToUSFormat(
|
||||
value / (1_000_000 * 1_000 ** letterIndex),
|
||||
1
|
||||
1,
|
||||
)}${letter}`;
|
||||
}
|
||||
}
|
||||
@@ -1097,7 +1098,7 @@ function createOklchToRGBA() {
|
||||
return rgb.map((c) =>
|
||||
Math.abs(c) > 0.0031308
|
||||
? (c < 0 ? -1 : 1) * (1.055 * Math.abs(c) ** (1 / 2.4) - 0.055)
|
||||
: 12.92 * c
|
||||
: 12.92 * c,
|
||||
);
|
||||
}
|
||||
/**
|
||||
@@ -1109,7 +1110,7 @@ function createOklchToRGBA() {
|
||||
1, 0.3963377773761749, 0.2158037573099136, 1, -0.1055613458156586,
|
||||
-0.0638541728258133, 1, -0.0894841775298119, -1.2914855480194092,
|
||||
]),
|
||||
lab
|
||||
lab,
|
||||
);
|
||||
const LMS = /** @type {[number, number, number]} */ (
|
||||
LMSg.map((val) => val ** 3)
|
||||
@@ -1120,7 +1121,7 @@ function createOklchToRGBA() {
|
||||
-0.0405757452148008, 1.112286803280317, -0.0717110580655164,
|
||||
-0.0763729366746601, -0.4214933324022432, 1.5869240198367816,
|
||||
]),
|
||||
LMS
|
||||
LMS,
|
||||
);
|
||||
}
|
||||
/**
|
||||
@@ -1133,7 +1134,7 @@ function createOklchToRGBA() {
|
||||
-0.9692436362808796, 1.8759675015077202, 0.04155505740717559,
|
||||
0.05563007969699366, -0.20397695888897652, 1.0569715142428786,
|
||||
],
|
||||
xyz
|
||||
xyz,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1156,8 +1157,8 @@ function createOklchToRGBA() {
|
||||
});
|
||||
const rgb = srgbLinear2rgb(
|
||||
xyz2rgbLinear(
|
||||
oklab2xyz(oklch2oklab(/** @type {[number, number, number]} */ (lch)))
|
||||
)
|
||||
oklab2xyz(oklch2oklab(/** @type {[number, number, number]} */ (lch))),
|
||||
),
|
||||
).map((v) => {
|
||||
return Math.max(Math.min(Math.round(v * 255), 255), 0);
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ const LINE = "line";
|
||||
const CANDLE = "candle";
|
||||
|
||||
/**
|
||||
* @typedef {"timestamp" | "date" | "week" | "diff. epoch" | "month" | "quarter" | "year" | "decade" } SerializedChartableIndex
|
||||
* @typedef {"timestamp" | "date" | "week" | "d.epoch" | "month" | "quarter" | "year" | "decade" } SerializedChartableIndex
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -47,7 +47,7 @@ export function init({
|
||||
});
|
||||
|
||||
const TIMERANGE_LS_KEY = signals.createMemo(
|
||||
() => `chart-timerange-${index()}`
|
||||
() => `chart-timerange-${index()}`,
|
||||
);
|
||||
|
||||
let firstRun = true;
|
||||
@@ -101,7 +101,7 @@ export function init({
|
||||
from.set(t.from);
|
||||
to.set(t.to);
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
elements.charts.append(fieldset);
|
||||
@@ -218,7 +218,7 @@ export function init({
|
||||
date.setUTCFullYear(
|
||||
Math.floor(date.getUTCFullYear() / 10) * 10,
|
||||
0,
|
||||
1
|
||||
1,
|
||||
);
|
||||
} else {
|
||||
throw Error("Unsupported");
|
||||
@@ -361,9 +361,9 @@ export function init({
|
||||
({ latest, hasData }) => {
|
||||
if (!series || !latest || !hasData) return;
|
||||
printLatest({ iseries: series.inner, unit: topUnit, index });
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
[
|
||||
@@ -425,7 +425,7 @@ export function init({
|
||||
blueprint.color?.() ?? blueprint.colors?.[1](),
|
||||
},
|
||||
order,
|
||||
})
|
||||
}),
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -443,13 +443,13 @@ export function init({
|
||||
paneIndex,
|
||||
options: blueprint.options,
|
||||
order,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
firstRun = false;
|
||||
@@ -469,7 +469,7 @@ function createIndexSelector({ option, vecIdToIndexes, signals, utils }) {
|
||||
"timestamp",
|
||||
"date",
|
||||
"week",
|
||||
"diff. epoch",
|
||||
"d.epoch",
|
||||
"month",
|
||||
"quarter",
|
||||
"year",
|
||||
@@ -488,7 +488,7 @@ function createIndexSelector({ option, vecIdToIndexes, signals, utils }) {
|
||||
[Object.values(o.top), Object.values(o.bottom)]
|
||||
.flat(2)
|
||||
.map((blueprint) => vecIdToIndexes[blueprint.key])
|
||||
.flat()
|
||||
.flat(),
|
||||
);
|
||||
|
||||
const serializedIndexes = [...rawIndexes].flatMap((index) => {
|
||||
@@ -515,7 +515,7 @@ function createIndexSelector({ option, vecIdToIndexes, signals, utils }) {
|
||||
fieldset.dataset.size = "sm";
|
||||
|
||||
const index = signals.createMemo(() =>
|
||||
utils.serde.chartableIndex.deserialize(selected())
|
||||
utils.serde.chartableIndex.deserialize(selected()),
|
||||
);
|
||||
|
||||
return { fieldset, index };
|
||||
|
||||
53
websites/default/scripts/explorer.js
Normal file
53
websites/default/scripts/explorer.js
Normal file
@@ -0,0 +1,53 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {Colors} args.colors
|
||||
* @param {LightweightCharts} args.lightweightCharts
|
||||
* @param {Accessor<ChartOption>} args.option
|
||||
* @param {Signals} args.signals
|
||||
* @param {Utilities} args.utils
|
||||
* @param {WebSockets} args.webSockets
|
||||
* @param {Elements} args.elements
|
||||
* @param {VecsResources} args.vecsResources
|
||||
* @param {VecIdToIndexes} args.vecIdToIndexes
|
||||
*/
|
||||
export function init({
|
||||
colors,
|
||||
elements,
|
||||
lightweightCharts,
|
||||
option,
|
||||
signals,
|
||||
utils,
|
||||
webSockets,
|
||||
vecsResources,
|
||||
vecIdToIndexes,
|
||||
}) {
|
||||
const chain = window.document.createElement("div");
|
||||
chain.id = "chain";
|
||||
elements.explorer.append(chain);
|
||||
|
||||
chain.append(createCube());
|
||||
chain.append(createCube());
|
||||
chain.append(createCube());
|
||||
chain.append(createCube());
|
||||
chain.append(createCube());
|
||||
}
|
||||
|
||||
function createCube() {
|
||||
const cubeElement = window.document.createElement("div");
|
||||
cubeElement.classList.add("cube");
|
||||
const faceFrontElement = window.document.createElement("div");
|
||||
faceFrontElement.classList.add("face");
|
||||
faceFrontElement.classList.add("front");
|
||||
cubeElement.append(faceFrontElement);
|
||||
const faceSideElement = window.document.createElement("div");
|
||||
faceSideElement.classList.add("face");
|
||||
faceSideElement.classList.add("side");
|
||||
cubeElement.append(faceSideElement);
|
||||
const faceTopElement = window.document.createElement("div");
|
||||
faceTopElement.classList.add("face");
|
||||
faceTopElement.classList.add("top");
|
||||
cubeElement.append(faceTopElement);
|
||||
return cubeElement;
|
||||
}
|
||||
@@ -64,14 +64,14 @@ function initPackages() {
|
||||
const imports = {
|
||||
async signals() {
|
||||
return import("../packages/solid-signals/wrapper.js").then(
|
||||
(d) => d.default
|
||||
(d) => d.default,
|
||||
);
|
||||
},
|
||||
async lightweightCharts() {
|
||||
return window.document.fonts.ready.then(() =>
|
||||
import("../packages/lightweight-charts/wrapper.js").then(
|
||||
(d) => d.default
|
||||
)
|
||||
(d) => d.default,
|
||||
),
|
||||
);
|
||||
},
|
||||
async leanQr() {
|
||||
@@ -79,7 +79,7 @@ function initPackages() {
|
||||
},
|
||||
async ufuzzy() {
|
||||
return import("../packages/ufuzzy/v1.0.18/script.js").then(
|
||||
({ default: d }) => d
|
||||
({ default: d }) => d,
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -587,7 +587,7 @@ function createUtils() {
|
||||
window.history.pushState(
|
||||
null,
|
||||
"",
|
||||
`${pathname}?${urlParams.toString()}`
|
||||
`${pathname}?${urlParams.toString()}`,
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
@@ -604,7 +604,7 @@ function createUtils() {
|
||||
window.history.replaceState(
|
||||
null,
|
||||
"",
|
||||
`${pathname}?${urlParams.toString()}`
|
||||
`${pathname}?${urlParams.toString()}`,
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
@@ -1177,7 +1177,7 @@ function createUtils() {
|
||||
case /** @satisfies {DecadeIndex} */ (1):
|
||||
return "decade";
|
||||
case /** @satisfies {DifficultyEpoch} */ (2):
|
||||
return "diff. epoch";
|
||||
return "d.epoch";
|
||||
// case /** @satisfies {HalvingEpoch} */ (4):
|
||||
// return "halving";
|
||||
case /** @satisfies {Height} */ (5):
|
||||
@@ -1206,7 +1206,7 @@ function createUtils() {
|
||||
return /** @satisfies {DateIndex} */ (0);
|
||||
case "week":
|
||||
return /** @satisfies {WeekIndex} */ (22);
|
||||
case "diff. epoch":
|
||||
case "d.epoch":
|
||||
return /** @satisfies {DifficultyEpoch} */ (2);
|
||||
case "month":
|
||||
return /** @satisfies {MonthIndex} */ (7);
|
||||
@@ -1248,8 +1248,8 @@ function createUtils() {
|
||||
today.getUTCDate(),
|
||||
0,
|
||||
0,
|
||||
0
|
||||
)
|
||||
0,
|
||||
),
|
||||
);
|
||||
},
|
||||
/**
|
||||
@@ -1336,7 +1336,7 @@ function createUtils() {
|
||||
*/
|
||||
function getNumberOfDaysBetweenTwoDates(oldest, youngest) {
|
||||
return Math.round(
|
||||
Math.abs((youngest.getTime() - oldest.getTime()) / date.ONE_DAY_IN_MS)
|
||||
Math.abs((youngest.getTime() - oldest.getTime()) / date.ONE_DAY_IN_MS),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1456,7 +1456,7 @@ function createUtils() {
|
||||
function genPath(index, vecId, from, to) {
|
||||
let path = `/${serde.index.serialize(index)}-to-${vecId.replaceAll(
|
||||
"_",
|
||||
"-"
|
||||
"-",
|
||||
)}?`;
|
||||
|
||||
if (from !== undefined) {
|
||||
@@ -1562,7 +1562,7 @@ function createVecsResources(signals, utils) {
|
||||
const fetchedRecord = signals.createSignal(
|
||||
/** @type {Map<string, {loading: boolean, at: Date | null, vec: Signal<T[] | null>}>} */ (
|
||||
new Map()
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -1612,7 +1612,7 @@ function createVecsResources(signals, utils) {
|
||||
index,
|
||||
id,
|
||||
from,
|
||||
to
|
||||
to,
|
||||
)
|
||||
);
|
||||
fetched.at = new Date();
|
||||
@@ -1708,6 +1708,7 @@ function getElements() {
|
||||
style: getComputedStyle(window.document.documentElement),
|
||||
charts: getElementById("charts"),
|
||||
table: getElementById("table"),
|
||||
explorer: getElementById("explorer"),
|
||||
simulation: getElementById("simulation"),
|
||||
};
|
||||
}
|
||||
@@ -1873,7 +1874,7 @@ function initWebSockets(signals, utils) {
|
||||
|
||||
window.document.addEventListener(
|
||||
"visibilitychange",
|
||||
reinitWebSocketIfDocumentNotHidden
|
||||
reinitWebSocketIfDocumentNotHidden,
|
||||
);
|
||||
|
||||
window.document.addEventListener("online", reinitWebSocket);
|
||||
@@ -1882,7 +1883,7 @@ function initWebSockets(signals, utils) {
|
||||
ws?.close();
|
||||
window.document.removeEventListener(
|
||||
"visibilitychange",
|
||||
reinitWebSocketIfDocumentNotHidden
|
||||
reinitWebSocketIfDocumentNotHidden,
|
||||
);
|
||||
window.document.removeEventListener("online", reinitWebSocket);
|
||||
live.set(false);
|
||||
@@ -1908,7 +1909,7 @@ function initWebSockets(signals, utils) {
|
||||
symbol: ["BTC/USD"],
|
||||
interval: 1440,
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1937,7 +1938,7 @@ function initWebSockets(signals, utils) {
|
||||
|
||||
/** @type {ReturnType<typeof createWebsocket<CandlestickData>>} */
|
||||
const kraken1dCandle = createWebsocket((callback) =>
|
||||
krakenCandleWebSocketCreator(callback)
|
||||
krakenCandleWebSocketCreator(callback),
|
||||
);
|
||||
|
||||
kraken1dCandle.open();
|
||||
@@ -1996,7 +1997,7 @@ function main() {
|
||||
}
|
||||
|
||||
const frame = window.document.getElementById(
|
||||
/** @type {string} */ (input.value)
|
||||
/** @type {string} */ (input.value),
|
||||
);
|
||||
|
||||
if (!frame) {
|
||||
@@ -2094,23 +2095,23 @@ function main() {
|
||||
|
||||
function initDark() {
|
||||
const preferredColorSchemeMatchMedia = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)"
|
||||
"(prefers-color-scheme: dark)",
|
||||
);
|
||||
const dark = signals.createSignal(
|
||||
preferredColorSchemeMatchMedia.matches
|
||||
preferredColorSchemeMatchMedia.matches,
|
||||
);
|
||||
preferredColorSchemeMatchMedia.addEventListener(
|
||||
"change",
|
||||
({ matches }) => {
|
||||
dark.set(matches);
|
||||
}
|
||||
},
|
||||
);
|
||||
return dark;
|
||||
}
|
||||
const dark = initDark();
|
||||
|
||||
const qrcode = signals.createSignal(
|
||||
/** @type {string | null} */ (null)
|
||||
/** @type {string | null} */ (null),
|
||||
);
|
||||
|
||||
function createLastHeightResource() {
|
||||
@@ -2121,7 +2122,7 @@ function main() {
|
||||
lastHeight.set(h);
|
||||
},
|
||||
/** @satisfies {Height} */ (5),
|
||||
"height"
|
||||
"height",
|
||||
);
|
||||
}
|
||||
fetchLastHeight();
|
||||
@@ -2165,10 +2166,10 @@ function main() {
|
||||
const owner = signals.getOwner();
|
||||
|
||||
const chartOption = signals.createSignal(
|
||||
/** @type {ChartOption | null} */ (null)
|
||||
/** @type {ChartOption | null} */ (null),
|
||||
);
|
||||
const simOption = signals.createSignal(
|
||||
/** @type {SimulationOption | null} */ (null)
|
||||
/** @type {SimulationOption | null} */ (null),
|
||||
);
|
||||
|
||||
let previousElement = /** @type {HTMLElement | undefined} */ (
|
||||
@@ -2177,6 +2178,7 @@ function main() {
|
||||
let firstTimeLoadingChart = true;
|
||||
let firstTimeLoadingTable = true;
|
||||
let firstTimeLoadingSimulation = true;
|
||||
let firstTimeLoadingExplorer = true;
|
||||
|
||||
signals.createEffect(options.selected, (option) => {
|
||||
console.log(utils.url.pathnameToSelectedId(), option.id);
|
||||
@@ -2192,6 +2194,35 @@ function main() {
|
||||
let element;
|
||||
|
||||
switch (option.kind) {
|
||||
case "explorer": {
|
||||
element = elements.explorer;
|
||||
|
||||
if (firstTimeLoadingExplorer) {
|
||||
const lightweightCharts = packages.lightweightCharts();
|
||||
import("./explorer.js").then(({ init }) =>
|
||||
lightweightCharts.then((lightweightCharts) =>
|
||||
signals.runWithOwner(owner, () =>
|
||||
init({
|
||||
colors,
|
||||
elements,
|
||||
lightweightCharts,
|
||||
option: /** @type {Accessor<ChartOption>} */ (
|
||||
chartOption
|
||||
),
|
||||
signals,
|
||||
utils,
|
||||
webSockets,
|
||||
vecsResources,
|
||||
vecIdToIndexes,
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
firstTimeLoadingExplorer = false;
|
||||
|
||||
break;
|
||||
}
|
||||
case "chart": {
|
||||
element = elements.charts;
|
||||
|
||||
@@ -2214,9 +2245,9 @@ function main() {
|
||||
webSockets,
|
||||
vecsResources,
|
||||
vecIdToIndexes,
|
||||
})
|
||||
)
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
firstTimeLoadingChart = false;
|
||||
@@ -2236,8 +2267,8 @@ function main() {
|
||||
vecsResources,
|
||||
option,
|
||||
vecIdToIndexes,
|
||||
})
|
||||
)
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
firstTimeLoadingTable = false;
|
||||
@@ -2261,9 +2292,9 @@ function main() {
|
||||
signals,
|
||||
utils,
|
||||
vecsResources,
|
||||
})
|
||||
)
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
firstTimeLoadingSimulation = false;
|
||||
@@ -2292,7 +2323,7 @@ function main() {
|
||||
createMobileSwitchEffect();
|
||||
|
||||
utils.dom.onFirstIntersection(elements.aside, () =>
|
||||
signals.runWithOwner(owner, initSelectedFrame)
|
||||
signals.runWithOwner(owner, initSelectedFrame),
|
||||
);
|
||||
}
|
||||
initSelected();
|
||||
@@ -2372,7 +2403,7 @@ function main() {
|
||||
if (indexes?.length) {
|
||||
const maxIndex = Math.min(
|
||||
(order || indexes).length - 1,
|
||||
minIndex + RESULTS_PER_PAGE - 1
|
||||
minIndex + RESULTS_PER_PAGE - 1,
|
||||
);
|
||||
|
||||
list = Array(maxIndex - minIndex + 1);
|
||||
@@ -2448,7 +2479,7 @@ function main() {
|
||||
haystack,
|
||||
needle,
|
||||
undefined,
|
||||
infoThresh
|
||||
infoThresh,
|
||||
);
|
||||
|
||||
if (!result?.[0]?.length || !result?.[1]) {
|
||||
@@ -2456,7 +2487,7 @@ function main() {
|
||||
haystack,
|
||||
needle,
|
||||
outOfOrder,
|
||||
infoThresh
|
||||
infoThresh,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2465,7 +2496,7 @@ function main() {
|
||||
haystack,
|
||||
needle,
|
||||
outOfOrder,
|
||||
infoThresh
|
||||
infoThresh,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2474,7 +2505,7 @@ function main() {
|
||||
haystack,
|
||||
needle,
|
||||
outOfOrder,
|
||||
infoThresh
|
||||
infoThresh,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2483,7 +2514,7 @@ function main() {
|
||||
haystack,
|
||||
needle,
|
||||
undefined,
|
||||
infoThresh
|
||||
infoThresh,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2492,7 +2523,7 @@ function main() {
|
||||
haystack,
|
||||
needle,
|
||||
outOfOrder,
|
||||
infoThresh
|
||||
infoThresh,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2575,7 +2606,7 @@ function main() {
|
||||
|
||||
shareDiv.hidden = false;
|
||||
});
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
initShare();
|
||||
@@ -2599,7 +2630,7 @@ function main() {
|
||||
utils.storage.write(barWidthLocalStorageKey, String(width));
|
||||
} else {
|
||||
elements.main.style.width = elements.style.getPropertyValue(
|
||||
"--default-main-width"
|
||||
"--default-main-width",
|
||||
);
|
||||
utils.storage.remove(barWidthLocalStorageKey);
|
||||
}
|
||||
@@ -2636,9 +2667,9 @@ function main() {
|
||||
window.addEventListener("mouseleave", setResizeFalse);
|
||||
}
|
||||
initDesktopResizeBar();
|
||||
})
|
||||
)
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
main();
|
||||
|
||||
@@ -46,6 +46,14 @@
|
||||
* @property {string} title
|
||||
* @property {string[]} path
|
||||
*
|
||||
* @typedef {Object} PartialExplorerOptionSpecific
|
||||
* @property {"explorer"} kind
|
||||
* @property {string} title
|
||||
*
|
||||
* @typedef {PartialOption & PartialExplorerOptionSpecific} PartialExplorerOption
|
||||
*
|
||||
* @typedef {Required<PartialExplorerOption> & ProcessedOptionAddons} ExplorerOption
|
||||
*
|
||||
* @typedef {Object} PartialChartOptionSpecific
|
||||
* @property {"chart"} [kind]
|
||||
* @property {string} title
|
||||
@@ -85,9 +93,9 @@
|
||||
*
|
||||
* @typedef {Required<PartialUrlOption> & ProcessedOptionAddons} UrlOption
|
||||
*
|
||||
* @typedef {PartialChartOption | PartialTableOption | PartialSimulationOption | PartialUrlOption} AnyPartialOption
|
||||
* @typedef {PartialExplorerOption | PartialChartOption | PartialTableOption | PartialSimulationOption | PartialUrlOption} AnyPartialOption
|
||||
*
|
||||
* @typedef {ChartOption | TableOption | SimulationOption | UrlOption} Option
|
||||
* @typedef {ExplorerOption | ChartOption | TableOption | SimulationOption | UrlOption} Option
|
||||
*
|
||||
* @typedef {Object} PartialOptionsGroup
|
||||
* @property {string} name
|
||||
@@ -105,10 +113,12 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Colors} colors
|
||||
* @param {Object} args
|
||||
* @param {Env} args.env
|
||||
* @param {Colors} args.colors
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createPartialOptions(colors) {
|
||||
function createPartialOptions({ env, colors }) {
|
||||
/**
|
||||
* @template {string} S
|
||||
* @typedef {Extract<VecId, `${S}${string}`>} StartsWith
|
||||
@@ -1505,7 +1515,7 @@ function createPartialOptions(colors) {
|
||||
key: `${fixKey(key)}realized_price`,
|
||||
name,
|
||||
color,
|
||||
})
|
||||
}),
|
||||
),
|
||||
}
|
||||
: createPriceWithRatio({
|
||||
@@ -1551,7 +1561,7 @@ function createPartialOptions(colors) {
|
||||
}),
|
||||
createBaseSeries({
|
||||
key: `cumulative_${fixKey(
|
||||
args.key
|
||||
args.key,
|
||||
)}negative_realized_loss`,
|
||||
name: "Cumulative Negative Loss",
|
||||
color: colors.red,
|
||||
@@ -1560,7 +1570,7 @@ function createPartialOptions(colors) {
|
||||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||||
type: "Baseline",
|
||||
key: `${fixKey(
|
||||
args.key
|
||||
args.key,
|
||||
)}realized_profit_relative_to_realized_cap`,
|
||||
title: "Profit",
|
||||
color: colors.green,
|
||||
@@ -1573,7 +1583,7 @@ function createPartialOptions(colors) {
|
||||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||||
type: "Baseline",
|
||||
key: `${fixKey(
|
||||
args.key
|
||||
args.key,
|
||||
)}realized_loss_relative_to_realized_cap`,
|
||||
title: "Loss",
|
||||
color: colors.red,
|
||||
@@ -1602,7 +1612,7 @@ function createPartialOptions(colors) {
|
||||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||||
type: "Baseline",
|
||||
key: `cumulative_${fixKey(
|
||||
key
|
||||
key,
|
||||
)}net_realized_profit_and_loss`,
|
||||
title: "Cumulative net",
|
||||
defaultActive: false,
|
||||
@@ -1615,7 +1625,7 @@ function createPartialOptions(colors) {
|
||||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||||
type: "Baseline",
|
||||
key: `cumulative_${fixKey(
|
||||
key
|
||||
key,
|
||||
)}net_realized_profit_and_loss_30d_change`,
|
||||
title: "cum net 30d change",
|
||||
defaultActive: false,
|
||||
@@ -1628,7 +1638,7 @@ function createPartialOptions(colors) {
|
||||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||||
type: "Baseline",
|
||||
key: `${fixKey(
|
||||
key
|
||||
key,
|
||||
)}net_realized_profit_and_loss_relative_to_realized_cap`,
|
||||
title: "Net",
|
||||
options: {
|
||||
@@ -1640,7 +1650,7 @@ function createPartialOptions(colors) {
|
||||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||||
type: "Baseline",
|
||||
key: `cumulative_${fixKey(
|
||||
key
|
||||
key,
|
||||
)}net_realized_profit_and_loss_30d_change_relative_to_realized_cap`,
|
||||
title: "cum net 30d change",
|
||||
options: {
|
||||
@@ -1652,7 +1662,7 @@ function createPartialOptions(colors) {
|
||||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||||
type: "Baseline",
|
||||
key: `cumulative_${fixKey(
|
||||
key
|
||||
key,
|
||||
)}net_realized_profit_and_loss_30d_change_relative_to_market_cap`,
|
||||
title: "cum net 30d change",
|
||||
options: {
|
||||
@@ -1760,7 +1770,7 @@ function createPartialOptions(colors) {
|
||||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||||
type: "Baseline",
|
||||
key: `${fixKey(
|
||||
key
|
||||
key,
|
||||
)}net_realized_profit_and_loss_relative_to_realized_cap`,
|
||||
title: name,
|
||||
color,
|
||||
@@ -1810,7 +1820,7 @@ function createPartialOptions(colors) {
|
||||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||||
type: "Baseline",
|
||||
key: `cumulative_${fixKey(
|
||||
key
|
||||
key,
|
||||
)}net_realized_profit_and_loss`,
|
||||
title: name,
|
||||
color,
|
||||
@@ -1830,7 +1840,7 @@ function createPartialOptions(colors) {
|
||||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||||
type: "Baseline",
|
||||
key: `cumulative_${fixKey(
|
||||
key
|
||||
key,
|
||||
)}net_realized_profit_and_loss_30d_change`,
|
||||
title: name,
|
||||
color,
|
||||
@@ -1843,7 +1853,7 @@ function createPartialOptions(colors) {
|
||||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||||
type: "Baseline",
|
||||
key: `cumulative_${fixKey(
|
||||
key
|
||||
key,
|
||||
)}net_realized_profit_and_loss_30d_change_relative_to_realized_cap`,
|
||||
title: name,
|
||||
color,
|
||||
@@ -1856,7 +1866,7 @@ function createPartialOptions(colors) {
|
||||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||||
type: "Baseline",
|
||||
key: `cumulative_${fixKey(
|
||||
key
|
||||
key,
|
||||
)}net_realized_profit_and_loss_30d_change_relative_to_market_cap`,
|
||||
title: name,
|
||||
color,
|
||||
@@ -1897,7 +1907,7 @@ function createPartialOptions(colors) {
|
||||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||||
type: "Baseline",
|
||||
key: `${fixKey(
|
||||
key
|
||||
key,
|
||||
)}adjusted_spent_output_profit_ratio`,
|
||||
title: name,
|
||||
color,
|
||||
@@ -1920,7 +1930,7 @@ function createPartialOptions(colors) {
|
||||
key: `${fixKey(key)}sell_side_risk_ratio`,
|
||||
name: useGroupName ? name : "Risk",
|
||||
color: color,
|
||||
})
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
@@ -2009,7 +2019,7 @@ function createPartialOptions(colors) {
|
||||
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
|
||||
type: "Baseline",
|
||||
key: `${fixKey(
|
||||
key
|
||||
key,
|
||||
)}net_unrealized_profit_and_loss_relative_to_market_cap`,
|
||||
title: useGroupName ? name : "Net",
|
||||
color: useGroupName ? color : undefined,
|
||||
@@ -2134,6 +2144,15 @@ function createPartialOptions(colors) {
|
||||
}
|
||||
|
||||
return [
|
||||
...(env.localhost
|
||||
? /** @type {const} */ ([
|
||||
{
|
||||
name: "Explorer",
|
||||
title: "Explorer",
|
||||
kind: "explorer",
|
||||
},
|
||||
])
|
||||
: []),
|
||||
{
|
||||
name: "Charts",
|
||||
tree: [
|
||||
@@ -2199,7 +2218,7 @@ function createPartialOptions(colors) {
|
||||
key: `${key}_sma`,
|
||||
name: key,
|
||||
color,
|
||||
})
|
||||
}),
|
||||
),
|
||||
},
|
||||
...averages.map(({ key, name, color }) =>
|
||||
@@ -2209,7 +2228,7 @@ function createPartialOptions(colors) {
|
||||
title: `${name} Market Price Moving Average`,
|
||||
legend: "average",
|
||||
color,
|
||||
})
|
||||
}),
|
||||
),
|
||||
],
|
||||
},
|
||||
@@ -2326,7 +2345,7 @@ function createPartialOptions(colors) {
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
}),
|
||||
),
|
||||
.../** @type {const} */ ([
|
||||
{ name: "2 Year", key: "2y" },
|
||||
@@ -2397,7 +2416,7 @@ function createPartialOptions(colors) {
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
}),
|
||||
),
|
||||
],
|
||||
},
|
||||
@@ -2413,7 +2432,7 @@ function createPartialOptions(colors) {
|
||||
name: `${year}`,
|
||||
color,
|
||||
defaultActive,
|
||||
})
|
||||
}),
|
||||
),
|
||||
},
|
||||
...dcaClasses.map(
|
||||
@@ -2440,7 +2459,7 @@ function createPartialOptions(colors) {
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
}),
|
||||
),
|
||||
],
|
||||
},
|
||||
@@ -2501,10 +2520,10 @@ function createPartialOptions(colors) {
|
||||
bottom: [
|
||||
...createAverageSumCumulativeMinMaxPercentilesSeries("fee"),
|
||||
...createAverageSumCumulativeMinMaxPercentilesSeries(
|
||||
"fee_in_btc"
|
||||
"fee_in_btc",
|
||||
),
|
||||
...createAverageSumCumulativeMinMaxPercentilesSeries(
|
||||
"fee_in_usd"
|
||||
"fee_in_usd",
|
||||
),
|
||||
],
|
||||
},
|
||||
@@ -3227,7 +3246,7 @@ function createPartialOptions(colors) {
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
])
|
||||
]),
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -3252,7 +3271,7 @@ function createPartialOptions(colors) {
|
||||
key,
|
||||
name,
|
||||
color,
|
||||
})
|
||||
}),
|
||||
),
|
||||
],
|
||||
},
|
||||
@@ -3277,7 +3296,7 @@ function createPartialOptions(colors) {
|
||||
color: colors.orange,
|
||||
}),
|
||||
],
|
||||
})
|
||||
}),
|
||||
),
|
||||
],
|
||||
},
|
||||
@@ -3292,7 +3311,7 @@ function createPartialOptions(colors) {
|
||||
key,
|
||||
name,
|
||||
color,
|
||||
})
|
||||
}),
|
||||
),
|
||||
},
|
||||
...cointimePrices.map(({ key, name, color, title }) =>
|
||||
@@ -3302,7 +3321,7 @@ function createPartialOptions(colors) {
|
||||
color,
|
||||
name,
|
||||
title,
|
||||
})
|
||||
}),
|
||||
),
|
||||
],
|
||||
},
|
||||
@@ -3436,7 +3455,7 @@ export function initOptions({ colors, signals, env, utils, qrcode }) {
|
||||
/** @type {Signal<Option>} */
|
||||
const selected = signals.createSignal(/** @type {any} */ (undefined));
|
||||
|
||||
const partialOptions = createPartialOptions(colors);
|
||||
const partialOptions = createPartialOptions({ env, colors });
|
||||
|
||||
/** @type {Option[]} */
|
||||
const list = [];
|
||||
@@ -3445,7 +3464,7 @@ export function initOptions({ colors, signals, env, utils, qrcode }) {
|
||||
const detailsList = [];
|
||||
|
||||
const treeElement = signals.createSignal(
|
||||
/** @type {HTMLDivElement | null} */ (null)
|
||||
/** @type {HTMLDivElement | null} */ (null),
|
||||
);
|
||||
|
||||
/** @type {string[] | undefined} */
|
||||
@@ -3557,7 +3576,7 @@ export function initOptions({ colors, signals, env, utils, qrcode }) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
null
|
||||
null,
|
||||
);
|
||||
|
||||
partialTree.forEach((anyPartial, partialIndex) => {
|
||||
@@ -3580,7 +3599,7 @@ export function initOptions({ colors, signals, env, utils, qrcode }) {
|
||||
|
||||
if ("tree" in anyPartial) {
|
||||
const folderId = utils.stringToId(
|
||||
`${(path || []).join(" ")} ${anyPartial.name} folder`
|
||||
`${(path || []).join(" ")} ${anyPartial.name} folder`,
|
||||
);
|
||||
|
||||
/** @type {Omit<OptionsGroup, keyof PartialOptionsGroup>} */
|
||||
@@ -3595,13 +3614,13 @@ export function initOptions({ colors, signals, env, utils, qrcode }) {
|
||||
const thisPath = groupAddons.id;
|
||||
|
||||
const passedDetails = signals.createSignal(
|
||||
/** @type {HTMLDivElement | HTMLDetailsElement | null} */ (null)
|
||||
/** @type {HTMLDivElement | HTMLDetailsElement | null} */ (null),
|
||||
);
|
||||
|
||||
const childOptionsCount = recursiveProcessPartialTree(
|
||||
anyPartial.tree,
|
||||
passedDetails,
|
||||
[...(path || []), thisPath]
|
||||
[...(path || []), thisPath],
|
||||
);
|
||||
|
||||
listForSum.push(childOptionsCount);
|
||||
@@ -3657,7 +3676,15 @@ export function initOptions({ colors, signals, env, utils, qrcode }) {
|
||||
/** @type {Option} */
|
||||
let option;
|
||||
|
||||
if ("kind" in anyPartial && anyPartial.kind === "table") {
|
||||
if ("kind" in anyPartial && anyPartial.kind === "explorer") {
|
||||
option = /** @satisfies {ExplorerOption} */ ({
|
||||
kind: anyPartial.kind,
|
||||
id: anyPartial.kind,
|
||||
name: anyPartial.name,
|
||||
path: path || [],
|
||||
title: anyPartial.title,
|
||||
});
|
||||
} else if ("kind" in anyPartial && anyPartial.kind === "table") {
|
||||
option = /** @satisfies {TableOption} */ ({
|
||||
kind: anyPartial.kind,
|
||||
id: anyPartial.kind,
|
||||
@@ -3733,14 +3760,17 @@ export function initOptions({ colors, signals, env, utils, qrcode }) {
|
||||
});
|
||||
|
||||
return signals.createMemo(() =>
|
||||
listForSum.reduce((acc, s) => acc + s(), 0)
|
||||
listForSum.reduce((acc, s) => acc + s(), 0),
|
||||
);
|
||||
}
|
||||
recursiveProcessPartialTree(partialOptions, treeElement);
|
||||
|
||||
function setDefaultSelectedIfNeeded() {
|
||||
if (!selected()) {
|
||||
selected.set(list[0]);
|
||||
const firstChartOption = list.find((option) => option.kind === "chart");
|
||||
if (firstChartOption) {
|
||||
selected.set(firstChartOption);
|
||||
}
|
||||
}
|
||||
}
|
||||
setDefaultSelectedIfNeeded();
|
||||
@@ -3760,7 +3790,7 @@ export function initOptions({ colors, signals, env, utils, qrcode }) {
|
||||
console.log(
|
||||
[...m.entries()]
|
||||
.filter(([_, value]) => value > 1)
|
||||
.map(([key, _]) => key)
|
||||
.map(([key, _]) => key),
|
||||
);
|
||||
|
||||
throw Error("ID duplicate");
|
||||
|
||||
Reference in New Issue
Block a user