pub mod bitcoin;
pub mod fork_scratch_pad;
pub mod stacks;
use crate::utils::{AbstractBlock, Context};
use chainhook_types::{
BitcoinBlockSignaling, BitcoinNetwork, BlockHeader, BlockIdentifier, BlockchainEvent,
StacksBlockData, StacksChainEvent, StacksNetwork, StacksNodeConfig,
};
use hiro_system_kit::slog;
use rocket::serde::json::Value as JsonValue;
use stacks::StacksBlockPool;
use std::collections::{HashMap, VecDeque};
use self::fork_scratch_pad::ForkScratchPad;
#[derive(Deserialize, Debug, Clone, Default)]
pub struct AssetClassCache {
pub symbol: String,
pub decimals: u8,
}
#[derive(Deserialize, Debug, Clone)]
pub struct PoxConfig {
pub first_burnchain_block_height: u32,
pub prepare_phase_block_length: u32,
pub reward_phase_block_length: u32,
}
impl PoxConfig {
pub fn mainnet_default() -> PoxConfig {
PoxConfig {
first_burnchain_block_height: 666050,
prepare_phase_block_length: 100,
reward_phase_block_length: 2000,
}
}
pub fn testnet_default() -> PoxConfig {
PoxConfig {
first_burnchain_block_height: 2000000,
prepare_phase_block_length: 50,
reward_phase_block_length: 1000,
}
}
pub fn devnet_default() -> PoxConfig {
Self::default()
}
}
impl Default for PoxConfig {
fn default() -> PoxConfig {
PoxConfig {
first_burnchain_block_height: 100,
prepare_phase_block_length: 4,
reward_phase_block_length: 6,
}
}
}
pub struct StacksChainContext {
asset_class_map: HashMap<String, AssetClassCache>,
pox_config: PoxConfig,
}
impl StacksChainContext {
pub fn new(network: &StacksNetwork) -> StacksChainContext {
StacksChainContext {
asset_class_map: HashMap::new(),
pox_config: match network {
StacksNetwork::Mainnet => PoxConfig::mainnet_default(),
StacksNetwork::Testnet => PoxConfig::testnet_default(),
_ => PoxConfig::devnet_default(),
},
}
}
}
pub struct BitcoinChainContext {}
impl BitcoinChainContext {
pub fn new() -> BitcoinChainContext {
BitcoinChainContext {}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct IndexerConfig {
pub bitcoin_network: BitcoinNetwork,
pub stacks_network: StacksNetwork,
pub bitcoind_rpc_url: String,
pub bitcoind_rpc_username: String,
pub bitcoind_rpc_password: String,
pub bitcoin_block_signaling: BitcoinBlockSignaling,
}
impl IndexerConfig {
pub fn get_stacks_node_config(&self) -> &StacksNodeConfig {
match self.bitcoin_block_signaling {
BitcoinBlockSignaling::Stacks(ref config) => config,
_ => unreachable!(),
}
}
}
pub struct Indexer {
pub config: IndexerConfig,
stacks_blocks_pool: StacksBlockPool,
bitcoin_blocks_pool: ForkScratchPad,
pub stacks_context: StacksChainContext,
pub bitcoin_context: BitcoinChainContext,
}
impl Indexer {
pub fn new(config: IndexerConfig) -> Indexer {
let stacks_blocks_pool = StacksBlockPool::new();
let bitcoin_blocks_pool = ForkScratchPad::new();
let stacks_context = StacksChainContext::new(&config.stacks_network);
let bitcoin_context = BitcoinChainContext::new();
Indexer {
config,
stacks_blocks_pool,
bitcoin_blocks_pool,
stacks_context,
bitcoin_context,
}
}
pub fn seed_stacks_block_pool(&mut self, blocks: Vec<StacksBlockData>, ctx: &Context) {
self.stacks_blocks_pool.seed_block_pool(blocks, ctx);
}
pub fn handle_bitcoin_header(
&mut self,
header: BlockHeader,
ctx: &Context,
) -> Result<Option<BlockchainEvent>, String> {
let event = self.bitcoin_blocks_pool.process_header(header, ctx);
event
}
pub fn standardize_stacks_marshalled_block(
&mut self,
marshalled_block: JsonValue,
ctx: &Context,
) -> Result<StacksBlockData, String> {
stacks::standardize_stacks_marshalled_block(
&self.config,
marshalled_block,
&mut self.stacks_context,
ctx,
)
}
pub fn process_stacks_block(
&mut self,
block: StacksBlockData,
ctx: &Context,
) -> Result<Option<StacksChainEvent>, String> {
self.stacks_blocks_pool.process_block(block, ctx)
}
pub fn handle_stacks_serialized_microblock_trail(
&mut self,
serialized_microblock_trail: &str,
ctx: &Context,
) -> Result<Option<StacksChainEvent>, String> {
let microblocks = stacks::standardize_stacks_serialized_microblock_trail(
&self.config,
serialized_microblock_trail,
&mut self.stacks_context,
ctx,
)?;
self.stacks_blocks_pool
.process_microblocks(microblocks, ctx)
}
pub fn handle_stacks_marshalled_microblock_trail(
&mut self,
marshalled_microblock_trail: JsonValue,
ctx: &Context,
) -> Result<Option<StacksChainEvent>, String> {
let microblocks = stacks::standardize_stacks_marshalled_microblock_trail(
&self.config,
marshalled_microblock_trail,
&mut self.stacks_context,
ctx,
)?;
self.stacks_blocks_pool
.process_microblocks(microblocks, ctx)
}
pub fn get_pox_config(&mut self) -> PoxConfig {
self.stacks_context.pox_config.clone()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ChainSegment {
pub block_ids: VecDeque<BlockIdentifier>,
}
#[derive(Clone, Debug)]
pub enum ChainSegmentIncompatibility {
OutdatedBlock,
OutdatedSegment,
BlockCollision,
ParentBlockUnknown,
AlreadyPresent,
Unknown,
BlockNotFound,
}
#[derive(Debug)]
pub struct ChainSegmentDivergence {
block_ids_to_apply: Vec<BlockIdentifier>,
block_ids_to_rollback: Vec<BlockIdentifier>,
}
impl ChainSegment {
pub fn new() -> ChainSegment {
let block_ids = VecDeque::new();
ChainSegment { block_ids }
}
fn is_empty(&self) -> bool {
self.block_ids.is_empty()
}
fn is_block_id_newer_than_segment(&self, block_identifier: &BlockIdentifier) -> bool {
if let Some(tip) = self.block_ids.front() {
return block_identifier.index > (tip.index + 1);
}
return false;
}
fn get_relative_index(&self, block_identifier: &BlockIdentifier) -> usize {
if let Some(tip) = self.block_ids.front() {
let segment_index = tip.index.saturating_sub(block_identifier.index);
return segment_index.try_into().unwrap();
}
return 0;
}
fn can_append_block(
&self,
block: &dyn AbstractBlock,
ctx: &Context,
) -> Result<(), ChainSegmentIncompatibility> {
if self.is_block_id_newer_than_segment(&block.get_identifier()) {
return Err(ChainSegmentIncompatibility::OutdatedSegment);
}
let tip = match self.block_ids.front() {
Some(tip) => tip,
None => return Ok(()),
};
ctx.try_log(|logger| {
slog::info!(logger, "Comparing {} with {}", tip, block.get_identifier())
});
if tip.index == block.get_parent_identifier().index {
match tip.hash == block.get_parent_identifier().hash {
true => return Ok(()),
false => return Err(ChainSegmentIncompatibility::ParentBlockUnknown),
}
}
if let Some(colliding_block) = self.get_block_id(&block.get_identifier(), ctx) {
match colliding_block.eq(&block.get_identifier()) {
true => return Err(ChainSegmentIncompatibility::AlreadyPresent),
false => return Err(ChainSegmentIncompatibility::BlockCollision),
}
}
Err(ChainSegmentIncompatibility::Unknown)
}
fn get_block_id(&self, block_id: &BlockIdentifier, _ctx: &Context) -> Option<&BlockIdentifier> {
match self.block_ids.get(self.get_relative_index(block_id)) {
Some(res) => Some(res),
None => None,
}
}
pub fn append_block_identifier(&mut self, block_identifier: &BlockIdentifier) {
self.block_ids.push_front(block_identifier.clone());
}
pub fn prune_confirmed_blocks(&mut self, cut_off: &BlockIdentifier) -> Vec<BlockIdentifier> {
let mut keep = vec![];
let mut prune = vec![];
for block_id in self.block_ids.drain(..) {
if block_id.index >= cut_off.index {
keep.push(block_id);
} else {
prune.push(block_id);
}
}
for block_id in keep.into_iter() {
self.block_ids.push_back(block_id);
}
prune
}
pub fn get_tip(&self) -> &BlockIdentifier {
self.block_ids.front().unwrap()
}
pub fn get_length(&self) -> u64 {
self.block_ids.len().try_into().unwrap()
}
pub fn keep_blocks_from_oldest_to_block_identifier(
&mut self,
block_identifier: &BlockIdentifier,
) -> (bool, bool) {
let mut mutated = false;
loop {
match self.block_ids.pop_front() {
Some(tip) => {
if tip.eq(&block_identifier) {
self.block_ids.push_front(tip);
break (true, mutated);
}
}
_ => break (false, mutated),
}
mutated = true;
}
}
fn try_identify_divergence(
&self,
other_segment: &ChainSegment,
allow_reset: bool,
ctx: &Context,
) -> Result<ChainSegmentDivergence, ChainSegmentIncompatibility> {
let mut common_root = None;
let mut block_ids_to_rollback = vec![];
let mut block_ids_to_apply = vec![];
for cursor_segment_1 in other_segment.block_ids.iter() {
block_ids_to_apply.clear();
for cursor_segment_2 in self.block_ids.iter() {
if cursor_segment_2.eq(cursor_segment_1) {
common_root = Some(cursor_segment_2.clone());
break;
}
block_ids_to_apply.push(cursor_segment_2.clone());
}
if common_root.is_some() {
break;
}
block_ids_to_rollback.push(cursor_segment_1.clone());
}
ctx.try_log(|logger| {
slog::debug!(logger, "Blocks to rollback: {:?}", block_ids_to_rollback)
});
ctx.try_log(|logger| slog::debug!(logger, "Blocks to apply: {:?}", block_ids_to_apply));
block_ids_to_apply.reverse();
match common_root.take() {
Some(_common_root) => Ok(ChainSegmentDivergence {
block_ids_to_rollback,
block_ids_to_apply,
}),
None if allow_reset => Ok(ChainSegmentDivergence {
block_ids_to_rollback,
block_ids_to_apply,
}),
None => Err(ChainSegmentIncompatibility::Unknown),
}
}
fn try_append_block(
&mut self,
block: &dyn AbstractBlock,
ctx: &Context,
) -> (bool, Option<ChainSegment>) {
let mut block_appended = false;
let mut fork = None;
ctx.try_log(|logger| {
slog::info!(
logger,
"Trying to append {} to {}",
block.get_identifier(),
self
)
});
match self.can_append_block(block, ctx) {
Ok(()) => {
self.append_block_identifier(&block.get_identifier());
block_appended = true;
}
Err(incompatibility) => {
ctx.try_log(|logger| {
slog::warn!(logger, "Will have to fork: {:?}", incompatibility)
});
match incompatibility {
ChainSegmentIncompatibility::BlockCollision => {
let mut new_fork = self.clone();
let (parent_found, _) = new_fork
.keep_blocks_from_oldest_to_block_identifier(
&block.get_parent_identifier(),
);
if parent_found {
ctx.try_log(|logger| slog::info!(logger, "Success"));
new_fork.append_block_identifier(&block.get_identifier());
fork = Some(new_fork);
block_appended = true;
}
}
ChainSegmentIncompatibility::OutdatedSegment => {
}
ChainSegmentIncompatibility::ParentBlockUnknown => {}
ChainSegmentIncompatibility::OutdatedBlock => {}
ChainSegmentIncompatibility::Unknown => {}
ChainSegmentIncompatibility::AlreadyPresent => {}
ChainSegmentIncompatibility::BlockNotFound => {}
}
}
}
(block_appended, fork)
}
}
impl std::fmt::Display for ChainSegment {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"Fork [{}], height = {}",
self.block_ids
.iter()
.map(|b| format!("{}", b))
.collect::<Vec<_>>()
.join(", "),
self.get_length()
)
}
}
#[cfg(test)]
pub mod tests;