Skip to content

Releases: Project-DSpace/darwin-node

Storage node v0.23.0+ds.20

Choose a tag to compare

@github-actions github-actions released this 28 Oct 11:38
Immutable release. Only release title and notes can be modified.

Storage node v0.23.0+ds.20 for Linux x86_64 (Ubuntu 24.04 or newer).

Running nodes install it by themselves within the staged-rollout window.
The archive is signed with ML-DSA-65; check it with:
ant-keygen verify --key release-signing-key.pub --input ant-node-cli-linux-x64.tar.gz --signature ant-node-cli-linux-x64.tar.gz.sig

The archive also contains LICENSE-MIT and LICENSE-APACHE, plus THIRD-PARTY-NOTICES.txt and RUST-STD-COPYRIGHT.html with the licences and copyright notices of the third-party code in the binary and of the tools used to build it.

What changes

ds.20 brings two changes. It changes the price of storage, and it adds a third way for a client to pay for records: root payments, which pay for a whole wave of records in one transaction (settlement version 3). Dan, 2026-10-28: "no i want to combine them" (decisions log).

The price change sends no new kind of message, field or reply: what changes is the price curve nodes quote, and which prices storers accept. Dan, 2026-10-28: "make chunk price 100 darwin = 1gb" and "update to D = 2000". Root payments are new: a client released before ds.20 is quoted and paid as before on every node, apart from the new curve's prices once nodes switch (see "Who is cut off"). Merkle (batch) payments are still refused.

The price of storage

The new curve

  • The formula is upstream's, price = BASELINE + K × (n ÷ D)², with n the records a node has committed to: BASELINE = 0.1396736 DARWIN, K = 9 × BASELINE (1.2570624 DARWIN), D = 2,000 records.
  • Where the numbers come from. 100 DARWIN per 10⁹ bytes × 4,190,208 bytes per full chunk ÷ 3 (a chunk pays 3 × the median quote) = 0.1396736 DARWIN per quote at an empty node. K stays 9 × BASELINE as upstream, and with D = 2,000 the price reaches 10 × at 2,000 records instead of 6,000.
  • What a GB of full chunks costs: exactly 100 DARWIN on empty nodes, 1,000 DARWIN at 2,000 records, 8,200 DARWIN at 6,000 records. Smaller records pay by their size class as in ds.19.

The switch

  • Nodes quote the new curve from 2026-10-29T00:00:00Z (unix 1793232000; a placeholder, see "Before tagging"), by the node's own clock when it makes the quote. Before then a ds.20 node quotes the previous curve, Autonomi's × 32 (0.125 DARWIN at an empty node, D = 6,000), exactly as ds.19 does.
  • ds.20 storers accept a quote on either curve, each checked with today's rules: the price within the band its committed key count allows, the size-class weights, and the 3 × median × weight requirement. A payment whose quotes mix the two curves, as around the switch, is accepted.
  • The revenue floor (off unless ANT_PRICE_FLOOR_ENFORCE is set) prices its reference on the curve the paid quote is on, so a payment on the previous curve is never held to the new one.
  • The floor's margins move on the new curve. The new curve rises faster, so at the default 65% tolerance three of fifteen neighbours overstating their record counts make a node that enforces the floor refuse an honest payment at the bottom of the true median's band (on the previous curve it took four). In shadow mode, the default, such a payment is only logged with below_floor=true. Four understating neighbours no longer disarm the floor. Whether the default tolerance is re-derived for the new curve is open for Dan.

Who is cut off by the price change

  • Nodes on ds.19 and older refuse quotes on the new curve, and with them every payment that carries one. Every node needs ds.20 before the switch; nodes install it by themselves.
  • Clients v0.3.11+ds.4 and older refuse new-curve quotes, so they can't pay once nodes switch. Dan accepted this. v0.3.11+ds.5 accepts both curves.

Root payments

How a root payment works

  • One transaction per wave. A client can pay a wave of up to 512 records in one transaction through the root vault, 0xf6675722f443132b045857546E57e35360Bb03b9 on Robinhood Chain. The vault records the whole wave under one key, built from a tree whose leaves each bind a paid quote's hash, its payee's whole address and the amount paid to it. A wave of many records to one payee costs far less gas per record than paying each record alone.
  • Priced as any quote. A root proof's quotes are the same quotes a per-record proof carries, on the same curve: each leaf is the size-class amount of its paid quote, so the new curve and the acceptance of both curves apply to root payments exactly as to per-record ones, and so does the revenue floor, on the paid quote's curve.
  • The proof. A root proof (tag 0x03) carries the same quotes as a per-record proof, plus the paid leaf's amount, its place in the wave, the wave's size and the path of hashes up to the wave's key. The node runs every check a per-record proof gets, rebuilds the leaf from the signed median quote, hashes it up the path, and reads the vault's record under the key, once more a moment later if it reads empty. A malformed or underpaid root proof is refused before anything is read. The amount is held to the same size-weighted rule as a per-record payment.
  • Each proof in its own vault. Root proofs settle in the root vault's records alone, per-record proofs in the payment vault's alone. Merkle proofs are still refused first, before anything else is read.
  • Built in for the production network. A node paid in DARWIN through its payment vault knows the root vault without any setting, so a node whose binary was upgraded alone knows it too. A custom network gives its own with --evm-root-vault (ANT_EVM_ROOT_VAULT); with none, root proofs are refused. A production node whose setting names a different root vault warns at start. The node's payment vault, which the image and browsers check, is unchanged.

