Configuration
Layered, validated config — defaults < file < env < per-call; nothing is hard-coded. The values shown are this node's REAL resolved defaults (serialized from the Rust GridConfig); the file / env / per-call layers override these at runtime.
Every setting the node runs with, and where each value comes from. Config is layered: a built-in default, overridden by a file, then environment variables, then a per-query override.
Impact: Operators tune behavior without touching code — nothing is hard-coded — and you can see exactly what is in effect right now.
- 1Built-in defaults
Compiled-in, safe values — this is exactly the resolved layer shown below. The grid runs with zero config.
- 2p2p.toml file
Operator file, found via P2P_CONFIG or --config. Overrides defaults.
- 3P2P_* environment
Per-process overrides, ideal for secrets and containers.
- 4Per-call override
Argument on a single call, e.g. a p2p_query() parameter.
# =============================================================================
# duckdb-p2p — example node configuration
# =============================================================================
# Layered precedence (lowest -> highest):
# 1. built-in defaults (shown below)
# 2. this TOML file (P2P_CONFIG=/path/to/p2p.toml, or pass --config)
# 3. environment variables (P2P_* — see crates/config/src/lib.rs)
# 4. per-call SQL parameters (args to p2p_query / p2p_share / p2p_join)
#
# Every value here is optional; omit a key to inherit the documented default.
# `deny_unknown_fields` is on, so a typo'd key is a hard error (fail fast).
# -----------------------------------------------------------------------------
[protocol]
# Protocol version this node advertises and the minimum it will accept.
# Compatibility: same MAJOR required; MINOR/PATCH may differ (newer side
# downgrades to the negotiated common version). Peers below min_supported_version
# or with a different major are cleanly rejected with a typed error.
version = "1.0.0"
min_supported_version = "1.0.0"
# Require peers to report an identical DuckDB engine version to participate in a
# quorum (result-determinism). Off by default.
require_matching_engine_version = false
[network]
# QUIC bind address. Port 0 = OS-assigned ephemeral port (handy for tests).
bind_addr = "127.0.0.1:0"
# advertised_addr = "203.0.113.10:9494" # externally reachable addr, if NAT'd
idle_timeout_ms = 30000
connect_timeout_ms = 10000
keepalive_ms = 10000 # must be < idle_timeout_ms
max_concurrent_bidi_streams = 256 # backpressure: cap in-flight streams
stream_receive_window = 8388608 # 8 MiB per-stream flow-control window
receive_window = 67108864 # 64 MiB per-connection window
result_chunk_bytes = 262144 # 256 KiB result streaming chunk size
# -----------------------------------------------------------------------------
# Transport performance tuning (low latency + high throughput, all configurable)
# See ARCHITECTURE.md "Transport performance tuning" for the full rationale.
# -----------------------------------------------------------------------------
[transport.quic]
gso = true # UDP Generic Segmentation Offload on TX
# (biggest throughput lever; quinn-udp
# auto-disables if unsupported)
gro = true # desire UDP Generic Receive Offload
# (quinn-udp enables automatically; advisory)
congestion = "cubic" # "bbr" | "cubic" | "newreno"
pacing = true # NOTE: Quinn always paces; off = advisory
# stream_receive_window_bytes = 8388608 # override [network].stream_receive_window
# connection_receive_window_bytes = 67108864 # override [network].receive_window
send_window_bytes = 67108864 # 64 MiB connection send window
max_concurrent_uni_streams = 256 # cap on bulk result fan-out streams
enable_0rtt = false # 0-RTT/session resumption (see caveats)
session_ticket_lifetime_secs = 43200 # 12h resumption ticket lifetime
[transport.quic.bdp]
# Auto-size flow-control windows from bandwidth-delay product when enabled;
# overrides the explicit windows above so large results aren't window-limited.
enabled = false
bandwidth_mbps = 1000 # target link bandwidth (Mbit/s)
rtt_ms = 50 # target round-trip time (ms)
[transport.result]
parallelism = 1 # concurrent uni-streams per large result
# (1 = inline; overridable per p2p_query call)
# chunk_bytes = 262144 # override [network].result_chunk_bytes
parallel_min_bytes = 1048576 # only fan out results >= 1 MiB
max_result_bytes = 2147483648 # reject a result manifest larger than 2 GiB
# (untrusted winner; bypasses the frame cap —
# clamped to p2p_proto::MAX_RESULT_BYTES = 8 GiB)
max_result_parts = 1024 # max parallel result streams accepted per result
[transport.compression]
algorithm = "none" # "none" | "lz4" | "zstd"
# default off on loopback/LAN; for WAN
# prefer "lz4" (cheap) or "zstd" (ratio)
level = 3 # zstd level 1..=22 (lz4/none ignore)
min_size_bytes = 65536 # only compress results >= 64 KiB
[identity]
# key_path = "/etc/duckdb-p2p/node.key" # PKCS#8 Ed25519 PEM; omit = ephemeral
pinning_mode = "tofu" # "tofu" | "allowlist"
allowlist = [] # node ids trusted when pinning_mode="allowlist"
# env: P2P_IDENTITY_PINNING_MODE,
# P2P_IDENTITY_ALLOWLIST (comma list),
# P2P_IDENTITY_KEY_PATH
# -----------------------------------------------------------------------------
# Closure posture — the single PRIVATE / ENTERPRISE switch (see docs/PRIVATE_MODE.md)
# -----------------------------------------------------------------------------
# "public" (default) = the zero-config grid: TOFU pinning allowed, an ungrouped
# host serves everyone, soft (declared) labels. "private" = a fully closed
# company grid: outsiders can neither connect/impersonate nor be served. Setting
# mode = "private" makes the node FAIL TO START unless ALL of the following hold
# (fail-closed — a misconfigured private node never leaks):
# * identity.pinning_mode = "allowlist" with a NON-EMPTY identity.allowlist
# (the member roster — outsiders are refused at the mTLS layer);
# * membership.group_enforcement = "token" (cryptographic group proof, never a
# soft declared label) with a membership.group_issuers entry per group;
# * membership.networks set to an explicit, non-"default" name.
# It also turns on, at runtime: fail-closed discovery (drop unknown-labeled
# peers), require-grouped-host (an ungrouped host refuses to serve), and a
# default-deny requester roster (serve only identity.allowlist members). The
# application↔transport identity binding (an offer's requester_id must equal the
# authenticated mTLS peer) is ALWAYS on, in both modes (it is a correctness fix).
# env: P2P_SECURITY_MODE = public|private
[security]
mode = "public" # "public" | "private"
# -----------------------------------------------------------------------------
# Request-scoping / routing labels (architecture §7.5) — logical partition,
# groups, region. Default-off (serves the implicit "default" partition,
# ungrouped, no region) so a zero-config node is unconstrained. In PRIVATE mode
# these become the company's closed pool (see docs/PRIVATE_MODE.md).
# -----------------------------------------------------------------------------
[membership]
networks = ["default"] # logical grid partition(s); PRIVATE: set a
# non-"default" name. env: P2P_MEMBERSHIP_NETWORKS
groups = [] # group memberships served/claimed; empty =
# ungrouped/public. env: P2P_MEMBERSHIP_GROUPS
# region = "eu" # data-residency hint. env: P2P_MEMBERSHIP_REGION
group_enforcement = "soft" # "soft" (declared) | "token" (cryptographic
# proof). PRIVATE requires "token".
# env: P2P_MEMBERSHIP_GROUP_ENFORCEMENT
region_trust = "declared" # "declared" | "attested". env: P2P_MEMBERSHIP_REGION_TRUST
# group_token = "<json CapabilityToken>" # this node's OWN group proof, presented as a
# requester under the token tier.
# env: P2P_MEMBERSHIP_GROUP_TOKEN
# region_token = "<json CapabilityToken>" # this node's region-attestation proof (host side)
# Trusted group issuer pubkeys (group -> hex ed25519). Required per group under
# the token tier. env: P2P_MEMBERSHIP_GROUP_ISSUERS = "finance=<hex>,ops=<hex>"
# [membership.group_issuers]
# finance = "ab12...<64 hex>"
# [membership.region_issuers] # trusted region issuers (region -> hex), attested tier
# eu = "cd34...<64 hex>"
[discovery]
mode = "static" # "static" (MVP) | "kademlia" (libp2p
# Kademlia DHT + gossipsub; scales)
bootstrap = [] # entry peers only; never in data path.
# kademlia mode: libp2p multiaddrs incl.
# peer id, e.g.
# "/ip4/203.0.113.10/tcp/9595/p2p/12D3Koo..."
listen_addrs = [] # libp2p listen multiaddrs for the
# discovery overlay (distinct from the
# QUIC data-plane network.bind_addr).
# [] = ephemeral loopback TCP port.
# e.g. ["/ip4/0.0.0.0/tcp/9595"]
candidate_sample_size = 16 # HARD cap on workers contacted per job
# (keeps fan-out bounded at any swarm size)
[discovery.kademlia]
replication_factor = 20 # k-bucket size
query_parallelism = 3 # alpha
record_ttl_secs = 3600
[discovery.gossip]
topic = "duckdb-p2p/caps/1" # gossipsub topic for signed cap ads
heartbeat_ms = 5000 # capability-ad republish interval
fanout = 6
capability_ttl_secs = 30 # ads older than this are rejected
# -----------------------------------------------------------------------------
# Global NAT traversal (architecture §8 "Networking & NAT traversal")
# -----------------------------------------------------------------------------
# Lets two nodes behind home/office NATs on DIFFERENT networks worldwide connect
# DIRECTLY, with NO central server and NO fixed IP/URL. `identify` is always on;
# these gate the optional libp2p behaviours layered on the discovery overlay:
# * autonat — learn if we're publicly reachable + discover our external addr
# * dcutr — coordinated hole punching: upgrade a relayed link to a DIRECT
# one through NATs (works over QUIC/UDP). Requires relay_client.
# * relay_client — Circuit Relay v2 client + AutoRelay: when hole punching fails
# (symmetric NAT), route through VOLUNTEER relay peers
# auto-selected from the network (never a central server).
# * act_as_relay — volunteer THIS node as a Circuit Relay v2 server for others.
# * mdns — zero-config peer discovery on the same LAN.
# NOTE (law of distributed systems): at least one reachable entry point
# (discovery.bootstrap seed or a relay) is required to JOIN the swarm — but it is
# replaceable, owns nothing, and is never in the query data path.
[discovery.nat]
autonat = true
dcutr = true # requires relay_client = true
relay_client = true
act_as_relay = false # opt in to volunteer as a relay
mdns = true # LAN zero-config discovery
mdns_query_interval_secs = 300 # how often mDNS re-queries the LAN
external_addresses = [] # e.g. ["/ip4/203.0.113.10/udp/9595/quic-v1"]
relays = [] # explicit relay multiaddrs (incl. /p2p/<id>);
# [] = AutoRelay picks relays from the network
max_relays = 3 # max simultaneous relay reservations (AutoRelay cap)
[discovery.nat.relay_limits] # caps applied only when act_as_relay = true
max_reservations = 128
max_reservations_per_peer = 4
reservation_duration_secs = 3600
max_circuits = 16
max_circuits_per_peer = 4
max_circuit_duration_secs = 120
max_circuit_bytes = 131072 # 128 KiB
[scheduler]
replicas = 3 # k workers to race (>= quorum)
quorum = 2 # matching hashes required to accept
verify_mode = "quorum" # "fast" | "quorum"
offer_timeout_ms = 2000
dispatch_timeout_ms = 30000
# --- Resilience / re-dispatch (architecture §8/§11) ---
attempt_deadline_ms = 60000 # requester per-attempt deadline; a silent
# attempt past this is inconclusive (job
# fault, NO provider penalty) -> re-dispatch
max_retries = 0 # max (re)dispatch attempts; 0 = UNLIMITED
# (route around dead/timed-out nodes until done)
max_total_duration_ms = 0 # optional wall-clock cap on the whole loop (0 = none)
backoff_initial_ms = 200 # exponential backoff start
backoff_max_ms = 5000 # backoff ceiling
backoff_jitter_frac = 0.5 # jitter fraction [0,1] to de-sync retry storms
retry_budget_max_tokens = 32 # global retry/hedge token bucket (burst cap; 0 = unlimited)
retry_budget_refill_per_sec = 4 # token refill rate (tokens/sec)
progress_interval_ms = 2000 # requester's expected heartbeat interval
progress_stall_multiplier = 5 # stall if no progress within interval * this
max_inflight_jobs = 64 # requester-side concurrency semaphore
# -----------------------------------------------------------------------------
# Host (worker) execution deadline + progress/heartbeat streaming (resilience)
# -----------------------------------------------------------------------------
# The host abandons a job that exceeds job_timeout_ms (the requester then
# re-dispatches), and streams a progress/heartbeat update every
# progress_interval_ms while executing — the progress update IS the liveness
# signal the requester watches for stalls.
[worker]
job_timeout_ms = 60000 # host execution deadline (0 = none)
progress_interval_ms = 2000 # how often the host streams progress (0 = off)
# -----------------------------------------------------------------------------
# Liveness / failure detection (architecture §8): phi-accrual + SWIM
# -----------------------------------------------------------------------------
# A phi-accrual failure detector over heartbeat/gossip intervals plus SWIM-style
# indirect probing, layered on the libp2p gossip overlay. Unhealthy/suspect peers
# are excluded from candidate selection. Off-path until a liveness view is wired
# into the coordinator, so a node with no liveness wiring behaves as before.
[liveness.phi]
enabled = true
convict_threshold = 8.0 # phi at/above which a peer is convicted (~8-12)
window_size = 100 # sliding window of recent heartbeat intervals
min_std_ms = 50.0 # floor on interval std-dev (avoid over-confidence)
acceptable_pause_ms = 0.0 # extra slack added to the mean interval
first_interval_ms = 5000.0 # bootstrap interval before enough samples
[liveness.swim]
enabled = true
indirect_probe_count = 3 # k random peers asked to indirect-probe a suspect
probe_timeout_ms = 1000 # direct-probe timeout
indirect_probe_timeout_ms = 2000 # each indirect (relayed) probe timeout
[budget]
memory_bytes = 4294967296 # 4 GiB donated
threads = 2
max_jobs = 3
per_job_memory_bytes = 1073741824 # 1 GiB default per-job lease
per_job_threads = 1
data_classes = ["public"] # public | internal | sensitive
[trust]
min_trust = 0.7 # soft score gate [0,1]
min_attestation = "L0" # hard gate: L0 | L1 | L2
reputation_half_life_secs = 604800 # 7 days recency half-life
canary_rate = 0.05 # fraction of jobs that are canaries
incorrect_penalty = 0.5
bootstrap_trust = 0.1 # trust for brand-new identities
# store_path = "/var/lib/duckdb-p2p/trust.redb" # persist reputation
# across restarts (embedded redb).
# omit = bounded in-memory (default)
[trust.weights] # effective_trust soft-score weights
alpha_reputation = 0.7
beta_age = 0.1
gamma_voucher = 0.1
delta_stake = 0.1
[sybil]
pow_difficulty_bits = 16 # leading zero bits for identity PoW
min_stake = 0
vouch_weight = 0.05
[storage]
provider = "local-fake" # default credential provider: local-fake | s3 | az | gcs
# (use "s3" for AWS *and* self-hosted MinIO / S3-compatible)
# endpoint = "minio.local:9000" # S3-compatible / MinIO host:port (host only; scheme via use_ssl)
# region = "us-east-1" # MinIO accepts any region; must be set
# url_style = "path" # "path" for MinIO/most self-hosted, "vhost" for AWS
# use_ssl = false # MinIO dev is often plain HTTP; true in production
credential_ttl_secs = 900 # scoped credential lifetime
key_ttl_secs = 900 # sealed data-key lifetime
# --- Data sources & formats (architecture §4 / §9.2 / §9.4) ------------------
# Master switch: allow worker NETWORK EGRESS for remote object-storage reads.
# When false (default) the execution engine stays in the strict/local lockdown
# (enable_external_access=false). DuckDB's enable_external_access is all-or-
# nothing for the network — it CANNOT restrict egress to specific endpoints — so
# enabling remote access REQUIRES complementary OS-level egress filtering (the
# OS sandbox is separately deferred; see §9.4).
enable_remote_access = false
# Fail engine init if a preload_extensions entry can't be loaded. Required
# extensions are pre-loaded at init, NEVER INSTALL/LOAD'd at query time.
require_extensions = true
# DuckDB extensions pre-loaded at engine init. For real cloud reads enable e.g.
# ["httpfs", "aws", "azure", "parquet", "json", "delta", "iceberg"]. (parquet
# and json are also statically linked in the bundled build.)
preload_extensions = []
# Formats workers will serve (extensible; unknown values tolerated).
enabled_formats = ["csv", "json", "parquet"]
# Storage/credential providers enabled on this node.
enabled_providers = ["local-fake"]
# Local directories DuckDB may read even with external access disabled
# (DuckDB `allowed_directories`); used for local fixtures. e.g. ["/srv/data"].
allowed_local_paths = []
# Per-provider option overrides (endpoint/region/url_style/use_ssl/...), keyed
# by id. These are NON-SECRET connection knobs only — the access key / secret
# are NEVER placed here; they are delivered per job, sealed (encrypted) to the
# worker (see "Encrypted credentials" below).
# [storage.provider_options.s3] # AWS S3
# region = "us-east-1"
# endpoint = "s3.amazonaws.com"
# Self-hosted MinIO / S3-compatible (reads Delta/Parquet/CSV/JSON/Iceberg):
# [storage.provider_options.s3]
# endpoint = "minio.local:9000"
# url_style = "path" # MinIO requires path-style addressing
# use_ssl = false # plain HTTP for a local MinIO; true with TLS
# region = "us-east-1"
# [storage.provider_options.gcs]
# endpoint = "storage.googleapis.com"
#
# Encrypted credentials (the MinIO access key / secret) — NEVER in config:
# * Requester side: put the key/secret in a CloudCredential, seal it to the
# selected worker's X25519 sealing public key (from its attestation /
# capability record) with `p2p_node::sealed_credential("s3", pubkey, &cred,
# "bucket/prefix/", ttl)`. The token is `sealed:v1:<hex>` — ciphertext only.
# * Worker side: the engine opens it just-in-time at setup with its sealing
# key (StorageSetup::with_sealing) and mints a prefix-scoped CREATE SECRET.
# Plaintext never persists; at rest only 0600 files / sealed-to-worker blobs.
# Per-format reader options, keyed by format id:
# [storage.format_options.parquet]
# hive_partitioning = "true"
# -----------------------------------------------------------------------------
# Local-first execution planner (architecture §4 data plane / §11 scheduler)
# -----------------------------------------------------------------------------
# A node can run a query entirely in its OWN locked-down in-process DuckDB for
# FREE — no bidding, escrow, quorum or payment — because it trusts its own
# machine. This section governs WHEN that free local path is chosen over a grid
# dispatch. A pre-flight, metadata-only data-size estimate (Parquet footers,
# Delta _delta_log stats, Iceberg manifests, CSV/JSON object size + sampling) is
# translated into an estimated PEAK WORKING-SET memory and compared against the
# node's CURRENT headroom (budget.memory_bytes * ram_fraction minus memory in
# use by concurrent local jobs). A job that blows past the threshold mid-flight
# is aborted and re-dispatched to the grid (adaptive fail-over).
[planner]
enabled = true # false = always dispatch to the grid
local_execution_enabled = true # false = REMOTE-ONLY mode: never run a
# query on this machine (even tiny ones);
# always route to the grid. Hard gate that
# overrides `prefer` (incl. per-call
# prefer => 'local'). Thin-client friendly:
# a node that never called p2p_share works
# as a pure requester. No hosts => clear
# NoCandidates error (no local fallback).
prefer = "auto" # default routing: local | remote | auto
# (per call: p2p_query(..., prefer => 'local'))
# set "remote" for a sticky prefer-the-grid default
ram_fraction = 0.6 # alpha: fraction of budget.memory_bytes
# usable locally (headroom basis)
max_concurrent_local_jobs = 4 # local saturation -> route to grid
size_threshold_bytes = 268435456 # 256 MiB hard cap on local scanned bytes
spill_tolerance_bytes = 536870912 # 512 MiB the peak working set may exceed
# RAM headroom via out-of-core spill (0=never)
max_local_latency_ms = 10000 # latency budget for the local path (0=off)
# -----------------------------------------------------------------------------
# OS-level execution sandbox (architecture §9.4)
# -----------------------------------------------------------------------------
# The boundary AROUND job execution, complementing DuckDB's own lockdown
# (enable_external_access / lock_configuration / allowed_directories / ephemeral
# temp). DuckDB CANNOT scope network egress to specific endpoints — the OS must.
# SECURE BY DEFAULT for the HOST serving path: enabled + process_per_job default
# ON, so a node that serves OTHER peers' jobs runs them OS-sandboxed in a child
# process. This does NOT affect the requester's OWN local self-run path (it runs
# in-process and needs no protection from yourself). backend = auto degrades to a
# no-op on platforms with no OS sandbox, so a node never crashes; where OS
# confinement can't be applied the host fails SAFE (keeps the DuckDB SQL lockdown,
# refuses to serve remote-access jobs unconfined).
[sandbox]
enabled = true # master switch (false = no-op sandbox)
backend = "auto" # auto | none | rlimit | cgroups-seccomp |
# macos-seatbelt | windows-jobobject |
# android | ios
# auto (OS-agnostic): Linux->cgroups+seccomp,
# macOS->Seatbelt, Windows->Job Objects,
# Android->app sandbox+seccomp, iOS->app
# sandbox (in-process only), else rlimit
process_per_job = true # run each FOREIGN job in an OS-sandboxed
# child (needs P2P_JOB_EXEC; else falls
# back to in-process under the lockdown)
egress_mode = "inherit_storage" # inherit_storage | explicit
# inherit_storage derives the allow-list
# from [storage] endpoints/providers
egress_allowlist = [] # extra host[:port] entries (appended in
# inherit_storage mode; the whole list
# in explicit mode)
temp_dir_policy = "ephemeral" # ephemeral | inherit | custom
# temp_dir = "/var/tmp/duckdb-p2p" # REQUIRED when temp_dir_policy = "custom"
[sandbox.limits]
# A field set to 0 = unlimited / inherit (no cap installed for that resource).
mode = "inherit_budget" # inherit_budget | explicit
# inherit_budget: RAM/threads from [budget]
memory_bytes = 0 # RLIMIT_AS (explicit mode only)
cpu_seconds = 0 # RLIMIT_CPU wall of CPU time (0 = off)
max_file_size_bytes = 0 # RLIMIT_FSIZE max created-file size (0 = off)
max_open_files = 0 # RLIMIT_NOFILE fd cap (0 = inherit)
max_processes = 0 # RLIMIT_NPROC cap (explicit mode only)
[limits]
receipt_cache_per_worker = 256 # bounded reputation history per worker
trust_store_capacity = 100000 # LRU-evicted workers
peer_cache_capacity = 50000 # LRU-evicted peers
worker_pool_size = 8 # inbound job concurrency semaphore
connection_pool_size = 1024
# -----------------------------------------------------------------------------
# Blockchain economic / settlement layer (TON) — see docs/BLOCKCHAIN_ECONOMICS.md
# -----------------------------------------------------------------------------
# OFF by default: with enabled = false the node behaves exactly as today — every
# job is FREE, touches NO chain (no escrow/stake/anchor/fees) — yet is STILL
# scored (quorum/canary verification + signed receipts + reputation). Settlement
# and scoring are decoupled: turning the chain off never turns scoring off.
#
# Per-job payment mode resolves (highest precedence first):
# 1. per-call SQL override p2p_query(..., payment => 'free'|'paid'|'auto')
# 2. data-class policy ('auto' => public:free, internal/sensitive:paid)
# 3. economics.default_payment (below)
# 4. economics.enabled (false => always free, no chain)
[economics]
enabled = false # master switch (false = free, no chain)
settlement = "noop" # "noop" | "mock" | "channel" | "onchain" (SQL: noop|mock|ton)
custody = "noncustodial" # v1: code-governed contracts, no platform wallet
accounting_unit = "ton" # v1: price AND settle natively in TON (no oracle)
chain = "ton"
network = "testnet" # "testnet" (safe default) | "mainnet" (real funds)
mainnet_confirmed = false # explicit opt-in required to use mainnet
default_payment = "auto" # free | paid | auto (per-call overridable)
# fee_recipient = "EQ...treasury" # platform fee-recipient (REQUIRED once enabled = true
# AND settlement = "channel"|"onchain")
# Per-network settings — keep BOTH networks configured at once; the active one is
# selected by `network`. Manage these via SQL: p2p_contracts(...) / p2p_wallet(...).
# RPC/explorer default per network (testnet -> testnet.toncenter.com / testnet.tonviewer.com;
# mainnet -> toncenter.com / tonviewer.com) and are overridable here.
# [economics.testnet]
# rpc = "https://testnet.toncenter.com/api/v2/"
# explorer = "testnet.tonviewer.com"
# api_key_file = "/path/outside/repo/testnet.api_key" # 0600 file ref, never the raw key
# [economics.testnet.contracts]
# stake_vault = "kQ..."
# job_escrow = "kQ..."
# record_anchor = "kQ..."
# global_params = "kQ..." # platform-wide params (§12); address is STABLE (edited in place)
# [economics.testnet.wallet]
# address = "kQ..."
# mnemonic_file = "/path/outside/repo/testnet.mnemonic" # 0600 file ref, never the raw mnemonic
# [economics.mainnet] … same shape …
# Pricing (role-appropriate): providers advertise unit_price; requesters cap with max_bid.
[economics.pricing]
unit_price = 0 # whole TON per reference unit (0 = free)
max_bid = 0 # whole TON budget cap (0 = no cap)
[economics.stake]
min_stake = 0 # 0 = permissionless/free tier (public only)
min_stake_internal = 100 # per-class minimums (whole TON)
min_stake_sensitive = 1000
stake_cap = 100000 # ranking ceiling (anti-centralization)
unbonding_secs = 604800 # MUST be >= slashing.challenge_window_secs
receipt_jetton = true # mint 1:1 TEP-74 stake-receipt jetton (§8.5)
receipt_transfer_locked = true # receipt non-transferable while bonded (anti-exit)
[economics.ranking]
w_quality = 0.6
w_stake = 0.30 # diminishing + capped (StakeFactor, §5.2); doubled so
# stake has real pull — safe because it is reliability-gated
w_price = 0.25
stake_reliability_floor = 0.5 # stake earns ranking credit only as a node's verified-success
# rate clears this floor (0 below it, ramps to full at 1.0), so
# stake amplifies reliable nodes and never rescues bad ones
exploration_rate = 0.0 # cold-start ε (§5.2/§6): 0 = pure exploitation;
# a small value (e.g. 0.1) samples new honest nodes
exploration_saturation = 20 # obs count at which the exploration bonus hits 0
[economics.quality] # provider quality score Q weights (§4.1)
w_success = 0.5
w_latency = 0.2 # size-normalized latency (allowance scales with bytes)
w_throughput = 0.2 # throughput-as-rate (bytes/ms), log-scaled
w_completion = 0.1
latency_ref_ms = 5000
bytes_ref = 1073741824 # 1 GiB reference for log throughput scaling
throughput_ref_bytes_per_ms = 0 # 0 => derive ref rate from bytes_ref / latency_ref_ms
[economics.reputation] # confidence-aware reputation priors (§4.1/§7.3)
prior_alpha = 1.0 # Beta pseudo-successes
prior_beta = 2.0 # Beta pseudo-failures (pessimistic about thin history)
confidence_z = 1.96 # Wilson lower-bound z (≈95%); 0 = posterior mean
[economics.selection]
n_public = 3 # default N for public/low-value jobs
n_default = 5 # default N for internal/high-value jobs
n_max = 10 # ceiling on requester overrides
requester_overridable = true
checksum_min = 3 # min matching results to accept (safety floor)
checksum_allow_degraded = true # if < checksum_min respond, proceed + flag
[economics.fees]
platform_fee_pct = 0.15 # phi: platform/admin cut (15%)
verification_surcharge_pct = 0.05
participation_commission_frac = 0.05 # kappa: fixed cut of winner payout per agreeing node (5%)
bonus_aggressiveness = 0.5 # rho: escrow slack funding the perf bonus
lambda_quality = 0.5 # blend of quality vs speed in the bonus
lambda_speed = 0.5
[economics.slashing]
slash_wrong_result_pct = 0.15
slash_cheat_pct = 1.0
slash_downtime_pct = 0.02
slash_equivocation_pct = 0.5
slash_failed_commitment_pct = 0.1 # FINE for a broken commitment: accepted a PAID
# job then failed to deliver a valid result while
# the job was feasible (quorum reached / another
# node delivered). Distinct from a wrong-result slash.
challenge_window_secs = 86400 # optimistic-settlement window
slash_to_challenger = 0.4 # split of slashed funds (must sum to 1.0)
slash_to_redundancy = 0.3
slash_to_burn = 0.2
slash_to_treasury = 0.1
[economics.records]
epoch_secs = 60 # how often the receipt Merkle root is anchored
anchor_quorum_pct = 0.66 # stake-weighted signers needed to accept a root
# -----------------------------------------------------------------------------
# Anti-abuse / robustness layer (architecture "Abuse resistance")
# -----------------------------------------------------------------------------
# Defends the grid against reputation-griefing and other abuse. Defaults preserve
# today's behavior where a change would be observable: the scoring-altering pieces
# (requester-trust weighting, pre-flight cost gating, free-mode rate limiting,
# auto-blocking, gossip peer scoring) default OFF; only the always-safe pieces
# (provable-fault attribution and non-determinism detection, which never ADD a
# penalty) default ON. Deny-lists are managed via SQL: p2p_block / p2p_unblock /
# p2p_blocklist (persisted to blocklist.toml).
[antiabuse]
enabled = true # master switch (false = disables every sub-mechanism)
[antiabuse.fault_attribution]
# Penalize a provider ONLY for provable PROVIDER fault (result disagrees with a
# verified quorum, downtime, equivocation). Requester/job-caused failures
# (infeasible / too expensive / resource-exceeded / malformed / missing data)
# apply ZERO provider penalty. If >= job_consensus_fraction of the selected
# providers fail the SAME way with no quorum, blame the JOB, not the providers.
enabled = true
job_consensus_fraction = 0.67
[antiabuse.requester_trust]
# Trust-weighted score impact ("newer sender -> less effect"): a job's effect on
# a provider's score is multiplied by w(requester) in (0,1] from the requester's
# own reputation + age. New/unproven requester => w ≈ 0 (esp. for negatives);
# established => w -> 1. Asymmetric (gates negatives hardest). Primary defense
# against the heavy-query reputation-griefing attack. OFF by default (when off,
# every requester has weight 1.0 — today's behavior).
enabled = false
negative_floor_weight = 0.0 # floor weight for a brand-new requester's PENALTY
positive_floor_weight = 0.5 # floor weight for its reputation CREDIT (asymmetric)
age_saturation = 50 # requester observations at which weight saturates to 1.0
[antiabuse.cost_gate]
# Pre-flight cost gating at admission: the worker rejects an over-budget query up
# front (a rejection is NOT an execution failure and never affects score). OFF by
# default.
enabled = false
max_cost_hint_rows = 0 # reject offers with cost_hint_rows above this (0 = no cap)
max_working_set_factor = 1.0 # reject when est. peak working set > per-job mem * factor
[antiabuse.nondeterminism]
# Detect non-deterministic queries (random(), now()/current_*, unordered LIMIT)
# that can't reach a stable quorum hash, mark the job non-verifiable, and apply
# no provider penalty. ON by default (deterministic queries are unaffected).
enabled = true
[antiabuse.free_rate_limit]
# Per-requester-identity rate limiting for FREE jobs (anti-spam). Paid jobs are
# prioritized and bypass this limiter. Optional small PoW for free requesters.
# OFF by default.
enabled = false
max_free_per_window = 30
window_secs = 60
max_tracked_requesters = 10000 # bounded, LRU-evicted
require_pow_bits = 0 # optional PoW difficulty for free requesters (0 = none)
prioritize_paid = true
[antiabuse.blocklist]
# Local deny-list policy. Entries are managed via SQL (p2p_block/p2p_unblock/
# p2p_blocklist) and persisted to blocklist.toml. This governs the AUTOMATIC
# triggers and which external sources are honored. Each node decides on its own —
# no central authority.
auto_block_enabled = false # auto-block a worker below the trust floor
auto_block_trust_floor = 0.0 # trust floor for auto-blocking (0 = disabled)
honor_gossip_signals = false # act on signed, gossiped abuse signals
honor_global_params = false # consult the on-chain GlobalParams governance blocklist
[antiabuse.gossip]
# Eclipse / gossip hardening for the libp2p discovery overlay.
peer_scoring = false # gossipsub peer scoring (penalize misbehaving peers)
diverse_bootstrap = true # prefer a diverse bootstrap/relay set (anti-eclipse)
| Statement | What it does |
|---|---|
| SELECT * FROM p2p_info(); | Node identity, version, and connected peer count — the health check. |
| SELECT * FROM p2p_query('SELECT …', data_class:='internal'); | Run a query across the grid, tagging the data sensitivity class. |
| CALL p2p_share('view_name', 's3://…'); | Publish a named logical view backed by an object-store path. |
| SELECT * FROM p2p_join(…); | Distributed join across remote shared datasets. |
| CALL p2p_set('transport.compression', 'zstd'); | Override a config key at runtime (call-layer precedence). |
| CALL p2p_network('testnet'); | Switch the settlement network; mainnet requires explicit opt-in. |
Play money. Stakes, escrow and slashing all run, but no real-value funds are ever at risk. The default for development.
Real funds. Disabled unless explicitly enabled, so a stray config can never accidentally move value.
Switching to mainnet requires an explicit opt-in — set network = "mainnet" and flip mainnet_confirmed = true (now false). There is no implicit upgrade path from testnet.
- DataFormatMaps a format to the DuckDB extensions it needs (parquet, delta, iceberg, …) and loads them on demand.
- StorageProviderTurns a ScopedCredential into a CREATE SECRET. Impls: S3, Azure, Gcs, Https, Local.
- query engineSwap mock ↔ a locked-down DuckDB executor, feature-gated at build time.
- settlementPluggable economics backend: noop, mock, or the on-chain TON layer.
Notes
configEvery key above is validated on load; an unknown key or out-of-range value fails fast at startup rather than silently degrading at runtime.