Skip to main content

Database Storage

Once you have a processing pipeline crawling blocks, transactions, and logs, the next step is persisting that data. Nethereum provides EF Core-based storage for PostgreSQL, SQL Server, or SQLite. You can use the low-level repository layer to store data from custom processing pipelines, or use the pre-built hosted services that wire everything together as BackgroundService instances.

Packages

PackagePurpose
Nethereum.BlockchainStore.EFCoreBase DbContext, entity models, repository interfaces
Nethereum.BlockchainStore.PostgresPostgreSQL provider
Nethereum.BlockchainStore.SqlServerSQL Server provider
Nethereum.BlockchainStore.SqliteSQLite provider
Nethereum.BlockchainStorage.ProcessorsHosted services (database-agnostic)
Nethereum.BlockchainStorage.Processors.PostgresPostgreSQL hosted service registration
Nethereum.BlockchainStorage.Processors.SqlServerSQL Server hosted service registration
Nethereum.BlockchainStorage.Processors.SqliteSQLite hosted service registration

Quick Start — Hosted Services

The simplest way to index blockchain data is with the hosted service packages. They run as BackgroundService instances, automatically handling progress tracking, retry with exponential backoff, and reorg detection.

PostgreSQL

dotnet add package Nethereum.BlockchainStorage.Processors.Postgres
var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddPostgresBlockchainProcessor(
builder.Configuration,
connectionString: "Host=localhost;Database=blockchain;Username=postgres;Password=secret"
);

// Optional: index internal transactions (requires debug_traceTransaction RPC)
builder.Services.AddPostgresInternalTransactionProcessor();

var host = builder.Build();
await host.RunAsync();

SQL Server

dotnet add package Nethereum.BlockchainStorage.Processors.SqlServer
builder.Services.AddSqlServerBlockchainProcessor(
builder.Configuration,
connectionString: "Server=localhost;Database=blockchain;Trusted_Connection=true"
);
builder.Services.AddSqlServerInternalTransactionProcessor();

SQLite

dotnet add package Nethereum.BlockchainStorage.Processors.Sqlite
builder.Services.AddSqliteBlockchainProcessor(
builder.Configuration,
connectionString: "Data Source=blockchain.db"
);
builder.Services.AddSqliteInternalTransactionProcessor();

Configuration

All hosted services read from BlockchainProcessing in appsettings.json:

{
"BlockchainProcessing": {
"BlockchainUrl": "http://localhost:8545",
"Name": "My Chain",
"MinimumBlockConfirmations": 12,
"FromBlock": null,
"ToBlock": null,
"ReorgBuffer": 12,
"UseBatchReceipts": true,
"NumberOfBlocksToProcessPerRequest": 1000,
"RetryWeight": 50,
"ProcessBlockTransactionsInParallel": true,
"PostVm": false
}
}
PropertyDefaultDescription
BlockchainUrlrequiredJSON-RPC endpoint
MinimumBlockConfirmations12Blocks behind chain head before processing
FromBlocknullStarting block (if no prior progress)
ToBlocknullStop at this block (null = continuous)
ReorgBuffer0Re-check this many recent blocks for reorgs
UseBatchReceiptstrueUse eth_getBlockReceipts for efficiency
NumberOfBlocksToProcessPerRequest1000Log batch size
RetryWeight50Reduce batch size on failures
ProcessBlockTransactionsInParalleltrueParallel transaction processing
PostVmfalseInclude VM stack traces

Connection string resolution (PostgreSQL): explicit parameter > ConnectionStrings:PostgresConnection > ConnectionStrings:BlockchainDbStorage.


What Gets Stored

The hosted services persist all blockchain data into these tables:

TableContents
blocksBlock headers — number, hash, parent hash, gas, timestamp, miner
transactionsTransactions — hash, from, to, value, gas, type, EIP-1559/4844 fields
transactionlogsEvent logs — address, topics (indexed values), data
contractsDeployed contracts — address, creator, bytecode, ABI
internaltransactionsCall traces — from, to, value, type (CALL/DELEGATECALL/CREATE), depth
addresstransactionsAddress-to-transaction index for fast account lookups
blockprogressLast processed block number
chainstatesChain ID, last canonical block hash (for reorg detection)