Settlement version 3

  • Version 3 means "root proofs accepted". A node that knows a root vault quotes a version-3 request as it quotes a version-2 one, priced by size the same way. A node with no root vault, like any node before ds.20, answers a version-3 request that it must be updated, naming version 2, before quoting, so an up-to-date client never pays through the root vault to a node that can't check the payment.
  • Nothing older is turned away. The lowest version a node takes stays 1.

Root-paid records between nodes

  • Slim root proofs. A root proof is forwarded to other nodes only in its slim form, as a per-record proof is since ds.19: the quotes at the median price (the paid one is always among them), the transaction hashes, and the leaf whole, with no commitment sidecars.
  • A refusal at payment verification is excused. A node that stores a root-paid record leaves out of the record's possession check a close-group peer that refuses its offer at payment verification, as a node that can't check a root proof does, whether or not the proof holds a quote from that peer. Neighbour sync places the record with it later. A peer that refuses for any other reason (out of range, out of space, a failed write) is checked as before, as a refusal of a per-record offer is, and the peer whose quote was paid is always checked: a node paid for a record it then doesn't take is charged. A peer other than the paid one could word any refusal as a payment-verification one to skip that one check, since the reason is its own word; it is still checked at any later possession check.
  • Refusals of root-paid offers are answered. Offers go between nodes one-way. ds.20 answers a one-way offer only to refuse a root-paid one, so the sender can excuse it; every other answer to a one-way offer is still not sent, as in ds.19. A ds.19 node answers no one-way offer, so a ds.19 peer that refuses a root-paid offer stays in its possession check.
  • A "full" peer. The possession check's charge of a full peer that was paid for the record reads a root proof's one paid quote.

Listings

  • Both vaults. The listing service reads a listed record's payment from a per-record or a root proof, asking each vault for its payment logs in its own query, both in one batch, or each alone from an RPC that takes no batches. darwin-listings is now 0.4.0.

New counters

  • The traffic lines count quote requests by settlement version, settled proofs by tag, and the root vault's record read as a read of its own.

Before tagging

  • Set PRICE_SWITCH_AT from the tag time (decisions log, 2026-10-28: "PRICE_SWITCH_AT must fall after the combined release is on our nodes, so it is set when ds.20 is tagged"): at least the release's publication + the staged rollout (24 h by default) + one upgrade check (1 h) + the canary period and approval gate, and after darwin-client v0.3.11+ds.5 is deployed on the gateway and the CLIs. Change the constant in src/payment/pricing.rs, its pin the_switch_is_the_tagged_time, README.md and these notes together.

Updating the image (Docker)

ds.20 changes nothing in the image. Nodes install ds.20 by themselves, with everything above, whether or not you pull.

Storage node v0.23.0+ds.19

Choose a tag to compare

@github-actions github-actions released this 28 Oct 08:36
Immutable release. Only release title and notes can be modified.

Storage node v0.23.0+ds.19 for Linux x86_64 (Ubuntu 24.04 or newer).

Running nodes install it by themselves within the staged-rollout window.
The archive is signed with ML-DSA-65; check it with:
ant-keygen verify --key release-signing-key.pub --input ant-node-cli-linux-x64.tar.gz --signature ant-node-cli-linux-x64.tar.gz.sig

The archive also contains LICENSE-MIT and LICENSE-APACHE, plus THIRD-PARTY-NOTICES.txt and RUST-STD-COPYRIGHT.html with the licences and copyright notices of the third-party code in the binary and of the tools used to build it.

What changes

ds.19 nodes work next to ds.18 and older nodes. They send no new kind of message, field or reply: every message ds.19 sends between nodes is one that every earlier release already reads and handles. What changes is how much a stored record costs the network in traffic between nodes: about 11.6 MB to about 1.7 MB for a tiny record, and about 180 MB to about 1.7 MB for a full 4 MiB chunk. No payment rule changes: what a client pays, and what every store and notice check requires, are as in ds.18.

