Error codes
When a Dassie node refuses an ILP packet, it answers with a Reject that carries a three-character code and a short message. This page lists every code a node sends, what made it, and what your client does next.
On this page
Retry or stop
The first letter of a code tells you how to react, as ILPv4 defines it:
| Class | Means | Your client |
|---|---|---|
T, temporary |
Something on the way is short for the moment: liquidity, room, a route still settling in. | Waits a little, then sends again. Back off further each time. |
R, relative |
The packet's expiry did not fit the path. | Sends a new packet with a fresh, longer expiry. |
F, final |
Sending this same packet again will not help. | Changes the packet, or stops. |
Two more things help you read a Reject. Its triggeredBy
names the node that refused, and the message says why in a few words.
Neither is proof: any node on the path can write any code and any
address. Only the receiver can put an encrypted STREAM reply in a
Reject, so a Reject that carries a reply your connection can decrypt
came from the receiver. One without may come from any node on the
path, the receiver included, and a sender treats it as the path's.
That matters because the packets of one payment can take different paths: each node draws a next hop for every packet. A final error from one node on one path says little about the next packet. Dassie's own STREAM sender therefore sends the money again after a final error from the path, without backing off, and stops only after a run of such failures with no packet getting through. A simpler client may stop at the first one. Both are fine; the second just gives up sooner.
Why it works this way: a failure on one path should not end a payment another path can carry.
Each code here
The codes below are the ones a Dassie node sends today. The message text is quoted as the node writes it, so you can search your logs for it. A code from a node that is not Dassie may mean something a little different.
T00 Internal Error
| Message | What happened |
|---|---|
exchange rates are stale |
The packet converts between currencies, and the node's rates are too old. Rates come back once its rate sources answer. |
no exchange rate |
The node has no usable rate for this pair, or the result does not fit. |
internal error |
Something inside the node went wrong. Its log has the details. |
invalid response from the next hop |
The next node's answer to the packet was not a valid Fulfill or Reject. |
Your client: back off and try again later. When the
node sends a packet to a BTP client and the client answers with a BTP
error, the node rejects that packet toward its sender with the
error's code (the BTP client answered with an error), or
T00 when the code is not a valid one.
T01 Peer Unreachable
| Message | What happened |
|---|---|
the peer could not be reached |
The packet surely did not reach the next node: the connection was refused, or the peer answered that it did not take it. |
next hop went away |
The next node was removed while the packet waited for it. |
the only route leads back |
The only way on is back to the node the packet came from. Two neighbors see the network a little differently for a moment, right after a new peering; it passes when they next recompute their routes. |
the peering is ending |
The packet came from a peer whose peering with this node is winding down. |
Your client: wait a moment and send again. The next packet may take another path.
Why a route that leads back is T01, not F02
T03 Connector Busy
The node is holding as many packets as it allows, or its books have no
room for another pending transfer (connector busy). A
node's STREAM receiver sends T03 too, with no message, when its table
of connections is full and it cannot make room.
Your client: back off and try again.
T04 Insufficient Liquidity
A node on the way has no room to pass the packet on right now. Room comes back as peers settle what they owe each other, so this is the most common temporary error in a busy network.
| Message | What happened |
|---|---|
insufficient liquidity |
The credit between two peers is used up, or the node holds too little of the asset the packet would leave on. A node also answers this while it collects from a peer it decided to stop peering with. |
no settlement method available |
The route exists, but none of its next hops has a way to settle with this node right now. |
the settlement method is not open |
The packet came on a settlement method that is not open between this node and its sender. |
the method is being wound down |
The packet came on a settlement method the two peers are closing. |
the hold does not cover the packet |
Over a settlement hub, a hold does not cover the packet: the sending peer's toward this node, or this node's toward the next. |
no room for the peer's accounts |
The packet would open accounts for a new downstream node, and the node's books have no room for them. |
cannot answer the peer |
The node's own messages to that peer are not getting through, so it takes nothing new from it until the peer answers again. |
the spending cap is reached |
The BTP token has spent all its spending cap allows for now. The cap refills over time. |
Your client: back off and try again. Do not route around it by hand: the node never moves a packet to another hop or method because one is short of room.
T05 Rate Limited
| Message | What happened |
|---|---|
too many packets in flight |
You have more packets waiting at this node than it takes from one source. |
too much money in flight |
The packets in flight with one peer already carry as much money as the node lets them carry at once. |
the peer refused the packet for its rate |
The next node said it was getting too many requests. |
A node's STREAM receiver also sends T05, with no message, while its queue of events is full.
Your client: send fewer packets at once and back off. A STREAM sender with congestion control does this by itself.
R00 Transfer Timed Out
The packet expired before an answer came back
(packet timed out). A node waits for the full expiry,
never less, so a late Fulfill is not lost. A STREAM receiver that gets
a packet that has already expired answers R00 too.
Your client: send the money again in a new packet. If it keeps happening, the path is slow or a node on it is not answering.
R02 Insufficient Timeout
The packet leaves too little time. Each node takes a little off the
expiry, so the answer has time to travel back, and refuses a packet
that would arrive at the next hop with too little left
(insufficient timeout; expired for an ILDCP
request that already expired).
Your client: give packets a longer expiry. Thirty seconds, as STREAM senders use, is plenty.
F00 Bad Request
The BTP token your client connected with has been revoked
(the token is revoked). It will never work again.
Your client: stop, and ask the node's owner for a new token. See Connect an ILP client over BTP.
F01 Invalid Packet
The bytes are not a valid ILP Prepare (invalid packet),
or a packet to the ILDCP address is not an ILDCP request
(not an ILDCP request). Dassie reads packets strictly:
an address must follow the ILP address grammar, for example.
Your client: stop; this is a bug to fix in it.
F02 Unreachable
| Message | What happened |
|---|---|
no route found |
The node knows no route to the destination. A node without a domain name (a periphery node) also answers this to a peer's packet that is not meant for it, since it forwards nothing. |
amount too small |
After conversion, nothing of the packet's amount would arrive. |
pay from the route's ledger |
The owner's own packets never convert, and this one would have to. |
a payer takes no packets |
The address is one of the node's payers, which only send. |
no such address |
The address starts with peer., which belongs to the link and is never routed. A node answers only peer.config, for ILDCP, and its peers' swaps. |
A STREAM receiver answers F02, with no message, to an address below it whose credentials it cannot read: this receiver did not issue them, or the address was changed on the way.
Your client: stop, for most clients. Check the destination address and the credentials. A STREAM sender that keeps going after errors from the path, as Dassie's does, may try a few more packets first, since another path may lead there.
F05 Wrong Condition
A BTP client or one of the node's own endpoints answered with a
Fulfill whose fulfillment does not match the packet's condition
(invalid fulfillment). The node refuses the packet at
once. A peer's Fulfill that does not match is taken for a stale
resend and dropped: the packet waits for its own answer or its
expiry, and no F05 goes out.
Your client: stop for this packet; someone on the path is broken.
F06 Unexpected Payment
A STREAM receiver could not decrypt the packet's data. The shared secret does not belong to this address, or the data was damaged.
Your client: stop, and fetch fresh credentials.
F08 Amount Too Large
The packet carries more than a node takes in one packet
(packet amount exceeds maximum allowed amount), or more
than a token's spending cap could ever allow at once
(packet amount exceeds the spending cap). A packet that a
conversion would make too large on the way out gets F08 too.
Dassie's F08 always carries data: the amount that arrived and the most the node takes, both in the packet's own units. Scale your packets by that ratio. A Reject from other software may have no data; then halve the packet and try again.
Your client: send smaller packets. F08 means only that: a node never sends it for want of liquidity.
F99 Application Error
The receiver refused the packet. A Dassie node's receiver sends F99 in two ways:
- Without a STREAM reply, when it does not take the connection at all: the incoming payment it pays is unknown, or ended so long ago that the node forgot it, has expired, is on another ledger, or has every connection place taken.
-
With a STREAM reply, when it will not fulfill this
packet. Most often less arrived than the packet's minimum: the
path's rate is too low. A rate probe is refused this way on
purpose, so a sender can measure the rate. A packet that is more
than the receiver told its streams they may receive, beyond the
tolerance, and money for a stream that is closed, are refused this
way too. The reply says what arrived, in its prepare amount, and
how much the receiver takes, in each stream's
StreamMaxMoney. Read what arrived, and send what fits.
When the incoming payment has nothing left, or a packet fits what its
streams were told but not what is left, the receiver instead closes
the connection, with STREAM's NoError when the payment
got all it asked for, and ApplicationError when it was
stopped short. A payment that ended a short while ago is answered the
same way.
Your client: with a reply, follow it. Without one, stop and check the incoming payment. More on how a receiver talks to senders is on STREAM on Dassie.
How a receiver's money limit works
Swaps between peers
Peers swap assets with each other: a node sends a packet to
peer.swap.<scheme ID>, and its peer converts it
and sends it back on the method that scheme names. Only peers send
swaps, so a client never sees these codes. The wallet's network page
counts a node's swaps rejected and refused.
| Code and message | What happened |
|---|---|
F02 no swaps offered |
The peer's owner does not offer swaps, or the peer has no domain name. |
F02 not a settlement method of the peering |
The scheme the address names is not a method the two peers share. |
F02 no swaps on this method |
A swap came back on the method it names, to a node that has no swaps on that ledger. |
T04 no settlement method available |
The method the swap names is not available right now. |
F04 not a swap of ours |
The swap came back bringing less than the minimum its sender set, so the sender refused it. The message is the same as for F05. |
F05 not a swap of ours |
A swap came back to a node that did not send it, or with its data changed on the way. The message is the same as for F04. |
A swap otherwise meets the same checks as any packet a node passes on, with the same codes: F08 above the largest packet, T04 and T05 for room, T00 without a fresh rate.
Every code ILP defines, with its name, is in
crates/dassie-ilp/src/error_code.rs.