https://github.com/psyprotocol/txindex
A reorg-tolerant, modular framework for building ordinals/transaction indexers on Bitcoin and Dogecoin
https://github.com/psyprotocol/txindex
Last synced: 8 months ago
JSON representation
A reorg-tolerant, modular framework for building ordinals/transaction indexers on Bitcoin and Dogecoin
- Host: GitHub
- URL: https://github.com/psyprotocol/txindex
- Owner: PsyProtocol
- License: other
- Created: 2024-08-21T13:31:50.000Z (almost 2 years ago)
- Default Branch: main
- Last Pushed: 2024-08-23T09:10:05.000Z (almost 2 years ago)
- Last Synced: 2025-09-06T15:42:01.102Z (9 months ago)
- Language: Rust
- Homepage:
- Size: 114 KB
- Stars: 8
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# 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:
```rust
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)]
pub struct SimpleTxCounterDB {
pub spend_count: u64,
}
impl KVQSerializable for SimpleTxCounterDB {
fn to_bytes(&self) -> anyhow::Result> {
bincode::serialize(&self).map_err(|err| anyhow::anyhow!("Error serializing SimpleTxCounterDB: {:?}", err))
}
fn from_bytes(bytes: &[u8]) -> anyhow::Result {
bincode::deserialize(bytes).map_err(|err| anyhow::anyhow!("Error deserializing SimpleTxCounterDB: {:?}", err))
}
}
impl KVQTable for SimpleTxCounterDB {
type Key = [u8; 32];
type Value = Self;
const TABLE_NAME: &'static str = "simple_tx_counter";
const TABLE_ID: u32 = 0x100;
const TABLE_TYPE: u8 = TABLE_TYPE_FUZZY_BLOCK_INDEX;
}
```
#### 2. Implement one or more indexer/worker(s)
```rust
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;
use crate::tables::tx_counter::SimpleTxCounterDB;
pub struct TxCounterWorker {
pub _kvq: PhantomData,
pub _chain: PhantomData,
}
impl TxCounterWorker {
fn process_tx(
db: &mut IndexedBlockDBStore>,
_q: Arc,
_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::(&hash)?
.or(Some(SimpleTxCounterDB { spend_count: 0 }))
.unwrap();
db.put::(
&hash,
&SimpleTxCounterDB {
spend_count: ctr.spend_count + 1,
},
)?;
}
Ok(())
}
}
impl TxIndexWorker
for TxCounterWorker
{
fn process_block(
db: &mut IndexedBlockDBStore>,
q: Arc,
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/)
```rust
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;
use crate::tables::tx_counter::SimpleTxCounterDB;
pub struct TxCounterAPI {
_chain: std::marker::PhantomData,
}
impl TxCounterAPI {
fn handle_get_request_json(
network: Network,
uri: String,
_chain: Arc,
indexer_db: IndexedBlockDBStoreReader,
) -> anyhow::Result> {
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::(&sh)?
.or(Some(SimpleTxCounterDB { spend_count: 0 }))
.unwrap();
Ok(serde_json::to_vec(&db)?)
}
}
impl TxIndexAPIHandler for TxCounterAPI {
const PATH_SLUG: &'static str = "/indexer/tx_counter/";
fn handle_get_request(
network: Network,
uri: String,
chain: std::sync::Arc,
indexer_db: IndexedBlockDBStoreReader,
) -> 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:
```rust
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;
pub mod tx_counter;
pub struct ExampleRootWorker {}
impl TxIndexWorker for ExampleRootWorker {
fn process_block(
db: &mut IndexedBlockDBStore>,
q: Arc,
block_number: u64,
block: &Block,
) -> anyhow::Result<()> {
TxCounterWorker::::process_block(db, q, block_number, block)?;
Ok(())
}
}
```
Root API handler
```rust
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}};
pub mod tx_counter;
#[derive(Clone, Debug, Copy)]
pub struct ExampleRESTHandler {
}
impl TxIndexRESTHandler for ExampleRESTHandler {
fn handle_request(
_method: Method,
uri: hyper::Uri,
_body: hyper::body::Bytes,
q: Arc,
config: Arc,
) -> Result, HttpError> {
if uri.path().starts_with(TxCounterAPI::::PATH_SLUG){
Ok(TxCounterAPI::::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 🎉
```rust
fn main() {
start_txindex_server::();
}
```
### License
Copyright 2024 QED, MIT