Slim proofs between nodes

  • What a node forwards. A client's PUT carries every quote it was given, each with its commitment sidecar, about 57 KB. Once a storer has accepted the record, the nodes it tells about it need only what their own check reads: the quotes at the median price (normally one) and the payment's transaction hashes. Notices, offers and pointer offers now carry only those, with no sidecars and every byte field written as a byte string: about 5.5 KB.
  • Every release reads it. The proof's tag byte and field order are unchanged, so every release decodes a slim proof with the decoder it already has, and its payment check reads the same median quote and the same amount. A proof the storer can't slim (a merkle proof, one that doesn't decode, one whose quotes fail the payment check's own local checks, or one for another record) is not forwarded at all.

Holding notices

  • "I hold it." A storer that has stored a record and written the key into its own paid list now tells the rest of the record's close group with a payment notice whose proof is empty: a holding notice. Every release ignores a notice with an empty proof, so it costs the receiver nothing, and records nothing there.
  • A node that already held the record sends one too, when it answers a PUT "already stored": once per key in 20 seconds, for a key in its own paid list that it is responsible for.
  • The rest of the paid group gets the slim proof at once, as before. A storer whose own paid-list write failed sends the slim proof to the whole paid group, close group included.

Offers only where they're needed

  • The hold. After a store, a storer holds its offers for up to 10 seconds, the client's own wait for a store answer, so the other storers' holding notices arrive first. The hold ends sooner once every close-group peer has been heard from or is marked full.
  • When it ends, each close-group peer the storer didn't hear from gets the notice with the slim proof at once, never behind back-pressure, and a peer not marked full is then offered the record. Normally every peer was heard from, and nothing more is sent. A peer heard from is one that sent this node, within 20 seconds, a holding notice, a notice with a proof, or an offer of the record.
  • The close group here is the one ds.17 widens: the 7 nearest, one more for each peer marked full among them, at most 20. A peer marked full gets the notice with the slim proof and no offer; the nearest beyond stand in for it.
  • Every peer is still checked. Every peer of the unwidened close group that the storer skipped or offered still gets its possession check: a skipped peer from when the hold ends, an offered peer from when its offer is dispatched. The stand-ins are offered the record and not checked, as in ds.17. A peer marked full that the record's payment didn't pay keeps ds.17's waiver.
  • A hint for sending only. Being heard from never counts as proof that a peer holds a record: not for erasure, repairs, the paid-list quorum, audits or the "full" waiver. Those still rest on possession checks.
  • When the node stops, the open holds' notices are sent before it does. A record removed or found unreadable during its hold sends no proof notice: an unreadable one sends its notices once a read finds it.

No answers to one-way offers

  • A fresh offer sent as a one-way message is no longer answered: the sender doesn't wait for an answer, so it was sent for nothing. An offer sent as a request is answered as before.

Possession checks within a byte budget

  • 64 MiB per node. A possession check takes a share of a node-wide budget the size of its record before it reads its copy, and gives it back when its last probe ends. Checks waiting behind a slow peer can no longer hold more record bytes than that; a check waiting for its share holds only its key and its peers.

Payment notices from one storer

  • Each sender's share. A node admits 64 payment notices at once. One sender may now hold up to 48 of them, the pool less 16 kept for senders that hold none. With offers held, the notices a close-group peer still receives come from a storer whose hold ended without hearing from it, one per record, at the pace of the upload, so one storer's notices during an ordinary upload are no longer dropped.

New counters

  • The fresh replication summary (a line on ant_node::replication::traffic, every 5 minutes, cumulative) counts every new path: holding notices and notices with a proof sent and received, offers sent and skipped (heard from, or marked full), how holds ended (early, at the hold time, or at the cap on held proofs), close-group peers left unserved when a hold ended (always 0), stored writes with no proof to send, proofs the slim builder declined, proofs refused for their form in notices, offers and pointer offers, possession checks scheduled by how each peer was served, checks waiting for the byte budget and the bytes running checks hold, confirmed possession failures on skipped peers, proof refusals, notices dropped at admission, and answers to one-way offers not sent.
  • Sends per recipient. New lines on the same target, every 5 minutes, add <message>_sent_count and <message>_sent_bytes for every message between nodes, counted once per recipient and per attempt, beside the per-encoding <message>_tx_* fields. The "full" notice and the sync pages are counted too.

Updating the image (Docker)

ds.19 changes nothing in the image. Nodes install ds.19 by themselves, with everything above, whether or not you pull.

What ds.17's notes say about images still applies: the nodes status list of full nodes needs ds.17's image or later, and on an image older than the one that ships darwin-nodes, pull the new one (docker compose pull, then docker compose up -d).

Storage node v0.23.0+ds.18

Choose a tag to compare

@github-actions github-actions released this 28 Oct 02:45
Immutable release. Only release title and notes can be modified.

Storage node v0.23.0+ds.18 for Linux x86_64 (Ubuntu 24.04 or newer).

Running nodes install it by themselves within the staged-rollout window.
The archive is signed with ML-DSA-65; check it with:
ant-keygen verify --key release-signing-key.pub --input ant-node-cli-linux-x64.tar.gz --signature ant-node-cli-linux-x64.tar.gz.sig

