An open API service indexing awesome lists of open source software.

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

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