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();

PostgreSQL database with tables for blocks, transactions, tokens, and MUD records

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