The archive also contains LICENSE-MIT and LICENSE-APACHE, plus THIRD-PARTY-NOTICES.txt and RUST-STD-COPYRIGHT.html with the licences and copyright notices of the third-party code in the binary and of the tools used to build it.

What changes

ds.18 nodes work next to ds.17 and older nodes. They send no new kind of message: the one change on the wire is a byte per key appended at the very end of a paid-list reply, which older nodes' decoders stop before. A client released before ds.18 is quoted and paid exactly as before, on every node. What is new is a second way to pay, by the size of the record, which clients take up only once no storer before ds.18 is left on the network.

Records priced by size class

  • The weight of a record. A record now weighs the top of its size class as a share of a full 4 MiB chunk: a fixed quarter, plus three quarters scaled by the class top. There are nine classes, with power-of-two tops from 16 KiB to 4 MiB:

    Class top Weight, in 1/1,024 of a full chunk
    16 KiB 259
    32 KiB 262
    64 KiB 268
    128 KiB 280
    256 KiB 304
    512 KiB 352
    1 MiB 448
    2 MiB 640
    4 MiB 1,024

    A full chunk weighs exactly 1 and pays exactly what it paid before. Payments on chain show a record's size only to within a factor of two.

  • Pointers are weighed by their data type at their fixed 5,303 bytes, whatever size a request declares: the 16 KiB class. Paying to create and to update a pointer is unchanged.

  • What a store requires. A client settling under version 2 pays 3 times the median quote times the record's weight. Every store check now requires at least that, with the class taken from the bytes the node holds: a payment for a larger class clears, one for a smaller class is refused, and an overpayment always clears. A full-price payment, as every released client makes, always clears. The amount paid is still read through the proof's receipts and the vault's record; an amount that falls short goes on to the record read, and is never taken as nothing paid.

  • Unchanged: the price curve and its ×32 multiplier, the quote format, the price ceiling, merkle (batch) payments still refused, and every refusal still coming from the vault's record.

Quotes

  • The weight total. Each node adds up its committed records' weights (in full chunks, rounded up) whenever it builds its hourly storage commitment, and once at start for a commitment it reloads. It is held in memory only: the retention file is unchanged, so ds.17 and ds.18 read each other's. The commitment line logs weight_total=, or unknown until it is read.
  • Who is quoted what. A quote request declaring settlement version 2 is quoted at the price of the weight total, or of the key count if that is lower or the total isn't known yet. Every other request, unversioned or version 1, is quoted at the key-count price, as before. The quote still carries the key count.
  • A price band. A quote is now checked against a band, not one exact price: from the price of 259/1,024 of the key count up to the price of the key count. At a key count of 0 the band is the baseline price alone. The shape and key-cap checks, and the cross-check of the key count against the node's pinned commitment, are unchanged.
  • Settlement version 2. Nodes now settle up to version 2 and still accept version 1. A node before ds.18 answers a version-2 quote with StorerUpdateRequired, and the client asks another node.
  • Browsers. The browser listener accepts protocol version 6 for a quote alone, as a version-2 quote at the weight total, beside version 5 for every request, quoted as before. hello lists size_priced_quote. A node before ds.18 refuses a version-6 request before quoting, so a browser never pays a node that can't take a payment by size.
  • Traffic. The chunk traffic summary, every 5 minutes, counts versioned quote requests by the settlement version they declare: quote_v2_settlement_1_count, quote_v2_settlement_2_count and quote_v2_settlement_other_count, and the same for merkle requests, browser quotes included.

Payment notices record the class they paid

  • The paid class. A payment notice carries no bytes, so it is admitted at the smallest class's amount, and the class its settled amount covers is written beside the key in the paid list, as one byte. A class is only ever raised, never lowered. The full class is written as no byte at all, which is how every key admitted before ds.18 reads, and how ds.17 and older nodes keep writing every key. Older nodes never read the byte.
  • Paid-list replies. A node answers a paid-list query with the class each key records, appended at the very end of its reply. A reply from an older node reads as the full class for every key. A node that learns a key from a quorum of paid lists alone records the class a quorum of them record or exceed, not the full class.
  • The check on fetch. When fetched bytes arrive for a key this node knows only from paid lists, and the recorded class is below the class of the bytes, that fetch ends: no other source is tried, no peer is charged, and the paid-list entry and any copy already held stay. A key a majority of holders proved they store is fetched at any size, as before.

The price floor

  • The revenue floor's reference is now the price at the bottom of the median count's band, times the record's weight. It still runs in shadow mode by default; a node that enforces it (ANT_PRICE_FLOOR_ENFORCE) never refuses a correct payment by size, and a full-price payment clears it as before.

Older nodes and payments by size

A node before ds.18 can't check a payment by size anywhere: it refuses the fresh offers and drops the payment notices of a record paid that way, and its close group's possession checks and audits then charge it for a record it doesn't hold, as they charge any node. No node is spared by the release it advertises. So clients keep paying full price, version 1, until no storer before ds.18 is left; until then, ds.18 changes nothing a client pays.

