TON DocsTON Docs
OnboardingNodesApplicationsAPIsContractsSmart contractsTolkTolk languageTVMTON Virtual MachineFoundationsBlockchain foundations

Reliable Large Datagram Protocol (RLDP)

With the introduction of the sub-second upgrade, blockchain nodes now use QUIC instead of RLDP for internal node communication.

The Reliable Large Datagram Protocol (RLDP) runs on top of ADNL UDP and transfers data that does not fit in one ADNL datagram. It uses Forward Error Correction (FEC) to recover from packet loss. Its newer version, RLDP2, also uses acknowledgment packets, loss detection, pacing, and congestion control.

RLDP has been used throughout TON infrastructure for downloading blocks from other nodes, transferring data, and accessing TON Sites and TON Storage.

The rldp-http-proxy in the TON monorepo uses RLDP2 for accessing TON Sites.

Protocol

The TL schema retains structures for RLDP and RLDP2:

fec.raptorQ data_size:int symbol_size:int symbols_count:int = fec.Type;
fec.roundRobin data_size:int symbol_size:int symbols_count:int = fec.Type;
fec.online data_size:int symbol_size:int symbols_count:int = fec.Type;

rldp2.messagePart transfer_id:int256 fec_type:fec.Type part:int total_size:long seqno:int data:bytes = rldp2.MessagePart;
rldp2.confirm transfer_id:int256 part:int max_seqno:int received_mask:int received_count:int = rldp2.MessagePart;
rldp2.complete transfer_id:int256 part:int = rldp2.MessagePart;

rldp.messagePart transfer_id:int256 fec_type:fec.Type part:int total_size:long seqno:int data:bytes = rldp.MessagePart;
rldp.confirm transfer_id:int256 part:int seqno:int = rldp.MessagePart;
rldp.complete transfer_id:int256 part:int = rldp.MessagePart;

rldp.message id:int256 data:bytes = rldp.Message;
rldp.query query_id:int256 max_answer_size:long timeout:int data:bytes = rldp.Message;
rldp.answer query_id:int256 data:bytes = rldp.Message;

The message-part, confirmation, and completion structures are serialized as custom ADNL messages and transmitted over ADNL UDP. RLDP2 reuses rldp.message, rldp.query, and rldp.answer for the decoded payload.

  • rldp.message carries a one-way application payload.
  • rldp.query carries a request, a query_id, a maximum answer size, and a UNIX timeout.
  • rldp.answer carries the application response and the request's query_id. The querying endpoint rejects response data larger than max_answer_size.

The RLDP2 connection defaults to a 7,680-byte inbound limit. The send_query_ex registers max_answer_size as the limit for the answer transfer.

Transfer mechanism

  1. The sender generates a random transfer_id.
  2. The sender splits the payload into parts and creates a RaptorQ encoder for each part. The reference RLDP2 implementation uses parts of up to 2,000,000 bytes and 768-byte symbols.
  3. The sender transmits encoded symbols in rldp2.messagePart.
  4. The receiver reports the greatest received sequence number, a receive mask, and a total receive count in rldp2.confirm.
  5. The sender uses this feedback to estimate round-trip time, bandwidth, and loss. It paces symbols and adjusts its congestion window with a BBR-style controller.
  6. The sender stops transmitting a part after rldp2.complete arrives or the transfer times out.

The receiver decodes each part after collecting enough independent symbols, concatenates the parts in order, and deserializes the result as rldp.message, rldp.query, or rldp.answer. An answer uses the request's transfer_id with every bit inverted.

Forward Error Correction

The TL schema defines RoundRobin, Online, and RaptorQ FEC types. The reference RLDP2 implementation accepts only RaptorQ — its FEC parser rejects the other constructors.

RaptorQ

RaptorQ divides data into fixed-size source symbols. An encoder can derive additional repair symbols from the same source data.

After receiving enough independent source and repair symbols, the decoder reconstructs the original part without requesting specific lost symbols.

RLDP2 sends an initial set of source and repair symbols, then uses acknowledgments and loss estimates to decide how many additional symbols to send. The receiver sends rldp2.complete after decoding a part.

RLDP-HTTP

TON Sites use HTTP over RLDP. The host runs a standard HTTP web server with rldp-http-proxy alongside it. Incoming requests from the TON network arrive through RLDP2, and the proxy forwards them to the local HTTP server. On the client side, a local proxy, such as Tonutils Proxy, converts outgoing HTTP requests into RLDP.

HTTP over RLDP uses these TL structures:

