Distributed prototype of a P2P English-auction system backed by a public blockchain ledger.
- Docker ≥ 24 with the Compose plugin (
docker compose version)
docker compose up -d --builddocker compose psdocker compose logs -f # all services
docker compose logs -f frontend # single servicedocker compose stopdocker compose down -vAll tuneable parameters are set via environment variables. Override them in .env (already provided) or inline in docker-compose.yml.
| Variable | Default | Description |
|---|---|---|
P2P_PORT |
8080 |
Netty TCP port for P2P messages |
FRONTEND_PORT |
8090 |
HTTP port for the web/REST gateway |
ENABLE_FRONTEND |
false |
Set true only on the frontend service |
POW_DIFFICULTY |
16 |
Leading-zero bits required for PoW block mining |
CONSENSUS_MODE |
POW |
POW or POR (Proof-of-Reputation) |
BOOTSTRAP_HOST |
(none) | Hostname of the bootstrap node to contact on startup |
BOOTSTRAP_PORT |
8080 |
Port of the bootstrap node |
IDENTITY_PATH |
(in-memory) | Path to persist the node's ECDSA keypair (e.g. /app/data/identity.key) |
LEDGER_PATH |
/app/data/ledger.json |
Path to the NDJSON ledger file |
NODE_ROLE |
(miner) | Set FULL_NODE to disable mining on a node |
The provided .env file sets sensible defaults:
POW_DIFFICULTY=16 #or 8
Open the web UI at http://localhost:8090 after docker compose up -d.
curl http://localhost:8090/api/healthcurl -s -X POST http://localhost:8090/api/create-auction \
-H 'Content-Type: application/json' \
-d '{"itemName":"Bicycle","startingPrice":100.0,"durationSeconds":300}'curl -s -X POST http://localhost:8090/api/place-bid \
-H 'Content-Type: application/json' \
-d '{"itemName":"Bicycle","bidAmount":125.0}'curl -s -X POST http://localhost:8090/api/close-auction \
-H 'Content-Type: application/json' \
-d '{"itemName":"Bicycle"}'The auction identifier is derived from
itemName(SHA-256 hash of the normalised name). Use the exact same name across all operations.
Tests run outside Docker, against the compiled code directly (no network needed for unit/integration tests).
- Java 17+, Maven 3.9+
mvn testExpected result: 25 tests, 0 failures.
| Test class | What it covers |
|---|---|
Sprint1SecurityTest |
ECDSA identity, message signing, signature verification, replay rejection |
Sprint2OverlayTest |
Kademlia routing table, bucket eviction, LRU replacement |
Sprint2IterativeLookupTest |
Iterative FIND_NODE / FIND_VALUE across multi-hop topology |
Sprint3ConsensusTest |
Block validation, chain reorg, fork resolution by cumulative work |
AuctionLifecycleTest |
Create → bid → close auction flow, duplicate/invalid bid rejection |
PoRConsensusTest |
PoR difficulty reduction, invalid-block penalty, reputation-weighted chain scoring, miner key JSON round-trip |
GossipConvergenceIntegrationTest |
Multi-node gossip: transaction published to node B converges to node C without manual sync |
FaultInjectionResilienceIntegrationTest |
Simultaneous failure of 2 out of 4 nodes; surviving partition continues accepting transactions and eventually converges |
mvn test -Dtest=PoRConsensusTest
mvn test -Dtest=FaultInjectionResilienceIntegrationTest# Start the stack
docker compose up -d
# Create an auction
curl -s -X POST http://localhost:8090/api/create-auction \
-H 'Content-Type: application/json' \
-d '{"itemName":"Watch","startingPrice":50.0,"durationSeconds":600}'
# Kill two nodes
docker compose stop node-1 node-2
# Place a bid — surviving nodes must still accept it
curl -s -X POST http://localhost:8090/api/place-bid \
-H 'Content-Type: application/json' \
-d '{"itemName":"Watch","bidAmount":75.0}'
# Restart killed nodes
docker compose start node-1 node-2
# Nodes resync chain from peers via gossip/chain-request (check logs)
docker compose logs -f node-1Expected: bid is accepted while nodes are down; restarted nodes catch up via chain sync.
docker compose up -d
curl -s -X POST http://localhost:8090/api/create-auction \
-H 'Content-Type: application/json' \
-d '{"itemName":"Lamp","startingPrice":20.0,"durationSeconds":600}'
# Full stop and restart (volumes are kept)
docker compose stop
docker compose start
# Auction must still be present — chain loaded from volume
curl http://localhost:8090/api/healthExpected: chain length ≥ 2 after restart; auction state preserved.
Node IDs are derived via PoW over the public key (SHA-256(pubkey || nonce) must have N leading zero bits). A new node that tries to join must solve the puzzle before its ID is accepted into routing tables, making cheap Sybil ID generation computationally costly.
Verify the puzzle is enforced:
docker compose logs bootstrap-1 | grep "NodeID generated"
# Output: "NodeID generated successfully! Nonce found: <N>"Covered by PoRConsensusTest.invalidBlockPenalisesMiner: a block with a tampered hash is rejected and the producing node's reputation is set to 0, increasing its effective mining difficulty on subsequent attempts.
| Property | Mechanism |
|---|---|
| Node identity | ECDSA secp256r1 keypair, persisted across restarts via Docker volume |
| Transport security | TLS 1.3 / 1.2 on all P2P connections (Netty SslHandler) |
| Message authentication | Every SecureMessage is ECDSA-signed by the sender; signature verified on receipt |
| Anti-replay | Per-sender nonce on transactions; monotone nonce enforced at ingestion |
| Block integrity | SHA-256 hash chain; PoW difficulty enforced on all blocks |
| Sybil resistance (overlay) | S/Kademlia: node ID requires PoW over keypair |
| Sybil resistance (ledger) | PoR: repeated invalid blocks reduce reputation and raise effective difficulty |
| Least exposure | Only frontend:8090 and bootstrap ports exposed to host; all other communication stays on the internal auction-net bridge |