Uma infraestrututa para construir uma nova internet
  • Go 96.4%
  • Shell 3.2%
  • HTML 0.3%
Find a file
Ruben M. Damião a87032fb3b relay/relaytest: a stand-in validator, for testing below a relay
Upstream serves the validators' sync protocol on a real socket: real
CommitBlocks of brisa's state types with whatever actions a test puts in,
drops, and checksum confirmations (as an upstream relay sends them).
StartRelay runs a real relay over it. So a protocol's tests can run the
whole chain below the validators — relay, social nodes, children — with
every link real and the drops and confirmations where the test wants them.

The relay's own derivation of drops from the blocks needs the calendar and
signed pools and is not exercised: a relay below an Upstream passes on what
it is told.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:47:39 -03:00
chain social: the checkpoint is a photograph, not a rebuild under the lock 2026-09-26 15:57:29 -03:00
cmd relay: verify every block's proof against the height's committee, on by default 2026-09-24 22:34:05 -03:00
crypto crypto/dh: X25519 key agreement and a sealed box, back from breeze 2026-09-27 19:55:46 -03:00
deploy/hetzner deploy/hetzner: RELAY_HOST and FROM_HERE, for clients run from this machine 2026-09-25 21:37:56 -03:00
docker swell+cmd: a node announces an address, and hostname goes back to choosing the transport 2026-09-10 11:59:25 -03:00
docs chain+swell+node/swell+docs: a dropped window is a question to ask, not ten minutes to wait 2026-09-21 23:00:58 -03:00
network socket: SignedConnection.Shutdown runs once 2026-09-28 22:47:39 -03:00
node relay/relaytest: a stand-in validator, for testing below a relay 2026-09-28 22:47:39 -03:00
papirus papirus: HashSet.CloneAsync, a copy that runs alongside queries 2026-09-25 00:35:44 -03:00
scripts Migrating existing votuporanga references to brisa. Filling the gap on overall documentation 2026-04-05 21:13:04 -03:00
state chain, swell: a late action no longer sinks its pool, and a refused pool goes 2026-09-25 21:29:43 -03:00
swell chain, swell: a late action no longer sinks its pool, and a refused pool goes 2026-09-25 21:29:43 -03:00
testnet testnet: window_slots 140, so the four-node testnet starts again 2026-09-25 20:23:54 -03:00
util util: a strict Reader, and the TokenCiphers and LongByteArray codecs fixed 2026-09-28 22:26:46 -03:00
.gitignore docs: the session notes move under docs/temp, where the ignore rule holds 2026-09-14 01:47:30 -03:00
brisa.go Migrating existing votuporanga references to brisa. Filling the gap on overall documentation 2026-04-05 21:13:04 -03:00
docker-compose.yml swell/brisa: per-window network, brisa package, weighted BFT quorum 2026-04-09 14:53:18 -03:00
Dockerfile swell/brisa: per-window network, brisa package, weighted BFT quorum 2026-04-09 14:53:18 -03:00
go.mod go.mod: go 1.27, and nothing in the tree needed it yet 2026-08-27 23:37:18 -03:00
go.sum This is a rewrite of the codebase at github.com/freehandle/breeze together with github.com/freehandle/papirus 2026-04-02 23:29:19 -03:00
LICENSE relay+social: window-aware checksum confirm, dynamic pool update, and automatic rollback 2026-04-12 16:57:45 -03:00
Makefile Remove node/poa: swell is the only consensus path 2026-08-01 18:01:15 -03:00
README.md docs: the repository publishes specifications, and keeps the rest to itself 2026-09-14 16:52:13 -03:00
TESTNET.md swell+cmd: a node announces an address, and hostname goes back to choosing the transport 2026-09-10 11:59:25 -03:00

Brisa

Brisa is a Byzantine Fault Tolerant blockchain with stake-weighted validator selection, window-based checksum consensus, and coordinated disaster recovery.

Building

make build     # builds all binaries into the project root
make test      # runs all tests
make clean     # removes built binaries

Individual binaries:

make brisa-node
make relay-node
make gateway-node
make wallet

Node Types

Brisa networks consist of three node types. Each is a separate binary in cmd/.

Node Binary Role
Validator brisa-node Participates in BFT consensus, produces blocks
Relay relay-node Distributes blocks from validators to clients
Gateway gateway-node Accepts user actions, confirms them against validators

Swell is the only consensus path, from a single validator up to N. An earlier single-authority (Proof of Authority) binary, poa-server, was removed on 2026-08-01 once swell with one validator covered its ground. See Sizing the validator set.

Sizing the validator set

Swell is Byzantine fault tolerant with a 3f+1 threshold — the BFT quorum rule is weight > 2*TotalWeight/3 (swell/bft/ballot.go). With equally weighted validators:

Validators Votes needed Failures tolerated
1 1 0
2 2 0
3 3 0
4 3 1
5 4 1
6 5 1
7 5 2

The counter-intuitive part: 2 or 3 validators are strictly worse than 1. They tolerate no failure at all — every node must vote — but there are now two or three machines that can die instead of one. The useful jumps are 1 → 4 and 4 → 7.

  • Development, bootstrap, tests: 1 validator.
  • Production with resilience: 4 validators, plus at least 1 relay for block distribution. The fourth node only has to vote, so it can be a small machine.
  • Never: 2 or 3.

Running a single validator

Both pool knobs must be set — ValidateProtocol rejects a bft_pool_size larger than num_validators, so leaving the default of 10 against num_validators: 1 fails at startup rather than at the first window boundary. window_slots must also stay at or above WindowToPoolRatio (20) × num_validators, which puts the floor at 20 slots.

node:
  genesis: true                # first start only
  db_path: "./data"

protocol:
  window_slots: 300            # >= 20 x num_validators
  num_validators: 1
  bft_pool_size: 1             # must not exceed num_validators
  minimum_stake: 1000
  block_reward: 10

validators: []                 # no peers

The single validator is its own BFT quorum (weight > 2*1/3 needs 1 vote) and its own checksum majority (threshold len(MintOrder)/2 + 1 = 1). Covered end-to-end by TestIntegration_SingleValidator_TwoWindowBoundaries.

The 20-slot floor is a validation minimum, not a setting to use. Keep window_slots at the default of 300 whatever num_validators is — a single validator gets no benefit from a shorter window and pays for it, because window_slots also sizes the budget for the checkpoint state copy.

Each window the chain deep-copies the state at the 50% milestone, and that copy has to be delivered in time for the naked checksum statement to reach a committed block header before the 90% milestone — roughly 40% of a window. The copy is paced at about 100 MiB/s by design, so the budget in bytes is 0.4 × window_slots × block_interval × 100 MiB/s. At the defaults (300 slots, 1s blocks) that is ~120s, comfortably over 7 GiB of state; at the 20-slot floor it drops to ~8s, and with sub-second blocks it goes away entirely.

Missing it is not a crash and not silent corruption: the validator does not attest that window, so a lone validator finds no checksum majority and rolls back to the previous checkpoint, losing roughly a window and a half of blocks and trying again. That recovers a transient stall (a load spike, a long GC pause). It does not recover a state that is simply too large for the window configured — that repeats every window, logging a warning each time.

Keep the validator's deposit above minimum_stake. The pool lottery skips any candidate below the bar. With several validators the others absorb it; with exactly one, the pool comes back empty and the chain ends — no height has a designated leader, so nothing can be proposed and nothing can restore the stake. Recovery is disaster recovery, not a deposit.

The node does not die quietly. It finishes the current window, refuses to promote the empty pool at the boundary, and halts: block production stops but the web admin and the sync gateway stay up, reporting halted: true, halt_reason: pool_exhausted on /api/status, so the chain can still be read for recovery.

The ERROR at the 90% milestone is an alarm, not a save. By the time it can fire, the outcome is already decided: pool determination reads the state snapshot taken at the 50% checkpoint, so a deposit landing anywhere in the second half of the window is invisible to that lottery.

Note that TotalWeight counts slots, not nodes: the mint order is a slot list and a validator appearing k times weighs k. With slots spread evenly the arithmetic is the same; with an uneven spread, weight is what counts.

Clients should connect to relays, not to validators. Relays are the entry point and the read fan-out; validators stay lean.

Deploying a Validator Network

1. Generate keystores

On each validator machine:

./brisa-node -gen-keystore -keystore validator.json
# Enter a password, note the printed token (64-char hex)

2. Create configuration

Generate a sample config and edit it:

./brisa-node -gen-config   # writes brisa-node.example.yaml
cp brisa-node.example.yaml brisa-node.yaml

Each validator's config lists all other validators:

node:
  db_path: "./data"
  genesis: false               # true ONLY on the first node, first start

network:
  consensus_port: 9000
  sync_port: 9100              # defaults to consensus_port + 100
  action_gateway: ":9001"
  web_admin: ":8080"

credentials:
  keystore: "./validator.json"
  password_env: "BRISA_PASSWORD"

validators:
  - token: "<validator-2-token>"
    addr: "192.168.1.11:9000"
  - token: "<validator-3-token>"
    addr: "192.168.1.12:9000"

protocol:
  window_slots: 300            # 5 min windows @ 1 block/s
  num_validators: 15
  bft_pool_size: 10
  block_reward: 10
  minimum_stake: 1000

3. Start the genesis node

Exactly one node starts with genesis: true to create the genesis block. This records the genesis timestamp; all other nodes receive it via sync.

export BRISA_PASSWORD=<password>
./brisa-node -config brisa-node.yaml