http.header name:string value:string = http.Header;
http.payloadPart data:bytes trailer:(vector http.header) last:Bool = http.PayloadPart;
http.response http_version:string status_code:int reason:string headers:(vector http.header) no_payload:Bool = http.Response;
http.proxy.capabilities capabilities:long = http.proxy.Capabilities;

http.request id:int256 method:string url:string http_version:string headers:(vector http.header) = http.Response;
http.getNextPayloadPart id:int256 seqno:int max_chunk_size:int = http.PayloadPart;
http.proxy.getCapabilities capabilities:long = http.proxy.Capabilities;

The request-response flow:

  1. The client sends http.request via rldp.query.
  2. If the request has a payload, the server sends http.getNextPayloadPart queries to the client.
    • The client responds with http.payloadPart chunks and increments seqno until last is true.
  3. The server sends http.response.
  4. If no_payload is false, the client sends http.getNextPayloadPart queries to the server using the same chunked mechanism.

The proxy uses bit 0 (value 1) in the http.proxy.getCapabilities and http.proxy.capabilities exchange to advertise RLDP2 support. It requests payload chunks of up to 2,095,104 bytes and caps larger max_chunk_size values at that size.

TON Site request example

This example demonstrates requesting foundation.ton, assuming the ADNL address has been resolved via DNS and a connection has been established over ADNL UDP. The proxy requires the peer to advertise RLDP2 support.

GET request

The client fills the following structure:

http.request id:int256 method:string url:string http_version:string headers:(vector http.header) = http.Response;

The client serializes http.request:

e191b161                                                           -- TL ID http.request
116505dac8a9a3cdb464f9b5dd9af78594f23f1c295099a9b50c8245de471194   -- id (random)
03 474554                                                          -- method = "GET"
16 687474703a2f2f666f756e646174696f6e2e746f6e2f 00                 -- url = "http://foundation.ton/"
08 485454502f312e31 000000                                         -- http_version = "HTTP/1.1"
01000000                                                           -- headers (1 entry)
   04 486f7374 000000                                              -- name = "Host"
   0e 666f756e646174696f6e2e746f6e 00                              -- value = "foundation.ton"

The client then wraps the serialized http.request in rldp.query and serializes it. The following partial snippet shows the field layout. The placeholders represent values that the client supplies before serialization:

694d798a                    -- TL ID rldp.query
<QUERY_ID>                  -- 32 random bytes
0040000000000000            -- max_answer_size = 16 KiB (16,384 bytes)
<TIMEOUT>                   -- future UNIX timestamp encoded as a TL int
<SERIALIZED_HTTP_REQUEST>   -- TL bytes value

RaptorQ encoding

  1. The sender uses the 768-byte symbol size used by the reference RLDP2 implementation.
  2. For this one-part request, the sender sets data_size and total_size to the serialized rldp.query length. The RaptorQ encoder handles the final short symbol.
  3. The sender creates a RaptorQ encoder.

Packet transmission

The sender transmits encoded symbols in rldp2.messagePart:

rldp2.messagePart transfer_id:int256 fec_type:fec.Type part:int total_size:long seqno:int data:bytes = rldp2.MessagePart;
  • transfer_id – random int256, same for all parts of this transfer.
  • fec_typefec.raptorQ with the part's byte length, symbol_size = 768, and the encoder's source-symbol count.
  • part – zero-based part index. Start with 0.
  • total_size – complete serialized rldp.query size across all parts.
  • seqno – starts at 0, incremented for each packet.
  • data – encoded symbol (768 bytes).

The sender wraps each structure in adnl.message.custom and transmits it over ADNL UDP. It processes rldp2.confirm feedback and continues according to its loss-recovery and pacing logic until rldp2.complete arrives.

Response processing

The response arrives as rldp2.messagePart messages inside adnl.message.custom. Its transfer_id is the request transfer ID with all 256 bits inverted.

  1. The receiver extracts FEC parameters (data_size, symbol_size, symbols_count) from the first received message.
  2. The receiver initializes a RaptorQ decoder.
  3. The receiver feeds each received symbol and its seqno into the decoder.
  4. The receiver sends rldp2.confirm as symbols arrive.
  5. The receiver attempts decoding when the decoder reports that enough symbols are available. On success, it sends rldp2.complete for that part.

At this point, the decoded rldp.answer contains an http.response with the status code and headers, but not necessarily the body.

  • If no_payload is true, the response has no body and the request is complete.

  • If no_payload is false, the client fetches the body separately:

    1. The client sends an http.getNextPayloadPart request with incrementing seqno. The proxy requests at most 2,095,104 bytes per chunk.
    2. The client wraps each request in a new rldp.query transfer.
    3. The client repeats the request until the returned http.payloadPart has last set to true.

See also

On this page