PIVX Bridge
High-performance shield sync server for the PIVX network.
Drop-in replacement for PivxNodeController, written in Rust.
A shield sync bridge that connects to a PIVX full node, scans for Sapling shielded transactions, and serves compact shield data to light wallets over HTTP. Fully compatible with MyPIVXWallet — zero client changes required.
Benchmarked head-to-head against PivxNodeController on the same production server:
| Benchmark | PNC | Bridge | Improvement |
|---|---|---|---|
| getblockcount | 4.2ms | 2.0ms | 2x |
| getshieldblocks (34k JSON) | 11.2ms | 3.8ms | 3x |
| getshielddata full (200MB) | 2.12s | 0.75s | 2.8x |
| getshielddata incremental | 499ms | 1.0ms | 494x |
| New block visibility | 60s (polling) | Instant (ZMQ) | ~ |
| RSS memory | 386MB | 210MB | 46% less |
| VSZ memory | 1.9GB | 627MB | 67% less |
- In-memory shield buffer — the entire shield dataset (~200MB) is held in RAM. Requests serve a memory slice directly — zero disk I/O, zero file handle contention
- Binary search indexing — block lookups in O(log n) instead of linear scan. 17 comparisons for 34,000 shield blocks
- Inline tx extraction —
getblockverbosity 2 returns tx hex inline, eliminating a separate RPC call per transaction. 100x fewer RPC calls during chain scan - ZMQ + polling — new blocks indexed instantly via ZMQ hashblock notifications, with a 10s polling safety net that catches anything ZMQ misses
- Block hash LRU cache — the last 1,000 height→hash mappings are cached in memory.
getblockhashproxy calls serve from cache in <0.01ms instead of ~1ms RPC round-trips. On production traffic, ~50% of proxy calls are cache hits - Opt-in compact formats — strip Groth16 proofs and signatures that light wallets never verify, cutting sync data by 24-42%
# Build
cargo build --release
# Run (with a PIVX node on localhost)
./target/release/pivx-bridge \
--rpc-url http://127.0.0.1:51473 \
--rpc-user rpc \
--rpc-pass rpcOr use a .env file (copy .env.sample):
cp .env.sample .env
# Edit .env with your node credentials
./target/release/pivx-bridgeThe bridge scans from Sapling activation (block 2,700,501) on first run, then indexes new blocks in real-time via ZMQ.
Add to pivx.conf:
server=1
rpcuser=rpc
rpcpassword=rpc
zmqpubhashblock=tcp://127.0.0.1:28332
All endpoints are served at both /mainnet/... and /... (without prefix).
| Route | Method | Description |
|---|---|---|
/mainnet/getshielddata?startBlock=N |
GET | Binary shield stream |
/mainnet/getshielddatalength?startBlock=N&endBlock=M |
GET | Byte count (for progress bars) |
/mainnet/getshieldblocks |
GET | JSON array of shield block heights |
/mainnet/sendrawtransaction |
POST | Broadcast raw transaction hex |
/mainnet/address_index |
GET | SQLite address index (for MPW-Tauri) |
| Route | Method | Description |
|---|---|---|
/mainnet/:method?params=a,b,c&filter=<jq> |
GET | Proxied RPC call with optional jq filtering |
The proxy validates methods against a configurable whitelist. Parameters are automatically type-coerced (numbers, booleans, strings). The filter parameter uses the system jq binary for full compatibility.
The getshielddata endpoint accepts an optional format query parameter:
| Format | Packet | Per output | Use case |
|---|---|---|---|
pivx (default) |
0x03 full raw tx | 948 bytes | MPW compatibility |
compact |
0x04 | 724 bytes (-24%) | Wallets with sender recovery |
compactplus |
0x05 | 644 bytes (-32%) | New wallet bootstrap |
The default PIVX format is byte-identical to PivxNodeController output.
The shield stream is a sequence of length-prefixed packets:
PivxNodeController-compatible (default):
Transaction: [4-byte LE length][raw PIVX v3 tx bytes]
Block footer: [4-byte LE length=9][0x5d][height:4LE][time:4LE]
Transactions come first, block footer comes last (per block).
Compact (opt-in):
Block header: [4-byte LE length=5][0x5d][height:4LE]
Compact tx: [4-byte LE length][0x04][nSpends:1][nOutputs:1]
per spend: nullifier(32)
per output: cmu(32) + epk(32) + enc(580) + out_ct(80)
CompactPlus (opt-in):
Same as compact, but type=0x05 and out_ciphertext omitted per output.
src/
main.rs Startup, initial scan, ZMQ + polling subscribers, axum server
config.rs CLI args + .env configuration
rpc.rs JSON-RPC 1.0 client with auth + connection pooling + 30s timeout
scanner.rs Block scanner + PIVX v3 Sapling tx parser (validated against PIVX Core)
stream.rs Binary stream encoder (PivxCompat / Compact / CompactPlus)
cache.rs Persistent shield.bin cache with crash recovery
index.rs Binary search index with byte offsets (shield.json compat)
api.rs HTTP endpoints with in-memory buffer serving + block hash LRU cache
proxy.rs RPC proxy with system jq filtering + response timing
All options support both CLI flags and environment variables:
| Flag | Env | Default | Description |
|---|---|---|---|
--rpc-url |
RPC_URL |
http://127.0.0.1:51473 |
PIVX node RPC endpoint |
--rpc-user |
RPC_USER |
rpc |
RPC username |
--rpc-pass |
RPC_PASS |
rpc |
RPC password |
--zmq-url |
ZMQ_URL |
tcp://127.0.0.1:28332 |
ZMQ hashblock endpoint |
--port |
PORT |
3000 |
HTTP server port |
--sapling-height |
SAPLING_HEIGHT |
2700501 |
Sapling activation block |
--no-compression |
NO_COMPRESSION |
false |
Disable gzip (use behind nginx) |
--allowed-rpcs |
ALLOWED_RPCS |
(see below) | Comma-separated RPC whitelist |
Default allowed RPCs: getblockcount, getblockhash, getblock, getrawtransaction, sendrawtransaction, getmasternodecount, listmasternodes, getbudgetprojection, getbudgetinfo, getbudgetvotes
The bridge writes two files in the working directory:
shield.bin— Pre-encoded binary shield stream (append-only, survives restarts)shield_index.json— Block height to byte offset index (PivxNodeControllershield.jsoncompatible)
On startup, shield.bin is validated and truncated to the last complete block footer if a previous run crashed mid-write.
cargo test # 44 tests (includes real mainnet tx validation)
cargo clippy # Zero warnings- Download the latest release or build from source
- Copy your
.envcredentials (same format) - Copy
shield.binandshield.jsonfrom PivxNodeController (renameshield.jsontoshield_index.json) - Install
jqon the server (apt install jq) for RPC proxy filter support - Point your reverse proxy at the bridge instead of the Node.js server
- MPW connects unchanged — the binary protocol and API are identical
MIT