A peer-to-peer distributed DuckDB compute grid, settled on TON. Ordinary
machines run DuckDB + the duckton extension and donate a slice of their
RAM/CPU. A requester broadcasts a query (over data in S3 / ADLS / GCS) to many
hosts; several accept and run it redundantly; the first correct result wins and
the rest are cancelled. Machines talk directly over QUIC — there is no central
broker in the data path.
INSTALL duckton FROM community; -- (once published)
LOAD duckton;
SELECT * FROM p2p_query('SELECT 42 AS x');The published loadable extension is
duckton; the in-repo crate names stayp2p-*and the SQL surface staysp2p_*(p2p_query,p2p_share, …).
DuckDB is an in-process engine. The new Quack protocol turns it into a single client-server database. This project goes further: a decentralized, many-host grid with a built-in trust model so a requester can reason about which untrusted hosts to trust with a job.
- Transport: QUIC (TLS 1.3 mandatory, mutual auth, multiplexed streams) via Quinn + rustls. Nothing is readable on the wire.
- Data: lives in cloud object storage, encrypted at rest (Parquet Modular
Encryption). Hosts are pure compute; the design delivers per-job scoped,
short-lived credentials encrypted to the chosen worker. Status: the
coordinator now attaches a per-job scoped credential into each
Dispatchwhen a credential provider is wired (off by default — only when an operator opts into remote object-store reads viastorage.enable_remote_access), and the worker resolves it. The credential is currently scoped to the provider root and delivered unsealed; attestation-bound sealing to the worker's key still needs the per-offer attested key handshake (real TEE hardware, see below). - Trustworthiness: identity (Ed25519, Sybil-resistant) + attestation tiers
(anonymous / TPM measured-boot / hardware TEE) + reputation from signed receipts
- verification (canonical result hashing, quorum agreement, canary audits).
- Hedged execution: race
kworkers, accept the first result that reaches quorum,RESETthe losers.
Transport, at-rest, and integrity are protected on any machine. Confidentiality from a malicious host operator's RAM is only achievable on confidential-computing hardware (TEEs). Commodity laptops cannot guarantee it — so sensitive data is routed only to hosts claiming an attested tier (L2).
Attestation status (be precise): requester selection no longer trusts a
self-reported level — a bid claiming > L0 is honored only if its evidence
verifies against a wired AttestationVerifier (trusted-authority signature over an
allowlisted measurement + the offer nonce); absent/invalid evidence is treated as
L0 (the > L0 gate fails closed, so a spoofed level can't reach sensitive data).
Production nodes ship NO attestor and NO verifier by default — real L1/L2
attestation needs TPM/TEE hardware that is not yet shipped, so a production node
emits L0 and an L2 (sensitive) policy admits nobody until that hardware lands.
The console-server demo exercises the gate end-to-end honestly: its L1/L2
hosts carry a software MockAttestor that emits real per-offer, nonce-bound
evidence, and the demo coordinator wires the matching AllowlistVerifier — so the
gate is genuine (mock attestor + real verification), not a spoofable integer
compare. That wiring is demo-only and never enabled on a production Node. See the
architecture doc for the full reasoning and what remains (per-offer evidence
production + a
network-identity-bound key handshake).
📖 Full documentation site: https://angelerator.github.io/duckton/ (MkDocs
Material; sources in docs/, built + deployed by
.github/workflows/docs.yml). Quickstart, SQL/config
reference, guides, and the deep dives below all live there.
- docs/HOW_IT_WORKS.md — how Duckton works in plain terms: the free-vs-paid economic model, real captured TON testnet proof of the on-chain split (15% treasury / 5% verifier / winner base, free-winner refunds, rejected negatives, staking lifecycle), the wallet-setup walkthrough via the extension, the four contracts explained, and an honest security/threat model.
- docs/ARCHITECTURE.md — full architecture, trust mechanism, protocol flow, security model, versioning/compatibility, config system, pluggable traits, roadmap, and an honest "implementation status & deviations" section.
- docs/BLOCKCHAIN_ECONOMICS.md — design only (not implemented): an optional TON-based economic/incentive layer (wallet↔identity binding, provider earnings formula, stake-weighted bidding/selection, slashing, payment channels, and on-chain Merkle-root-anchored job records) that augments the trust model above.
Duckton settles on TON with a simple, non-custodial economic model. Free queries
run entirely off-chain — no chain client, no escrow, no fees. A paid query
locks the requester's max bid B in a per-job JobEscrow; on settle the contract
pays the winner its quoted base, a 15% platform fee to the admin treasury
(GlobalParams.fee_recipient, enforced on-chain), a 5% commission to each
agreeing wallet verifier, and refunds the remainder to the requester. A
free (walletless) winner is paid nothing and its base is refunded — but the
15% fee and 5% commission are still collected, so the platform and verifiers
earn on every paid job regardless of the node mix.
Every scenario below — including the full staking deposit → 1:1 receipt-mint →
unbond lifecycle and all three negatives — was freshly broadcast on TON testnet
in a single run and read back from chain. Paid-split scenarios use base =
0.004 TON, staking uses 0.1 TON, and the negatives use base = 0.04 TON; the
split percentages (15% / 5%) are enforced on-chain regardless of scale. Full
per-scenario addresses, tx hashes and balance deltas are captured in
ton/deployments/readme_proof.testnet.env.
Each escrow permanently retains MIN_TONS_FOR_STORAGE = 0.05 TON as a storage
reserve (funded from the deploy buffer, not from B), and every payout leg pays a
tiny per-message storage rent (~0.00006 TON) on landing — so a recipient's balance
delta is its gross leg minus that rent, and the numbers reconcile exactly.
| # | Scenario | Real captured on-chain result | On-chain |
|---|---|---|---|
| 1 | Paid, wallet winner | winner +0.003942 (base 0.004) · treasury +0.000542 (15%) · verifier +0.000142 (5%) · requester refund 0.044048 · escrow keeps 0.05 reserve · zero bounces |
escrow |
| 2 | Paid, free (walletless) winner | winner 0 · base 0.004 refunded to requester · treasury +0.000548 (15%, still collected) · verifier +0.000148 (5%) · escrow keeps 0.05 |
escrow |
| 3 | Reject: wrong platform fee | settle with platformFee ≠ base×15% → settle aborts on-chain exit_code=285 FEE_MISMATCH; escrow stays unsettled |
escrow |
| 4 | Reject: under-funded escrow | B = 0.01 < winner + 15% + 5% → settle aborts on-chain exit_code=226 PAYOUT_EXCEEDS_ESCROW (on-chain twin of the off-chain coverage preflight) |
escrow |
| 5 | Reject: mismatched treasury | escrow get_fee_recipient = requester ≠ GlobalParams.fee_recipient (treasury) → honest coordinator refuses (SettleError::TreasuryMismatch); never funded/settled |
escrow |
| 6 | Staking: deposit → 1:1 receipt mint | deposit 0.1 → Duckton receipt jetton 0.1 minted 1:1 to the node (transfer-locked); vault staked 0→0.1, eligible false→true |
vault |
| 7 | Staking: 7-day unbond lock | unbond sets readyAt = unbondingAt + 604800 = exactly 7 days; an immediate StakeWithdraw aborts on-chain exit_code=203 COOLDOWN_NOT_ELAPSED and the stake stays locked |
vault |
Verified live and reused (not redeployed): the
GlobalParams
singleton (platform_fee_bps=1500, participation_commission_bps=500,
fee_recipient = treasury), read back via get_fee_recipient /
get_platform_fee_bps. The end-to-end narrative is in
docs/HOW_IT_WORKS.md.
crates/
config/ p2p-config layered, validated config (defaults<file<env<per-call)
proto/ p2p-proto wire messages, identity, attestation, versioning, value model
transport/ p2p-transport Quinn QUIC + mTLS pinned to Ed25519 identities; version handshake
trust/ p2p-trust canonical hashing, quorum, receipts, reputation, canary,
Sybil PoW/vouch, capability tokens, attestation, sealing
node/ p2p-node coordinator (hedging), worker (admission), discovery,
membership, query engines (mock + locked-down DuckDB), storage
extension/ duckton loadable DuckDB C-API extension (table functions)
config/p2p.example.toml documented example configuration
scripts/ build_extension.sh, append_extension_metadata.py
Requires a Rust toolchain (stable). DuckDB CLI + python3 enable the extension
load test; macOS bundled-DuckDB builds need SDKROOT.
# Core suite (fast; mock engine; ~130 tests)
cargo test --workspace
# Real locked-down DuckDB engine + DuckDB-backed e2e scenarios (compiles DuckDB)
export SDKROOT=$(xcrun --show-sdk-path) # macOS only
cargo test -p p2p-node --features duckdb-engine
# Build the loadable DuckDB extension and smoke-test it in the duckdb CLI
scripts/build_extension.sh
duckdb -unsigned -c "LOAD 'dist/duckton.duckdb_extension'; SELECT * FROM p2p_info();"The end-to-end scenario suite lives in crates/node/tests/scenarios.rs
(functional, hedging/trust, admission, versioning, config, resilience/churn),
crates/node/tests/scenarios_duckdb.rs (real-engine e2e + sandbox, feature-gated),
and crates/extension/tests/load.rs (extension LOAD via the duckdb CLI).
The workspace builds and runs on Linux, macOS, and Windows; a CI matrix
(.github/workflows/ci.yml) runs build/test/clippy/fmt plus the loadable-extension
LOAD smoke test on all three. Notes:
- Windows is supported as a host for the loadable extension. Build it with
scripts/build_extension.ps1(the PowerShell mirror ofbuild_extension.sh); it needs theduckdbCLI and a Python interpreter onPATH. - The
duckdb-enginefeature (the bundled, locked-down DuckDB engine, off by default) compiles DuckDB from source, so it needs a working C/C++ toolchain (MSVC Build Tools on Windows; Xup/Command-Line Tools on macOS — setSDKROOT=$(xcrun --show-sdk-path); a C++ compiler on Linux). The default mock engine needs none of this. - The
ton-livesettlement path shells out to acurlexecutable at runtime (present by default on modern Windows/macOS/Linux); it is off by default. - Per-user secret/runtime files are restricted to the owner:
0600/0700on Unix, and an owner-only protected DACL on Windows.
OS sandbox status: the host now wires the configured
[sandbox]policy + anti-abuse runtime into the live worker, but the default backend isnoop(no OS isolation — jobs run in-process under the DuckDB configuration lockdown). Real OS enforcement (rlimits / Seatbelt / cgroups / Job Object) is opt-in via[sandbox].process_per_job+ thep2p-job-execchild binary (architecture §9.4); the in-process path is the default and what runs unless you enable it.
QUIC is tuned for low latency + high throughput, with everything configurable
under [transport] in the config (see config/p2p.example.toml and
ARCHITECTURE.md §20): UDP GSO/GRO offload, flow-control windows (sized
directly or from a bandwidth-delay-product target), congestion control
(bbr/cubic/newreno) + pacing, parallel result streaming over multiple
unidirectional QUIC streams (per-call overridable), optional wire compression
(none/lz4/zstd, default off on LAN), and 0-RTT/session resumption.
A loopback throughput + latency benchmark lives in
crates/node/tests/benches.rs:
# defaults are small & CI-fast; print the numbers with --nocapture
cargo test -p p2p-node --test benches -- --nocapture
# scale it up:
P2P_BENCH_ROWS=200000 P2P_BENCH_PARALLELISM=4 P2P_BENCH_COMPRESSION=zstd \
cargo test --release -p p2p-node --test benches -- --nocapturePhases 0–4 implemented and tested; Phase 5 scaffolded. See the roadmap and the "implementation status & deviations" section in the architecture doc for exactly what is real vs. mocked (mock attestor for TEE, local-fake object storage, etc.).