Updating the image (Docker)

ds.18 changes nothing in the image. Nodes install ds.18 by themselves, with everything above, whether or not you pull.

What ds.17's notes say about images still applies: the nodes status list of full nodes needs ds.17's image or later, and on an image older than the one that ships darwin-nodes, pull the new one (docker compose pull, then docker compose up -d).

Storage node v0.23.0+ds.17

Choose a tag to compare

@github-actions github-actions released this 27 Oct 20:15
Immutable release. Only release title and notes can be modified.

Storage node v0.23.0+ds.17 for Linux x86_64 (Ubuntu 24.04 or newer).

Running nodes install it by themselves within the staged-rollout window.
The archive is signed with ML-DSA-65; check it with:
ant-keygen verify --key release-signing-key.pub --input ant-node-cli-linux-x64.tar.gz --signature ant-node-cli-linux-x64.tar.gz.sig

The archive also contains LICENSE-MIT and LICENSE-APACHE, plus THIRD-PARTY-NOTICES.txt and RUST-STD-COPYRIGHT.html with the licences and copyright notices of the third-party code in the binary and of the tools used to build it.

What changes

ds.17 nodes work next to ds.16 and older nodes. They send two new kinds of message, sync pages and the full notice, only to peers whose user agent names ds.17 or later; every other peer is sent what it was sent before. Prices don't change, but a node at the full line no longer quotes for new data.

Neighbour sync is paged

  • The limit it lifts. A node's hints for a neighbour (the keys the neighbour should hold, and the paid-list keys it should track) went in one message of at most 10 MiB, about 327,000 keys. A node holding more could no longer build that message, and its sync stopped.
  • Pages. Between ds.17 nodes, each side's hints now go in pages of up to 8,192 keys (256 KiB), replica hints first. Each page is its own request and answer, the next sent only once the last is answered, up to 256 pages a side, and one exchange at a time with each neighbour.
  • Windows. Hints that come to more than one exchange carries go a window at a time, each exchange carrying the next keys of each list. A window moves on by the keys that actually arrived, so an exchange that ended part way is picked up in the next one.
  • Busy, not faulty. A page the other side can't take now is answered busy, and the requester tries again in a later cycle. Pages carry no trust reporting: silence, a timeout or a busy answer never counts against the neighbour.
  • Bounds. Each page waits at most 30 seconds, an exchange runs at most 2 minutes, and a node holds at most 64 MiB of a neighbour's keys across the exchanges it has asked for.
  • Older peers (ds.16 and before, or a peer whose release isn't known) get a single message, as before, which every release reads.
  • Bootstrap. A node starting up tries again, after 5, 10 and 20 seconds, a neighbour that answered busy, and counts a neighbour as done only once its last page has arrived.
  • Scans of a node's keys, for hints and for the hourly storage commitment, now work from one snapshot of the routing table instead of a lookup for every key.

Full, not faulty

  • The falling floor. Below 10% free, a node may erase records far from it that its nearer peers prove they hold, farthest first, until 15% is free. The reach now grows as free space falls:

    Free space below Records may be erased beyond
    10% 10 times the distance to the node's 7th-nearest peer
    7.5% 5 times that distance
    5% 2 times that distance
    2.5% the edge of the node's own range

    Each step also fires once free space is down to the 1 GiB full line plus 4 MiB, so on a small disk (under about 40 GiB) every step runs before the node is full. Nothing inside a node's own range is ever erased, and every erasure keeps its possession check. ds.16 nodes stop at 10 times, and never erase while the network is small.

  • Free inodes count as free space. The smaller share of free bytes and free inodes drives the steps above, and below 2.5% free inodes a node is at the full line.

  • Erasure in two phases. A record chosen for erasure is first left out of every sync hint, and erased only in a pass a whole sync cycle later. A record leaves a node's range only after 3 days out of it, and that wait now survives restarts (out_of_range.mdb in the node's data folder).

  • Full. A node at the full line with nothing left it may erase is full. It quotes and takes no new data, but keeps serving and proving what it holds, still answers a chunk it holds as already stored, and still takes updates of the pointers it holds. It leaves "full" once 256 MiB above the line is free again.

  • The full notice. A full node tells its ds.17 peers so, renewing the notice every 10 minutes. They send its records to the next nearest node instead, widen their windows around it (at most 20 nodes), and don't charge it for a record it can't take, in one bounded case: its notice is under 40 minutes old, it answered that it lacks the record, the check isn't pinned to its storage commitment, it was never seen proving the record, and, after a write, it wasn't paid for it. Nodes keep the record of keys peers were seen proving across restarts (seen_holders.bin).

  • Quotes at the line. A node at the full line, or full, no longer quotes for new data, so no client pays it for a record it couldn't keep.

  • ds.16 and older nodes don't read the notice, and keep charging a full node until they update.

  • Logs. A node logs Node full: since ... on entering "full", Node full: no longer on leaving it, and Node full: not at start when it restarts with room. These lines are kept at the full line, as warnings are. The hourly prune line adds floor_step=, being_erased=, near_full_peers=, few_holders=, seen_fill=, seen_false= and full=.

  • Filesystem. For a disk of its own, XFS, or ext4 made with -i 4096, keeps inodes from running out before bytes.

