Skip to content

Repository files navigation

txindex

A modular, reorg/fork-tolerant framework for writing composable transaction/ordinal indexers on Bitcoin and Dogecoin.

WARNING

This software is still in development, please DO NOT use in production yet.

Motivation

Previous ordinal/transaction indexers have been plagued issues when chain forks occur. txindex seeks to solve this by abstracting the data flow so it is guaranteed to be fork-safe, and to allow roll backs to any block.

With txindex, developers only have to worry about implementing their indexer's core logic!

In addition, we want to be able to consume indexers/APIs as modular rust packages so one server can support multiple indexers at once, so txindex allows you to compose/add new indexers whenever you like.

Usage

1. Implement the database tables you need:

use kvq::traits::KVQSerializable;use serde::{Deserialize,Serialize};use txindex_common::db::table::core::{KVQTable,TABLE_TYPE_FUZZY_BLOCK_INDEX};#[derive(Clone,Debug,Serialize,Deserialize,PartialEq,PartialOrd)]pubstructSimpleTxCounterDB{pubspend_count:u64,}implKVQSerializableforSimpleTxCounterDB{fnto_bytes(&self) -> anyhow::Result<Vec<u8>>{
bincode::serialize(&self).map_err(|err| anyhow::anyhow!("Error serializing SimpleTxCounterDB: {:?}", err))}fnfrom_bytes(bytes:&[u8]) -> anyhow::Result<Self>{
bincode::deserialize(bytes).map_err(|err| anyhow::anyhow!("Error deserializing SimpleTxCounterDB: {:?}", err))}}implKVQTableforSimpleTxCounterDB{typeKey = [u8;32];typeValue = Self;constTABLE_NAME:&'staticstr = "simple_tx_counter";constTABLE_ID:u32 = 0x100;constTABLE_TYPE:u8 = TABLE_TYPE_FUZZY_BLOCK_INDEX;}

2. Implement one or more indexer/worker(s)

use std::{marker::PhantomData, sync::Arc};use bitcoin::{Block,Transaction};use kvq::{cache::KVQBinaryStoreCached, traits::KVQBinaryStoreImmutable};use txindex_common::{
db::{chain::TxIndexChainAPI, indexed_block_db::IndexedBlockDBStore},
worker::traits::TxIndexWorker,};use txindex_server::daemon::schema::compute_script_hash;usecrate::tables::tx_counter::SimpleTxCounterDB;pubstructTxCounterWorker<KVQ:KVQBinaryStoreImmutable,T:TxIndexChainAPI>{pub_kvq:PhantomData<KVQ>,pub_chain:PhantomData<T>,}impl<KVQ:KVQBinaryStoreImmutable,T:TxIndexChainAPI>TxCounterWorker<KVQ,T>{fnprocess_tx(db:&mutIndexedBlockDBStore<KVQBinaryStoreCached<KVQ>>,_q:Arc<T>,_block_number:u64,_block:&Block,tx:&Transaction,) -> anyhow::Result<()>{for input in tx.output.iter(){let hash = compute_script_hash(&input.script_pubkey);let ctr = db
.get::<SimpleTxCounterDB>(&hash)?
.or(Some(SimpleTxCounterDB{spend_count:0})).unwrap();
db.put::<SimpleTxCounterDB>(&hash,&SimpleTxCounterDB{spend_count: ctr.spend_count + 1,},)?;}Ok(())}}impl<KVQ:KVQBinaryStoreImmutable,T:TxIndexChainAPI>TxIndexWorker<KVQ,T>forTxCounterWorker<KVQ,T>{fnprocess_block(db:&mutIndexedBlockDBStore<KVQBinaryStoreCached<KVQ>>,q:Arc<T>,block_number:u64,block:&Block,) -> anyhow::Result<()>{for tx in block.txdata.iter(){Self::process_tx(db, q.clone(), block_number, block, tx)?;}Ok(())}}

3. Implement any REST APIs you want to expose (with prefix /indexer/)

use std::sync::Arc;use txindex_common::{
api::{response::TxIndexAPIResponse, traits::TxIndexAPIHandler},
chain::Network,
db::{
chain::TxIndexChainAPI, indexed_block_db::IndexedBlockDBStoreReader, kvstore::BaseKVQStore,},};use txindex_server::api::chain::to_scripthash;usecrate::tables::tx_counter::SimpleTxCounterDB;pubstructTxCounterAPI<T:TxIndexChainAPI>{_chain: std::marker::PhantomData<T>,}impl<T:TxIndexChainAPI>TxCounterAPI<T>{fnhandle_get_request_json(network:Network,uri:String,_chain:Arc<T>,indexer_db:IndexedBlockDBStoreReader<BaseKVQStore>,) -> anyhow::Result<Vec<u8>>{let address_str = uri.split('/').last().unwrap();let sh = to_scripthash("address", address_str, network).map_err(|_| anyhow::anyhow!("invalid address"))?;let db = indexer_db
.get::<SimpleTxCounterDB>(&sh)?
.or(Some(SimpleTxCounterDB{spend_count:0})).unwrap();Ok(serde_json::to_vec(&db)?)}}impl<T:TxIndexChainAPI>TxIndexAPIHandler<T>forTxCounterAPI<T>{constPATH_SLUG:&'staticstr = "/indexer/tx_counter/";fnhandle_get_request(network:Network,uri:String,chain: std::sync::Arc<T>,indexer_db:IndexedBlockDBStoreReader<BaseKVQStore>,) -> TxIndexAPIResponse{Self::json_response(Self::handle_get_request_json(
network, uri, chain, indexer_db,))}}

