Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rune index #2491

Merged
merged 67 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
eb957b1
Add rune index
casey Sep 29, 2023
06fb638
Add separate RuneEntry struct
casey Oct 2, 2023
092bb75
Placate clippy
casey Oct 2, 2023
fad2525
Add more tests for Runestone deciphering
casey Oct 2, 2023
6c9e304
Merge remote-tracking branch 'upstream/master' into runes
casey Oct 2, 2023
8b5d472
Sort tables
casey Oct 2, 2023
8d9356a
sort
casey Oct 2, 2023
84835c5
Refactor
casey Oct 2, 2023
5217810
Sort
casey Oct 2, 2023
45fc01e
Sort
casey Oct 2, 2023
3a6de31
Sort
casey Oct 2, 2023
2012cac
Options
casey Oct 2, 2023
800b8c8
Index only has runes index if flag is passed and on mainnet
casey Oct 2, 2023
ba290df
Add some tests
casey Oct 2, 2023
58077cf
Add first tests
casey Oct 2, 2023
4c98cb9
Add more tests
casey Oct 2, 2023
f5cc72f
Add tests
casey Oct 2, 2023
bc78603
Add more tests
casey Oct 2, 2023
f2919e1
Add more tests
casey Oct 2, 2023
b1bd908
Add scary comment
casey Oct 2, 2023
32efc2b
Relocate unused import
casey Oct 2, 2023
bc9bb9a
Add test for /runes page
casey Oct 2, 2023
948db5f
Placate clippy
casey Oct 2, 2023
9de313f
Fix tests
casey Oct 2, 2023
3c003ba
Only allow runes in block corresponding sat has been mined
casey Oct 3, 2023
2f07a13
Placate clippy
casey Oct 3, 2023
b1e2165
Merge branch 'master' into runes
casey Oct 3, 2023
e1d0f9d
Add RuneId
casey Oct 3, 2023
f3569a2
Add rarity
casey Oct 3, 2023
aab2ca9
Add test
casey Oct 3, 2023
6524f18
Fix rarity
casey Oct 3, 2023
571460e
Add and use RuneId
casey Oct 3, 2023
03df9f4
Fix errors
casey Oct 3, 2023
13beede
decimals -> divisibility
casey Oct 3, 2023
ab10e84
Default decimals to 18
casey Oct 3, 2023
28061fc
Merge remote-tracking branch 'upstream/master' into runes
casey Oct 5, 2023
242bb24
Improve flag name
casey Oct 5, 2023
ad9f547
Move tests
casey Oct 5, 2023
490596a
Attempt to fix test
casey Oct 5, 2023
0f6d15b
Test runeid conversion
casey Oct 5, 2023
d244695
decimals -> divisibility
casey Oct 5, 2023
70dce2d
Add RuneEntry load and store test
casey Oct 5, 2023
c2a7367
directive -> edict
casey Oct 5, 2023
4a8395e
Add comment
casey Oct 5, 2023
9559831
Fix test
casey Oct 5, 2023
6031244
Fix
casey Oct 5, 2023
af8f597
Fix test names
casey Oct 5, 2023
a4b7434
Add the rest of the tests
casey Oct 6, 2023
ba0bb0e
Merge remote-tracking branch 'upstream/master' into runes
casey Oct 6, 2023
44223db
Return error if rune is out of range
casey Oct 6, 2023
faf43f0
Add rune page
casey Oct 6, 2023
9aa0d5e
Use literal text
casey Oct 6, 2023
df666f2
Add id to rune page
casey Oct 6, 2023
88c70f9
Add rune ID entry test
casey Oct 8, 2023
e8f4b36
Merge remote-tracking branch 'upstream/master' into runes
casey Oct 9, 2023
e9c3826
Add rune_id_entry and placate clippy
casey Oct 9, 2023
596ac84
Place id 0 edict first in test
casey Oct 9, 2023
6f1959f
Sort rune entry tuple
casey Oct 9, 2023
aa22f76
Fix test
casey Oct 9, 2023
281cfe5
Make rune id entry test more comprehensive
casey Oct 9, 2023
406e92c
Use tuple for rune id
casey Oct 9, 2023
2cf68de
Change default divisibility to 0
casey Oct 9, 2023
51a7d95
Fix
casey Oct 9, 2023
b20180d
tweak
casey Oct 9, 2023
571d095
Add new tests
casey Oct 9, 2023
6cf8121
tweak
casey Oct 9, 2023
1959f8b
tweak
casey Oct 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 97 additions & 96 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ use {
self::{
entry::{
BlockHashValue, Entry, InscriptionEntry, InscriptionEntryValue, InscriptionIdValue,
OutPointValue, SatPointValue, SatRange,
OutPointValue, RuneEntryValue, RuneIdValue, SatPointValue, SatRange,
},
reorg::*,
runes::{Rune, RuneId},
updater::Updater,
},
super::*,
Expand All @@ -23,13 +24,18 @@ use {
std::io::{BufWriter, Read, Write},
};

pub(crate) use self::entry::RuneEntry;

mod entry;
mod fetcher;
mod reorg;
mod rtx;
mod updater;

const SCHEMA_VERSION: u64 = 7;
#[cfg(test)]
pub(crate) mod testing;

const SCHEMA_VERSION: u64 = 8;

macro_rules! define_table {
($name:ident, $key:ty, $value:ty) => {
Expand All @@ -52,8 +58,11 @@ define_table! { HEIGHT_TO_LAST_SEQUENCE_NUMBER, u64, u64 }
define_table! { INSCRIPTION_ID_TO_INSCRIPTION_ENTRY, &InscriptionIdValue, InscriptionEntryValue }
define_table! { INSCRIPTION_ID_TO_SATPOINT, &InscriptionIdValue, &SatPointValue }
define_table! { INSCRIPTION_NUMBER_TO_INSCRIPTION_ID, i64, &InscriptionIdValue }
define_table! { OUTPOINT_TO_RUNE_BALANCES, &OutPointValue, &[u8] }
define_table! { OUTPOINT_TO_SAT_RANGES, &OutPointValue, &[u8] }
define_table! { OUTPOINT_TO_VALUE, &OutPointValue, u64}
define_table! { RUNE_ID_TO_RUNE_ENTRY, RuneIdValue, RuneEntryValue }
define_table! { RUNE_TO_RUNE_ID, u128, RuneIdValue }
define_table! { SAT_TO_SATPOINT, u64, &SatPointValue }
define_table! { SEQUENCE_NUMBER_TO_INSCRIPTION_ID, u64, &InscriptionIdValue }
define_table! { STATISTIC_TO_COUNT, u64, u64 }
Expand Down Expand Up @@ -252,6 +261,12 @@ impl Index {
tx.open_table(STATISTIC_TO_COUNT)?
.insert(&Statistic::Schema.key(), &SCHEMA_VERSION)?;

if options.index_runes() {
tx.open_table(OUTPOINT_TO_RUNE_BALANCES)?;
tx.open_table(RUNE_ID_TO_RUNE_ENTRY)?;
tx.open_table(RUNE_TO_RUNE_ID)?;
}

if options.index_sats {
tx.open_table(OUTPOINT_TO_SAT_RANGES)?
.insert(&OutPoint::null().store(), [].as_slice())?;
Expand Down Expand Up @@ -343,6 +358,14 @@ impl Index {
.collect()
}

pub(crate) fn has_rune_index(&self) -> Result<bool> {
match self.begin_read()?.0.open_table(RUNE_ID_TO_RUNE_ENTRY) {
Ok(_) => Ok(true),
Err(redb::TableError::TableDoesNotExist(_)) => Ok(false),
Err(err) => Err(err.into()),
}
}

pub(crate) fn has_sat_index(&self) -> Result<bool> {
match self.begin_read()?.0.open_table(OUTPOINT_TO_SAT_RANGES) {
Ok(_) => Ok(true),
Expand Down Expand Up @@ -613,6 +636,77 @@ impl Index {
}
}

pub(crate) fn rune(&self, rune: Rune) -> Result<Option<(RuneId, RuneEntry)>> {
if self.has_rune_index()? {
let rtx = self.database.begin_read()?;

let entry = match rtx.open_table(RUNE_TO_RUNE_ID)?.get(rune.0)? {
Some(id) => rtx
.open_table(RUNE_ID_TO_RUNE_ENTRY)?
.get(id.value())?
.map(|entry| (RuneId::load(id.value()), RuneEntry::load(entry.value()))),
None => None,
};

Ok(entry)
} else {
Ok(None)
}
}

pub(crate) fn runes(&self) -> Result<Option<Vec<(RuneId, RuneEntry)>>> {
if self.has_rune_index()? {
let mut entries = Vec::new();

for result in self
.database
.begin_read()?
.open_table(RUNE_ID_TO_RUNE_ENTRY)?
.iter()?
{
let (id, entry) = result?;
entries.push((RuneId::load(id.value()), RuneEntry::load(entry.value())));
}

Ok(Some(entries))
} else {
Ok(None)
}
}

#[cfg(test)]
pub(crate) fn rune_balances(&self) -> Vec<(OutPoint, Vec<(RuneId, u128)>)> {
let mut result = Vec::new();

for entry in self
.database
.begin_read()
.unwrap()
.open_table(OUTPOINT_TO_RUNE_BALANCES)
.unwrap()
.iter()
.unwrap()
{
let (outpoint, balances_buffer) = entry.unwrap();
let outpoint = OutPoint::load(*outpoint.value());
let balances_buffer = balances_buffer.value();

let mut balances = Vec::new();
let mut i = 0;
while i < balances_buffer.len() {
let (id, length) = runes::varint::decode(&balances_buffer[i..]).unwrap();
i += length;
let (balance, length) = runes::varint::decode(&balances_buffer[i..]).unwrap();
i += length;
balances.push((RuneId::try_from(id).unwrap(), balance));
}

result.push((outpoint, balances));
}

result
}

pub(crate) fn block_header(&self, hash: BlockHash) -> Result<Option<Header>> {
self.client.get_block_header(&hash).into_option()
}
Expand Down Expand Up @@ -1305,103 +1399,10 @@ impl Index {
mod tests {
use {
super::*,
crate::index::testing::Context,
bitcoin::secp256k1::rand::{self, RngCore},
};

struct ContextBuilder {
args: Vec<OsString>,
tempdir: Option<TempDir>,
}

impl ContextBuilder {
fn build(self) -> Context {
self.try_build().unwrap()
}

fn try_build(self) -> Result<Context> {
let rpc_server = test_bitcoincore_rpc::builder()
.network(Network::Regtest)
.build();

let tempdir = self.tempdir.unwrap_or_else(|| TempDir::new().unwrap());
let cookie_file = tempdir.path().join("cookie");
fs::write(&cookie_file, "username:password").unwrap();

let command: Vec<OsString> = vec![
"ord".into(),
"--rpc-url".into(),
rpc_server.url().into(),
"--data-dir".into(),
tempdir.path().into(),
"--cookie-file".into(),
cookie_file.into(),
"--regtest".into(),
];

let options = Options::try_parse_from(command.into_iter().chain(self.args)).unwrap();
let index = Index::open(&options)?;
index.update().unwrap();

Ok(Context {
options,
rpc_server,
tempdir,
index,
})
}

fn arg(mut self, arg: impl Into<OsString>) -> Self {
self.args.push(arg.into());
self
}

fn args<T: Into<OsString>, I: IntoIterator<Item = T>>(mut self, args: I) -> Self {
self.args.extend(args.into_iter().map(|arg| arg.into()));
self
}

fn tempdir(mut self, tempdir: TempDir) -> Self {
self.tempdir = Some(tempdir);
self
}
}

struct Context {
options: Options,
rpc_server: test_bitcoincore_rpc::Handle,
#[allow(unused)]
tempdir: TempDir,
index: Index,
}

impl Context {
fn builder() -> ContextBuilder {
ContextBuilder {
args: Vec::new(),
tempdir: None,
}
}

fn mine_blocks(&self, n: u64) -> Vec<Block> {
let blocks = self.rpc_server.mine_blocks(n);
self.index.update().unwrap();
blocks
}

fn mine_blocks_with_subsidy(&self, n: u64, subsidy: u64) -> Vec<Block> {
let blocks = self.rpc_server.mine_blocks_with_subsidy(n, subsidy);
self.index.update().unwrap();
blocks
}

fn configurations() -> Vec<Context> {
vec![
Context::builder().build(),
Context::builder().arg("--index-sats").build(),
]
}
}

#[test]
fn height_limit() {
{
Expand Down
80 changes: 80 additions & 0 deletions src/index/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,52 @@ impl Entry for BlockHash {
}
}

#[derive(Debug, PartialEq, Copy, Clone)]
pub(crate) struct RuneEntry {
pub(crate) divisibility: u128,
pub(crate) rarity: Rarity,
pub(crate) rune: Rune,
pub(crate) supply: u128,
}

pub(super) type RuneEntryValue = (u128, u8, u128, u128);

impl Entry for RuneEntry {
type Value = RuneEntryValue;

fn load((divisibility, rarity, rune, supply): RuneEntryValue) -> Self {
Self {
divisibility,
rarity: Rarity::try_from(rarity).unwrap(),
rune: Rune(rune),
supply,
}
}

fn store(self) -> Self::Value {
(
self.divisibility,
self.rarity.into(),
self.rune.0,
self.supply,
)
}
}

pub(super) type RuneIdValue = (u32, u16);

impl Entry for RuneId {
type Value = RuneIdValue;

fn load((height, index): Self::Value) -> Self {
Self { height, index }
}

fn store(self) -> Self::Value {
(self.height, self.index)
}
}

#[derive(Debug)]
pub(crate) struct InscriptionEntry {
pub(crate) fee: u64,
Expand Down Expand Up @@ -317,4 +363,38 @@ mod tests {
inscription_id
);
}

#[test]
fn rune_entry() {
let rune_entry = RuneEntry {
divisibility: 1,
rarity: Rarity::Rare,
rune: Rune(3),
supply: 4,
};

assert_eq!(rune_entry.store(), (1, 2, 3, 4));

assert_eq!(RuneEntry::load((1, 2, 3, 4)), rune_entry);
}

#[test]
fn rune_id_entry() {
assert_eq!(
RuneId {
height: 1,
index: 2,
}
.store(),
(1, 2),
);

assert_eq!(
RuneId {
height: 1,
index: 2,
},
RuneId::load((1, 2)),
);
}
}
Loading