After the first start, set genesis: false for all subsequent runs.

4. Start remaining validators

Each additional validator either:

  • Syncs from a peer (recommended for first start):
node:
  sync_peer: "192.168.1.10:9100"
  sync_peer_token: "<genesis-node-token>"
  • Or connects directly if the network is young enough that replay from genesis is fast.
export BRISA_PASSWORD=<password>
./brisa-node -config brisa-node.yaml

5. NTP clock correction

By default, validators query pool.ntp.org at startup for clock correction. All validators must agree on block slot times, so NTP is strongly recommended.

./brisa-node -ntp-server pool.ntp.org    # default
./brisa-node -no-ntp                      # use system clock (testing only)

Deploying a Relay Node

Relays distribute blocks from validators to downstream clients (gateways, social nodes, other relays). They do not participate in consensus.

./relay-node -example > relay.yaml
# Edit relay.yaml
export RELAY_PASSWORD=<password>
./relay-node -config relay.yaml

Minimal config:

relay:
  listen: ":9200"
  cache_size: 100

credentials:
  keystore: "./relay.json"
  password_env: "RELAY_PASSWORD"
  generate: true

upstreams:
  - token: "<validator-token>"
    addr: "192.168.1.10:9100"

History node mode

Set data_dir to persist blocks to disk. With bootstrap_history: true, the relay fetches missing history from upstreams at startup.

relay:
  listen: ":9200"
  cache_size: 200
  data_dir: "./relay-blocks"
  bootstrap_history: true

Size cache_size for at least 2 consensus windows (2 × window_slots) to ensure rollbacks never reach disk.

Deploying a Gateway Node

Gateways accept user actions (transactions), forward them to validators, and confirm them by watching the block stream. They are the entry point for application clients.

./gateway-node -example > gateway.yaml
# Edit gateway.yaml
export GW_PASSWORD=<password>
./gateway-node -config gateway.yaml
gateway:
  listen: ":9300"
  min_confirmations: 1
  confirmation_depth: 3

credentials:
  keystore: "./gateway.json"
  password_env: "GW_PASSWORD"
  generate: true

validators:
  - token: "<validator-token>"
    sync_addr: "192.168.1.10:9100"
    action_addr: "192.168.1.10:9001"

The gateway must be listed in each validator's authorized_nodes for action submission to be accepted.

Network Topology

                    ┌──────────┐
                    │ Validator│
              ┌────►│    1     │◄────┐
              │     └──────────┘     │
              │          ▲           │
         consensus    consensus   consensus
              │          │           │
              ▼          ▼           ▼
        ┌──────────┐ ┌──────────┐ ┌──────────┐
        │ Validator│ │ Validator│ │ Validator│
        │    2     │ │    3     │ │    ...   │
        └────┬─────┘ └────┬─────┘ └────┬─────┘
             │sync        │sync        │sync
             ▼            ▼            ▼
        ┌─────────────────────────────────┐
        │          Relay Node(s)          │
        └──────────┬──────────┬───────────┘
                   │          │
              ┌────▼───┐ ┌───▼────┐
              │Gateway │ │ Social │
              │  Node  │ │  Node  │
              └────────┘ └────────┘

Validators form a fully-connected BFT mesh on the consensus port. Each validator exposes a sync port for relay subscriptions. Relays fan out blocks to gateways, social nodes, and other relays.

Disaster Recovery

When the entire network stops (no BFT quorum can form), operators coordinate a restart out-of-band. Each operator agrees on:

  1. Snapshot hash — which snapshot to restart from
  2. Restart time — when consensus begins (RFC 3339)
  3. Validator pool — who participates in the new network

Add to each validator's config:

disaster_recovery:
  snapshot_hash: "a1b2c3..."
  restart_time: "2026-04-15T14:00:00Z"
  pool:
    - "<validator-1-token>"
    - "<validator-2-token>"
    - "<validator-3-token>"

Start each validator before restart_time. The node loads its local snapshot, validates the hash, and waits until the agreed time to begin consensus.

Relays do not need DR configuration — they receive a SyncMsgDRReset from validators on reconnect, clear their cache, and propagate the signal downstream automatically.

After successful restart, remove the disaster_recovery section from config for subsequent normal runs.

Recovery Levels

Outage Duration Mechanism Manual?
Single node down Any BFT tolerance — requires 4+ validators, see Sizing the validator set No
Short network stall < 1 window Skip blocks + stagnation No
Medium outage > 1 window Auto clock recovery (maybeAddRecoveryEntry) No
Checksum divergence Window boundary Rollback to last checkpoint No
Full network stop Any Disaster recovery (coordinated restart) Yes

Further Reading

docs/ carries the specifications and nothing else. Plans, surveys and measurement write-ups are working notes: they record how a decision was reached rather than what the protocol is, they go stale against the code by design, and they are not part of what this repository publishes.