Access and exposure
The reference node has two interfaces with very different trust levels. The control channel is private: it alone administers the node, and only a holder of the node's secret token can use it. REST is public: everything it offers is meant for anyone who can reach it.
The control channel#
The CLI administers the node over its own TCP listener, which must be on a loopback
address: a configuration naming any other address is refused. Both sides hold the
secret in control.token: 64 lower-case hex characters, a random 256-bit value
created at init. Only its owner can read or write the file: on Unix by its mode, on
Windows by an access list that grants the user who ran ting init and nobody else,
whatever the data directory's folder would pass down. The token never crosses the
connection, nor appears in status output, logs or peer messages; keep the file
private.
A control request takes one connection of its own and three frames, framed as in the peer protocol. Proofs are HMAC-SHA-256 keyed with the token's 64 characters, over the concatenation of the parts listed, nonces as their 32 bytes:
- The client sends
{"nonce": …}, 32 random bytes in hex. - The node answers
{"nonce": …, "proof": …}: its own 32 random bytes, and a proof overting-control-node, the client's nonce and its own. The client checks it before it sends anything more, so a listener that is not the node, such as one on a port a staleruntime.jsonnames, learns nothing it could use. - The client sends
{"request": …, "proof": …}: the request as a string of JSON, such as{"method":"status","params":{}}, and a proof overting-control-client, the node's nonce, its own and the request string's bytes. This frame may be as large as twice the next block's data capacity and 64 KiB more, at least 16 MiB, so thatsubmitcarries a transaction of any size a block can hold.
The node checks the proof in constant time and answers {"ok": true, "result": …},
or {"ok": false, "error": …}, with unauthorized for a wrong proof and
unknown control method for any other method than these:
| Method | Purpose | CLI |
|---|---|---|
status |
Chain status plus the listener addresses actually bound | ting status, ting docs |
peers |
Configured and discovered peers with their last success or error, and the size of the address book | ting peers |
add_peer, remove_peer |
Change the peer list | ting add node, ting remove node |
api |
Enable or disable REST, with or without CORS | ting api enable, ting api disable |
miner |
Start, stop or describe the built-in miner; set producer data and capacity votes | ting miner … |
votes |
Where capacity voting stands, with this node's votes | ting votes |
template, solution |
The node's half of mining | ting template, ting solution |
mine |
Template, search and solution in one bounded request | ting mine |
submit |
Submit a transaction, deferred with not_before |
ting submit |
invalidate, reconsider |
Mark a block invalid, or clear invalid marks (overriding validity) | ting invalidate, ting reconsider |
stop |
Graceful shutdown | ting stop |
The node serves at most 16 control connections at once, and the CLI waits up to 35
seconds for an answer. Only this channel can mine, manage peers, toggle REST,
override block validity and shut the node down. Mining software holding the token
uses template and solution exactly as the CLI does; mining has no REST surface.
runtime.json records the addresses actually bound, and the process ID. It is never
treated as authority to stop a process.
The REST interface#
REST is off by default. When on, it binds to loopback unless you choose another address, and it can be switched on or off while the node runs. Enabling it binds the listener before recording success, so a failed bind leaves REST off and reports the error. Switching it off, or stopping the node, closes idle connections at once and gives requests in progress a second to finish; then their connections close too.
Everything it offers is public by design:
- reads of blocks, transactions, events, contracts, storage, status and metrics;
- transaction submission into the bounded mempool with
POST /v1/transactions, with no token, because programs define their own authorization.
REST status leaves out process IDs, listener addresses and control-channel details. REST's reads run on threads of their own, one per processor core, beside the thread that validates and applies blocks, and its few requests to that thread have their own share of its queue, so REST traffic cannot slow block processing or crowd out peers (how the node is put together). There is no rate per client. Connections, requests, bodies, queues and pages are bounded:
| Bound | Value | When exceeded |
|---|---|---|
| Connections at once | 512, or what the open-file limit leaves beside what peers and storage may need | Further connections wait to be accepted |
| Connections from one client, an IPv4 address or an IPv6 /64 | 16; this machine's own, such as a reverse proxy's, are not counted | Closed at once |
| Time to send a request's header, and to leave a connection idle after an answer | 10 seconds; 5 minutes for this machine's own, longer than a reverse proxy keeps an idle connection to the node, so that the proxy closes it first | Connection closed |
| Time to send an answer, from when it is ready | A minute, and ten seconds for every MiB of it | Connection closed |
| Requests in progress, each until all but at most 128 KiB of its answer is written to its connection | 32 per processor core | 503, HTTP service busy |
| REST's requests waiting for the chain thread (submissions, the mempool, pending status) | 128 | 503, node request queue unavailable |
| Time per request | 35 seconds; a submission also ten seconds for every MiB its body may have | 504, HTTP deadline exceeded |
| A submission's body | Twice the data capacity of the next block, and 4 KiB | 413 |
| A batch storage read's body | 256 bytes for each of the 1,024 records it may name, and 4 KiB: 260 KiB | 413 |
| Any other request body | 16 MiB | 413 |
| A JSON page | Just under 16 MiB | The page stops early; follow next |
Every response carries X-Content-Type-Options: nosniff,
Referrer-Policy: no-referrer and Cache-Control: no-store. The endpoints are
listed on Explore the chain.
REST never takes the descriptors that peers and storage may need. Those are one for
each peer connection the configuration allows, max_inbound in and max_peers out
and 32 configured peers, and 64 for the rest: the seeds, control connections, block
files, the database and the log. On Unix the node raises its open-file limit at start
to that and 512 more, as far as the system's hard limit allows, and REST takes what
is left of the limit, at most 512. When that is fewer, the node says so in its log.
Windows has no such limit.
Exposing a node#
- Remote REST access is an explicit choice of bind address. For a remote audience, put a TLS reverse proxy in front.
- By default REST grants no cross-origin permissions.
ting api enable --corsanswers browser applications from any origin, such as a wallet in a web page; leave it off unless such applications must reach this node. - The explorer, the API page and the wiki load no external assets. The API page's Swagger UI (5.33.0) is vendored with its license, notice and an integrity manifest and served by the node; its online schema validator, remote example servers and external documentation assets are disabled, and its requests go to this node's own API.
- The explorer inserts all dynamic data as text, never as HTML.
- Wiki pages come from a fixed registry compiled into the node. No user-controlled Markdown and no filesystem path is ever rendered.
- Peer traffic is unencrypted and unauthenticated, but it carries only public chain data, and no peer message reaches administration.
- Proof of work bounds what blocks cost, not how many alternatives to one block there are: every child of a block has the same target, so each sibling of the tip costs one block of work. A block a peer pushes unasked is stored only when its branch overtakes the tip, and it is executed at once: valid, it moves the tip; invalid, it stays on disk and its sender is banned for a day. One that does not overtake is held in memory, 64 MiB at most for all connections (pushed blocks). Nor can a peer grow the node's header tree with such alternatives: headers sync keeps a chain only once it overtakes the best header, and checks any other without keeping it (headers first).
- What a node asks for along the best header's branch it stores whether or not it is ever executed, as Bitcoin does: a peer the node follows, whose header chain overtakes the best header, can withhold one of its blocks and have the node store the others, which it never connects without that one. Each such block costs the peer a block of work on a branch heavier than any the node knows, and only a peer the node chose to follow can do it; on a network with little hashpower that work is cheap, so this bounds the cost of such storage, not its possibility.
- With discovery on, a node tells its peers the addresses of nodes it has recently
synchronized with, and announces its own listening port. With
"discovery": falseit does neither. Its address can still spread: a node that configures it, synchronizes with it and has discovery on shares it like any other (finding more peers).