All records include an IsCanonical flag for reorg handling. Non-canonical records are marked but not deleted, so you can query historical forks if needed.


Low-Level Repository API

If you need custom processing logic instead of the hosted services, use the repository factory directly:

builder.Services.AddPostgresBlockchainStorage(connectionString);
// This registers IBlockchainStoreRepositoryFactory and all individual repositories

Then inject and use repositories:

public class MyProcessor
{
private readonly IBlockRepository _blockRepo;
private readonly ITransactionRepository _txRepo;
private readonly ITransactionLogRepository _logRepo;
private readonly IBlockProgressRepository _progressRepo;

public MyProcessor(
IBlockRepository blockRepo,
ITransactionRepository txRepo,
ITransactionLogRepository logRepo,
IBlockProgressRepository progressRepo)
{
_blockRepo = blockRepo;
_txRepo = txRepo;
_logRepo = logRepo;
_progressRepo = progressRepo;
}

public async Task ProcessBlockAsync(BlockWithTransactions block)
{
await _blockRepo.UpsertBlockAsync(block.MapToStorageEntity());
foreach (var tx in block.Transactions)
await _txRepo.UpsertAsync(tx.MapToStorageEntity());
await _progressRepo.UpsertProgressAsync(block.Number.Value);
}
}

Repository Interfaces

InterfaceKey Methods
IBlockRepositoryUpsertBlockAsync, FindByBlockNumberAsync, GetMaxBlockNumberAsync, MarkNonCanonicalAsync
ITransactionRepositoryUpsertAsync, FindByHashAsync, MarkNonCanonicalAsync
ITransactionLogRepositoryUpsertAsync, FindByTransactionHashAndIndexAsync, MarkNonCanonicalAsync
IContractRepositoryUpsertAsync, FindByAddressAsync
IInternalTransactionRepositoryUpsertAsync, FindByTransactionHashAsync
IBlockProgressRepositoryUpsertProgressAsync, GetLastBlockNumberProcessedAsync
IChainStateRepositoryUpsertAsync, GetAsync
IReorgHandlerHandleReorgAsync(fromBlock)

Wire with BlockchainProcessing

Connect the repository factory to the block processing pipeline:

var web3 = new Web3("http://localhost:8545");
var repoFactory = serviceProvider.GetRequiredService<IBlockchainStoreRepositoryFactory>();

var processor = web3.Processing.Blocks.CreateBlockStorageProcessor(
repositoryFactory: repoFactory,
minimumBlockConfirmations: 12,
configureSteps: steps =>
{
// Add custom handlers alongside the built-in storage handlers
steps.BlockStep.AddSynchronousProcessorHandler(block =>
Console.WriteLine($"Stored block {block.Number}"));
}
);

await processor.ExecuteAsync(cancellationToken: cts.Token);

Internal Transaction Indexing

Internal transactions (call traces) are indexed by a separate hosted service that runs behind the main block processor:

// Requires debug_traceTransaction RPC support
builder.Services.AddPostgresInternalTransactionProcessor();

The service uses the callTracer to extract the full call tree for each transaction, storing:

  • Call type (CALL, DELEGATECALL, STATICCALL, CREATE, CREATE2)
  • From/to addresses, value, gas
  • Input/output data
  • Error and revert reason
  • Trace depth and index

Schema Details

Entity Column Types

Column PatternMax LengthExample Fields
Hash67 charshash, parenthash, transactionhash
Address43 charsaddressfrom, addressto, miner
BigInteger100 charsvalue, gasused, gaslimit
Unlimited texttext (Postgres) / nvarchar(max) (SQL Server)input, data, abi, code

Key Indexes

EntityUnique IndexAdditional Indexes
Block(BlockNumber, Hash)BlockNumber, Hash, ParentHash, (IsCanonical, BlockNumber)
Transaction(BlockNumber, Hash)Hash, AddressFrom, AddressTo, NewContractAddress
TransactionLog(TransactionHash, LogIndex)BlockNumber, Address, EventHash
InternalTransaction(TransactionHash, Index)BlockNumber
ContractAddress--

Next Steps

  • Token Indexing — Index ERC-20/721/1155 transfers and aggregate balances on top of the stored data
  • Explorer — Embed a blockchain explorer UI over your indexed data
  • Blockchain Processing — Review the processing pipeline and handler patterns