4. Setup your root indexer and API handler

Root Indexer/Worker:

use std::sync::Arc;use bitcoin::Block;use kvq::cache::KVQBinaryStoreCached;use tx_counter::TxCounterWorker;use txindex_common::{
db::{indexed_block_db::IndexedBlockDBStore, kvstore::BaseKVQStore},
worker::traits::TxIndexWorker,};use txindex_server::daemon::schema::ChainQuery;pubmod tx_counter;pubstructExampleRootWorker{}implTxIndexWorker<BaseKVQStore,ChainQuery>forExampleRootWorker{fnprocess_block(db:&mutIndexedBlockDBStore<KVQBinaryStoreCached<BaseKVQStore>>,q:Arc<ChainQuery>,block_number:u64,block:&Block,) -> anyhow::Result<()>{TxCounterWorker::<BaseKVQStore,ChainQuery>::process_block(db, q, block_number, block)?;Ok(())}}

Root API handler

use std::sync::Arc;use hyper::{Method,Response};use tx_counter::TxCounterAPI;use txindex_common::{api::traits::TxIndexAPIHandler, config::Config, db::indexed_block_db::IndexedBlockDBStoreReader};use txindex_server::{api::{core::HttpError, traits::{BoxBody,TxIndexRESTHandler},TxIndexAPIResponseHelper}, daemon::{query::Query, schema::ChainQuery}};pubmod tx_counter;#[derive(Clone,Debug,Copy)]pubstructExampleRESTHandler{}implTxIndexRESTHandlerforExampleRESTHandler{fnhandle_request(_method:Method,uri: hyper::Uri,_body: hyper::body::Bytes,q:Arc<Query>,config:Arc<Config>,) -> Result<Response<BoxBody>,HttpError>{if uri.path().starts_with(TxCounterAPI::<ChainQuery>::PATH_SLUG){Ok(TxCounterAPI::<ChainQuery>::handle_get_request(config.network_type, uri.to_string(), q.get_chain_query(),IndexedBlockDBStoreReader{store: q.get_kvq_db().clone(),}).into_response())}else{Err(HttpError::not_found("not found".to_string()))}}}

5. Call start_txindex_server 🎉

fnmain(){start_txindex_server::<ExampleRESTHandler,ExampleRootWorker>();}

License

Copyright 2024 QED, MIT

About

A reorg-tolerant, modular framework for building ordinals/transaction indexers on Bitcoin and Dogecoin

Resources

Stars

15 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages