High-performance Ethereum blockchain monitoring service that tracks transactions for 500,000+ addresses using native JSON-RPC methods.
graph TB
subgraph "Entry Point"
Main[main.go]
end
subgraph "Core Components"
Monitor[Monitor]
EthClient[Ethereum Client]
Storage[In-Memory Storage]
Publisher[Output Publisher]
end
subgraph "Ethereum Network"
RPC[JSON-RPC Provider]
end
subgraph "Output Destinations"
Console[Console Output]
Kafka[Kafka Topic]
end
subgraph "Persistence"
StateFile[last_block.txt]
AddressFile[addresses.csv]
end
Main --> Monitor
Monitor --> EthClient
Monitor --> Storage
Monitor --> Publisher
EthClient -->|eth_blockNumber| RPC
EthClient -->|eth_getBlockByNumber| RPC
Storage -->|saves/loads| StateFile
Storage -->|loads| AddressFile
Publisher --> Console
Publisher --> Kafka
style Main fill:#e1f5ff
style Monitor fill:#fff4e1
style Storage fill:#f0f0f0
style RPC fill:#ffe1e1
sequenceDiagram
participant Main
participant Monitor
participant EthClient
participant Storage
participant Publisher
Main->>Storage: Load addresses from CSV (500k)
Main->>Storage: Get last processed block from file
Main->>Monitor: Start monitoring
loop Every 12 seconds
Monitor->>EthClient: Get latest block number
loop For each unprocessed block
Monitor->>EthClient: Get block with transactions
EthClient-->>Monitor: Block + Transactions
loop For each transaction
Monitor->>Monitor: Check if from/to in address set (O(1))
alt Address matches
Monitor->>Storage: Check if already processed (in-memory)
alt Not processed
Monitor->>Storage: Mark as processed
Monitor->>Publisher: Publish output
Publisher-->>Console: JSON output
Publisher-->>Kafka: Event (optional)
end
end
end
Monitor->>Storage: Save last processed block to file
end
end
go mod downloadConfigure via environment variables (prevents exposing API keys in command history):
cp .env.example .envAvailable Environment Variables:
ETH_RPC_URL- Ethereum RPC endpoint URLSTATE_PATH- State file path (default: ./last_block.txt)ADDRESSES_FILE- CSV file with addresses (default: addresses.csv)START_BLOCK- Starting block number (default: -1 for auto)ENABLE_KAFKA- Enable Kafka output (true/false)KAFKA_BROKERS- Kafka broker addressesKAFKA_TOPIC- Kafka topic name
go run main.gogo run main.go \
-rpc "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY" \
-state "./last_block.txt" \
-addresses "./addresses.csv" \
-start-block 18000000go run main.go \
-rpc "YOUR_RPC_URL" \
-kafka \
-kafka-brokers "localhost:9092" \
-kafka-topic "eth-transactions"CSV file with userId,address format:
| userId | address |
|---|---|
| user1 | 0xdAC17F958D2ee523a2206206994597C13D831ec7 |
| user2 | 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 |
{
"userId": "user1",
"from": "0xabc...",
"to": "0xdef...",
"amount": "1000000000000000000",
"hash": "0x123...",
"blockNumber": 18000000
}Run all tests:
go test ./... -vRun with coverage:
go test -cover ./... -vRun specific package tests:
go test ./ethereum
go test ./storage
go test ./monitor
go test ./outputThis service does not handle block reorganizations. If a reorg occurs, some published transactions may not exist on the final chain, and some transactions may be missed. For a demonstration service monitoring a small set of addresses, this is an acceptable limitation. Reorgs typically only affect the most recent 1-3 blocks and occur infrequently.
In a production system, you would implement reorg detection by storing block hashes alongside block numbers in your state file. On each polling cycle, verify that previously processed blocks still have matching hashes. If a mismatch is detected, a reorg has occurred.
// Detection: Check if a previously seen block has changed
func (m *Monitor) detectReorg(blockNum int64) (bool, int64) {
storedHash := m.storage.GetBlockHash(blockNum)
currentHash := m.ethClient.GetBlockHash(blockNum)
if storedHash != currentHash {
return true, m.findCommonAncestor(blockNum)
}
return false, blockNum
}
// Recovery: Roll back and reprocess from the reorg point
func (m *Monitor) handleReorg(reorgPoint int64) {
m.storage.ClearTransactionsAfter(reorgPoint)
m.publisher.PublishReorgAlert(reorgPoint)
m.currentBlock = reorgPoint
}Production systems typically wait for 12 block confirmations before considering transactions finalized, as this represents Ethereum's practical finality threshold. Transactions would include a status field indicating whether they are pending, confirmed, or reorged. Reorg events should be published to a separate notification channel so downstream systems can handle invalidated transactions appropriately.

