Skip to content

Commit

Permalink
fix: move unallocated without need for a runestone (#22)
Browse files Browse the repository at this point in the history
* fix: move unallocated without need for a runestone

* fix: block output cache
  • Loading branch information
rafaelcr authored Jul 7, 2024
1 parent de48a59 commit 69559e0
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 176 deletions.
106 changes: 53 additions & 53 deletions src/db/cache/index_cache.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use std::{collections::HashMap, num::NonZeroUsize, str::FromStr};

use bitcoin::Network;
use chainhook_sdk::{
types::bitcoin::{TxIn, TxOut},
utils::Context,
};
use bitcoin::{Network, ScriptBuf};
use chainhook_sdk::{types::bitcoin::TxIn, utils::Context};
use lru::LruCache;
use ordinals::{Cenotaph, Edict, Etching, Rune, RuneId, Runestone};
use tokio_postgres::{Client, Transaction};
Expand All @@ -26,13 +23,14 @@ use crate::{
use super::{
db_cache::DbCache,
transaction_cache::{InputRuneBalance, TransactionCache},
utils::move_tx_output_cache_to_output_cache,
transaction_location::TransactionLocation,
utils::move_block_output_cache_to_output_cache,
};

/// Holds rune data across multiple blocks for faster computations. Processes rune events as they happen during transactions and
/// generates database rows for later insertion.
pub struct IndexCache {
network: Network,
pub network: Network,
/// Number to be assigned to the next rune etching.
next_rune_number: u32,
/// LRU cache for runes.
Expand All @@ -41,9 +39,9 @@ pub struct IndexCache {
rune_total_mints_cache: LruCache<RuneId, u128>,
/// LRU cache for outputs with rune balances.
output_cache: LruCache<(String, u32), HashMap<RuneId, Vec<InputRuneBalance>>>,
/// Same as above but only for the current transaction. We use a `HashMap` instead of an LRU cache to make sure we keep all
/// outputs in memory while we index this transaction. Must be cleared every time a new transaction is processed.
tx_output_cache: HashMap<(String, u32), HashMap<RuneId, Vec<InputRuneBalance>>>,
/// Same as above but only for the current block. We use a `HashMap` instead of an LRU cache to make sure we keep all outputs
/// in memory while we index this block. Must be cleared every time a new block is processed.
block_output_cache: HashMap<(String, u32), HashMap<RuneId, Vec<InputRuneBalance>>>,
/// Holds a single transaction's rune cache. Must be cleared every time a new transaction is processed.
tx_cache: TransactionCache,
/// Keeps rows that have not yet been inserted in the DB.
Expand All @@ -60,8 +58,21 @@ impl IndexCache {
rune_cache: LruCache::new(cap),
rune_total_mints_cache: LruCache::new(cap),
output_cache: LruCache::new(cap),
tx_output_cache: HashMap::new(),
tx_cache: TransactionCache::new(network, &"".to_string(), 1, 0, &"".to_string(), 0),
block_output_cache: HashMap::new(),
tx_cache: TransactionCache::new(
TransactionLocation {
network,
block_hash: "".to_string(),
block_height: 1,
timestamp: 0,
tx_index: 0,
tx_id: "".to_string(),
},
HashMap::new(),
HashMap::new(),
None,
0,
),
db_cache: DbCache::new(),
}
}
Expand All @@ -73,69 +84,58 @@ impl IndexCache {
/// Creates a fresh transaction index cache.
pub async fn begin_transaction(
&mut self,
block_hash: &String,
block_height: u64,
tx_index: u32,
tx_id: &String,
timestamp: u32,
location: TransactionLocation,
tx_inputs: &Vec<TxIn>,
eligible_outputs: HashMap<u32, ScriptBuf>,
first_eligible_output: Option<u32>,
total_outputs: u32,
db_tx: &mut Transaction<'_>,
ctx: &Context,
) {
let input_runes = input_rune_balances_from_tx_inputs(
tx_inputs,
&self.block_output_cache,
&mut self.output_cache,
db_tx,
ctx,
)
.await;
self.tx_cache = TransactionCache::new(
self.network,
block_hash,
block_height,
tx_index,
tx_id,
timestamp,
location,
input_runes,
eligible_outputs,
first_eligible_output,
total_outputs,
);
self.tx_output_cache.clear();
}

/// Finalizes the current transaction index cache.
/// Finalizes the current transaction index cache by moving all unallocated balances to the correct output.
pub fn end_transaction(&mut self, _db_tx: &mut Transaction<'_>, ctx: &Context) {
let entries = self.tx_cache.allocate_remaining_balances(ctx);
self.add_ledger_entries_to_db_cache(&entries);
move_tx_output_cache_to_output_cache(&mut self.tx_output_cache, &mut self.output_cache);
}

pub fn end_block(&mut self) {
move_block_output_cache_to_output_cache(&mut self.block_output_cache, &mut self.output_cache);
}

pub async fn apply_runestone(
&mut self,
runestone: &Runestone,
tx_inputs: &Vec<TxIn>,
tx_outputs: &Vec<TxOut>,
db_tx: &mut Transaction<'_>,
_db_tx: &mut Transaction<'_>,
ctx: &Context,
) {
try_debug!(ctx, "{:?} {}", runestone, self.tx_cache.location);
let input_balances = input_rune_balances_from_tx_inputs(
tx_inputs,
&self.tx_output_cache,
&mut self.output_cache,
db_tx,
ctx,
)
.await;
self.tx_cache.set_input_rune_balances(input_balances, ctx);
self.tx_cache
.apply_runestone_pointer(runestone, tx_outputs, ctx);
self.tx_cache.apply_runestone_pointer(runestone, ctx);
}

pub async fn apply_cenotaph(
&mut self,
cenotaph: &Cenotaph,
tx_inputs: &Vec<TxIn>,
db_tx: &mut Transaction<'_>,
_db_tx: &mut Transaction<'_>,
ctx: &Context,
) {
try_debug!(ctx, "{:?} {}", cenotaph, self.tx_cache.location);
let input_balances = input_rune_balances_from_tx_inputs(
tx_inputs,
&self.tx_output_cache,
&mut self.output_cache,
db_tx,
ctx,
)
.await;
self.tx_cache.set_input_rune_balances(input_balances, ctx);
let entries = self.tx_cache.apply_cenotaph_input_burn(cenotaph);
self.add_ledger_entries_to_db_cache(&entries);
}
Expand Down Expand Up @@ -407,7 +407,7 @@ impl IndexCache {
address,
entry.amount.unwrap(),
));
// Add to current transaction's output cache if it's received balance.
// Add to current block's output cache if it's received balance.
let k = (entry.tx_id.clone(), entry.output.unwrap().0);
let rune_id = RuneId::from_str(entry.rune_id.as_str()).unwrap();
let balance = InputRuneBalance {
Expand All @@ -416,7 +416,7 @@ impl IndexCache {
};
let mut default = HashMap::new();
default.insert(rune_id, vec![balance.clone()]);
self.tx_output_cache
self.block_output_cache
.entry(k)
.and_modify(|i| {
i.entry(rune_id)
Expand Down
103 changes: 23 additions & 80 deletions src/db/cache/transaction_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ use std::{
vec,
};

use bitcoin::{Network, ScriptBuf};
use chainhook_sdk::{types::bitcoin::TxOut, utils::Context};
use bitcoin::ScriptBuf;
use chainhook_sdk::utils::Context;
use ordinals::{Cenotaph, Edict, Etching, Rune, RuneId, Runestone};

use crate::{
db::{cache::utils::{is_rune_mintable, new_ledger_entry}, models::{
db_ledger_entry::DbLedgerEntry, db_ledger_operation::DbLedgerOperation, db_rune::DbRune,
}},
db::{
cache::utils::{is_rune_mintable, new_ledger_entry},
models::{
db_ledger_entry::DbLedgerEntry, db_ledger_operation::DbLedgerOperation, db_rune::DbRune,
},
},
try_debug, try_info, try_warn,
};

Expand Down Expand Up @@ -38,98 +41,38 @@ pub struct TransactionCache {
input_runes: HashMap<RuneId, VecDeque<InputRuneBalance>>,
/// Non-OP_RETURN outputs in this transaction
eligible_outputs: HashMap<u32, ScriptBuf>,
/// Index of the output that should receive unallocated runes if there is no `pointer` present.
first_eligible_output: Option<u32>,
/// Total outputs contained in this transaction, including OP_RETURN outputs
total_outputs: u32,
}

impl TransactionCache {
pub fn new(
network: Network,
block_hash: &String,
block_height: u64,
tx_index: u32,
tx_id: &String,
timestamp: u32,
location: TransactionLocation,
input_runes: HashMap<RuneId, VecDeque<InputRuneBalance>>,
eligible_outputs: HashMap<u32, ScriptBuf>,
first_eligible_output: Option<u32>,
total_outputs: u32,
) -> Self {
TransactionCache {
location: TransactionLocation {
network,
block_hash: block_hash.clone(),
block_height,
tx_id: tx_id.clone(),
tx_index,
timestamp,
},
location,
next_event_index: 0,
etching: None,
pointer: None,
input_runes: HashMap::new(),
eligible_outputs: HashMap::new(),
total_outputs: 0,
input_runes,
eligible_outputs,
first_eligible_output,
total_outputs,
}
}

/// Takes this transaction's input runes and moves them to the unallocated balance for future edict allocation.
pub fn set_input_rune_balances(
&mut self,
input_runes: HashMap<RuneId, VecDeque<InputRuneBalance>>,
_ctx: &Context,
) {
#[cfg(feature = "debug")]
for (rune_id, vec) in input_runes.iter() {
for input in vec.iter() {
try_debug!(
_ctx,
"Input {} {:?} ({}) {}",
rune_id,
input.address,
input.amount,
self.location
);
}
}
self.input_runes = input_runes;
}

/// Takes the runestone's output pointer and keeps a record of eligible outputs to send runes to.
pub fn apply_runestone_pointer(
&mut self,
runestone: &Runestone,
tx_outputs: &Vec<TxOut>,
ctx: &Context,
) {
self.total_outputs = tx_outputs.len() as u32;
// Keep a record of non-OP_RETURN outputs.
let mut first_eligible_output: Option<u32> = None;
for (i, output) in tx_outputs.iter().enumerate() {
let Ok(bytes) = hex::decode(&output.script_pubkey[2..]) else {
try_warn!(
ctx,
"Unable to decode script for output {} {}",
i,
self.location
);
continue;
};
let script = ScriptBuf::from_bytes(bytes);
if !script.is_op_return() {
if first_eligible_output.is_none() {
first_eligible_output = Some(i as u32);
}
self.eligible_outputs.insert(i as u32, script);
}
}
if first_eligible_output.is_none() {
try_info!(
ctx,
"No eligible non-OP_RETURN output found, all runes will be burnt {}",
self.location
);
}
pub fn apply_runestone_pointer(&mut self, runestone: &Runestone, _ctx: &Context) {
self.pointer = if runestone.pointer.is_some() {
runestone.pointer
} else if first_eligible_output.is_some() {
first_eligible_output
} else if self.first_eligible_output.is_some() {
self.first_eligible_output
} else {
None
};
Expand Down
Loading

0 comments on commit 69559e0

Please sign in to comment.