Direct addresses before relays

  • A node now dials a peer's verified direct address before its relay. Before, traffic to a reachable node could pass through another node's relay.
  • The order is: a LAN address when the peer is behind the node's own public IP address; a verified direct address; the peer's relay (still skipped when the relay is behind the node's own public IP address); an unverified address; and a LAN address only when nothing else is known.
  • A node behind NAT has no verified direct address, so its relay is still dialled first, with no direct attempt spent before it.
  • The relay switch (ANT_RELAY_SERVING) is unchanged.
  • The change is a patch to saorsa-core 0.29.1, vendored in vendor/saorsa-core, whose own tests now run in the Test workflow.

Updating the image (Docker)

Auto-upgrade replaces only each node's binary. The rest of the container changes only when you pull a new image: docker compose pull, then docker compose up -d. ds.17's image brings:

  • full nodes in nodes status: docker exec storage-node nodes status lists each node that is full, since when, and whether bytes or inodes put it there.

Without the pull, the nodes still install ds.17 by themselves, with everything above that is part of the node: paged sync, the falling floor, "full, not faulty" and direct dialling. Only the nodes status list of full nodes needs the new image; without it, look for Node full: in the node logs (docker compose logs | grep 'Node full:').

What ds.16's notes say about images older than the one that ships darwin-nodes still applies: on such an image, pull the new one.

Storage node v0.23.0+ds.16

Choose a tag to compare

@github-actions github-actions released this 27 Oct 04:05
Immutable release. Only release title and notes can be modified.

Storage node v0.23.0+ds.16 for Linux x86_64 (Ubuntu 24.04 or newer).

Running nodes install it by themselves within the staged-rollout window.
The archive is signed with ML-DSA-65; check it with:
ant-keygen verify --key release-signing-key.pub --input ant-node-cli-linux-x64.tar.gz --signature ant-node-cli-linux-x64.tar.gz.sig

The archive also contains LICENSE-MIT and LICENSE-APACHE, plus THIRD-PARTY-NOTICES.txt and RUST-STD-COPYRIGHT.html with the licences and copyright notices of the third-party code in the binary and of the tools used to build it.

What changes

Every change in this release is local to each node: nodes send each other no new messages, and ds.16 nodes work next to ds.15 nodes. Prices and quotes don't change.

Payment checks spare chain reads, and answer a store in time

  • Payment facts. A node keeps up to 16,384 payment facts by quote hash: the amount paid and the payee, and only for payments the vault recorded. An empty record, a missing receipt, an error or a timeout is never kept. A fact that pays the quote settles it with no further read. A quote with no fact, or one its fact doesn't pay, is read from the vault's record (completedPayments) as before, and every refusal still comes from that read.
  • Receipts. The node reads the receipts of the first 4 distinct transactions a proof names, and counts only the vault's DataPaymentMade logs in a receipt whose transaction succeeded. One receipt can settle a whole wave of chunks.
  • Batched reads. Reads of one record at once share a single call while it is in flight. Record reads made while another is in flight go together as one JSON-RPC batch of up to 32 calls. An RPC that refuses batches, or leaves calls out of its answer, is read one call at a time.
  • A deadline inside the client's wait. Released clients wait 10 seconds for a store's answer, so a store's whole payment check, the envelope lookup included, now ends within 7 seconds. Each call has 2.5 seconds. A call still unanswered after 2 seconds also goes to the second RPC, and the first good answer counts. On a store, the proof's receipts get a 500 ms head start before the record read starts beside them. At the deadline, the store is refused with the payment error, and the reads still in flight fill the facts, so the client's next try passes.
  • Hold-off. An RPC that answered busy, or didn't answer in time, is asked after the others for 30 seconds. Background reads (fresh replication offers, paid notices, listings) give each call 15 seconds.
  • Unchanged: reads fall back across RPCs in up to 2 rounds, 0.5 seconds apart. A record that reads empty is read once more 1.5 seconds later, now on a fresh connection, at the next RPC when there is one.
  • Paid notices for a key the node's paid list already holds are dropped before they are admitted.
  • The RPC traffic figures count receipt reads, batches and block reads as their own kinds.

Default RPCs: Robinhood Chain's RPC and PublicNode

  • The node's built-in public RPCs are now PublicNode (robinhood-rpc.publicnode.com) and Robinhood Chain's own RPC (rpc.mainnet.chain.robinhood.com). A node whose RPC is one of them falls back to the other. A node on https://rpc.ordofi.network falls back to both (below). A node on any other RPC reads through that RPC alone.
  • The image's default RPC_URLS is now Robinhood Chain's own RPC alone. It was Robinhood Chain's RPC and rpc.ordofi.network.
  • rpc.ordofi.network is no longer built in. A node whose RPC is https://rpc.ordofi.network (trailing slashes aside) still reads payments through it first, then falls back to the two built-in RPCs; a node on any other form of it, such as http://rpc.ordofi.network or one with a path or query after it, reads through it alone. No node falls back to rpc.ordofi.network. The new image prints a notice at start when RPC_URL or RPC_URLS names it, saying which of these applies. To use the image's RPCs, delete RPC_URL and RPC_URLS from docker-compose.yml, pull the new image (see "Updating the image" below), then run docker compose up -d.
  • To see which RPC each node uses, look for RPC host in the node logs (docker compose logs | grep 'RPC host'). Each node logs only the RPC's host, never the rest of its URL.

Browser access: higher limits, a quick refusal, and your own config file

  • New defaults:

    • 128 browser sessions per node (was 32);
    • 7 sessions from one IPv4 address or IPv6 /64 (was 4);
    • 256 data channels across the node (was 32), two for each session.
  • Unchanged: 16 concurrent requests; 256 requests a second per node, 32 per address and 16 per session; 64 MiB of bytes in flight per node and 16 MiB per address; and new sessions from one address at a burst of 12, then 6 a minute.

  • The session ceiling. A node refuses more than 256 sessions at start, with a message naming the setting. 256 is the most the transport (saorsa-transport 0.38.2, now required) holds.

  • Bytes in flight. A browser GET reserves bytes in proportion to its record's stored size, no longer three times the largest record. More small records are read side by side, with no more bytes in flight than before.

  • A used-up channel pool refuses only the new channel. It no longer closes the browser's whole session.

  • The quick refusal. A node at its session limit, for the whole node or for one address, answers a new browser's connection check with a STUN 486 within about a second. Before, the browser waited out its connection timeout.

  • Refusals are logged by the limit reached, per address and in total, every five minutes (browser refusals (cumulative)).

  • Lowering a limit in Docker. Write the values in a node config file, for example node.toml next to docker-compose.yml:

    [webrtc_direct]
    max_connections = 64
    max_connections_per_ip = 4

    Mount it under volumes: as - ./node.toml:/etc/storage-node/node.toml:ro and run docker compose up -d. The log then says Nodes read /etc/storage-node/node.toml.

    • The settings the image passes itself (ports, rewards address, RPC, bootstrap peers, ANT_RELAY_SERVING) still take precedence over the file's.
    • A mount whose file doesn't exist is refused, with what to do.
    • The relations lowered values must keep are in docker/README.md, "Your own node settings".
    • Mounting the file takes the new image.

Full at 1 GiB free

  • The new line. Nodes now stop taking new data at 1 GiB free, where ds.15 stopped at 500 MiB: they refuse to store a record that would leave less. Each node therefore stores up to 500 MiB less.
  • Why. The node manager in images older than the one that ships darwin-nodes deletes a node's data at 500 MiB free. The higher line keeps the nodes' own writes away from that point.
  • Your own setting. The disk_reserve_mb default is now 1024; a value you set yourself is still used.
  • Refusal logs. At the line, each kind of refused store is logged once, then counted for five minutes; the next line says how many were held back.

Capped logs

  • Rotation and cap. Each node's log files start anew at 50 MiB or each hour, whichever comes first, and take at most 500 MiB in all; the oldest are deleted first. They rotated daily, with no size bound. --log-max-files still limits the number of files, and its help text no longer says rotation is daily.
  • At the 1 GiB line, logs give way. A node deletes its oldest log files first, then writes only warnings and errors into the room that frees, and nothing once none are left to delete. When the disk has room again, it logs how many lines it dropped.
  • The browser refusal counts and the hourly commitment line are kept at the line, as warnings are.

Upgrade timing and the space check

  • Timing. A node's staged-rollout delay, within the 24 hours after a release, now counts from the release's publication time, by GitHub's clock. A restart no longer starts the delay again, and a node that sees a release after its due time takes it at once. A release without a publication time, or with one in the future, counts from when the node first sees it, as before.
  • The first check. A node first checks for releases at a random point 1 to 5 minutes after it starts. It used to wait about an hour.
  • This release's own rollout runs on ds.15's timing. The new timing applies from the next release.
  • The space check. Before downloading, a node checks that its upgrade's writes leave at least 256 MiB above 500 MiB free. The writes are the archive twice and the binary twice. If they don't fit, the node skips the cycle with one log line and tries again at its next check. A node at the 1 GiB line still upgrades.
  • One node at a time. The nodes of one machine upgrade one after another, under a lock file in the shared upgrade cache.
  • Cache pruning. After a clean start, a node prunes the upgrade cache to the running version, the one before it, and any newer one a node on the machine is upgrading to.

The image marker

  • Images from the one that ships darwin-nodes on pass each node the commit the image was built from.
  • A ds.16 node reports it in its user agent, after its version (node/0.23.0+ds.16.image.<commit>), so the network's node census can count the installs still on an older image.
  • A node with no marker logs one warning at start: No image marker (ANT_IMAGE_VERSION): ... pull the new image. A node run outside Docker logs the same line, and can ignore it.

The hourly commitment line

Every hourly storage commitment tick now logs one info line, Storage commitment tick, with the number of records committed to, whether or not the commitment changed.

Updating the image (Docker)

Auto-upgrade replaces only each node's binary. The rest of the container changes only when you pull a new image: docker compose pull, then docker compose up -d. ds.16's image brings:

  • the node manager darwin-nodes, which de...
Read more

Storage node v0.23.0+ds.15

Choose a tag to compare

@github-actions github-actions released this 20 Oct 12:23
Immutable release. Only release title and notes can be modified.

Storage node v0.23.0+ds.15 for Linux x86_64 (Ubuntu 24.04 or newer).

Running nodes install it by themselves within the staged-rollout window.
The archive is signed with ML-DSA-65; check it with:
ant-keygen verify --key release-signing-key.pub --input ant-node-cli-linux-x64.tar.gz --signature ant-node-cli-linux-x64.tar.gz.sig

The archive also contains LICENSE-MIT and LICENSE-APACHE, plus THIRD-PARTY-NOTICES.txt and RUST-STD-COPYRIGHT.html with the licences and copyright notices of the third-party code in the binary and of the tools used to build it.

Storage node v0.23.0+ds.14

Choose a tag to compare

@github-actions github-actions released this 19 Oct 10:39
Immutable release. Only release title and notes can be modified.

Storage node v0.23.0+ds.14 for Linux x86_64 (Ubuntu 24.04 or newer).

Running nodes install it by themselves within the staged-rollout window.
The archive is signed with ML-DSA-65; check it with:
ant-keygen verify --key release-signing-key.pub --input ant-node-cli-linux-x64.tar.gz --signature ant-node-cli-linux-x64.tar.gz.sig

The archive also contains LICENSE-MIT and LICENSE-APACHE, plus THIRD-PARTY-NOTICES.txt and RUST-STD-COPYRIGHT.html with the licences and copyright notices of the third-party code in the binary and of the tools used to build it.

Storage node v0.23.0+ds.13

Choose a tag to compare

@github-actions github-actions released this 18 Oct 09:37
Immutable release. Only release title and notes can be modified.

Storage node v0.23.0+ds.13 for Linux x86_64 (Ubuntu 24.04 or newer).

Running nodes install it by themselves within the staged-rollout window.
The archive is signed with ML-DSA-65; check it with:
ant-keygen verify --key release-signing-key.pub --input ant-node-cli-linux-x64.tar.gz --signature ant-node-cli-linux-x64.tar.gz.sig

The archive also contains LICENSE-MIT and LICENSE-APACHE, plus THIRD-PARTY-NOTICES.txt and RUST-STD-COPYRIGHT.html with the licences and copyright notices of the third-party code in the binary and of the tools used to build it.

Storage node v0.21.0+ds.11

Choose a tag to compare

@github-actions github-actions released this 16 Oct 17:18
Immutable release. Only release title and notes can be modified.

Storage node v0.21.0+ds.11 for Linux x86_64 (Ubuntu 24.04 or newer).

Running nodes install it by themselves within the staged-rollout window.
The archive is signed with ML-DSA-65; check it with:
ant-keygen verify --key release-signing-key.pub --input ant-node-cli-linux-x64.tar.gz --signature ant-node-cli-linux-x64.tar.gz.sig

The archive also contains LICENSE-MIT and LICENSE-APACHE, plus THIRD-PARTY-NOTICES.txt and RUST-STD-COPYRIGHT.html with the licences and copyright notices of the third-party code in the binary and of the tools used to build it.

Storage node v0.21.0+ds.10

Choose a tag to compare

@github-actions github-actions released this 15 Oct 02:17
Immutable release. Only release title and notes can be modified.

Storage node v0.21.0+ds.10 for Linux x86_64 (Ubuntu 24.04 or newer).

Running nodes install it by themselves within the staged-rollout window.
The archive is signed with ML-DSA-65; check it with:
ant-keygen verify --key release-signing-key.pub --input ant-node-cli-linux-x64.tar.gz --signature ant-node-cli-linux-x64.tar.gz.sig

The archive also contains LICENSE-MIT and LICENSE-APACHE, plus THIRD-PARTY-NOTICES.txt and RUST-STD-COPYRIGHT.html with the licences and copyright notices of the third-party code in the binary and of the tools used to build it.