John Freeman
John Freeman

Reputation: 2692

How does a component within rippled send and receive messages from peers?

If I want to send a message and handle the response somewhere in the code, what is the API? What components do I need? How do I construct them or get handles to them? What methods do I call? How do I add new message types?

Upvotes: 0

Views: 35

Answers (1)

John Freeman
John Freeman

Reputation: 2692

Here is a sequence diagram I made for the messages exchanged as part of downloading candidate transaction sets referenced by proposals:

sequence diagram for InboundTransactions

To send a message, a component needs a PeerImp object (generally held via shared_ptr<Peer>) on which it calls the send(shared_ptr<Message>) method. There is only one generic implementation of send, and it handles every protocol buffer message type. This call returns void (i.e. no request object).

When a message is received from a peer, the onMessage(MessageType) method for that message type is called. There is a different overload of onMessage for each message type.

Consider when you write code for HTTP. A popular idiom in JavaScript looks like this:

const response = await http.get(url, params)

There are some important differences between this pattern and the one for RTXP (the official name of our message protocol):

  • HTTP has an association between request and response. RTXP generally does not have this association, but in one notable example it does. TMGetLedger is a request message type, and TMLedgerData is its response message type. They both have a requestCookie field to associate a response with its request. The request generates a (random?) identifier for its “request cookie”, and the response copies that cookie.

  • With HTTP, the code that sends the request passes a handler expecting the response. Generally, the response handler is different for each place in the code that sends a request. Not so with RTXP. Instead, each request message type typically corresponds to exactly one response message type, and each message of a given type has the same exact handler. That means each place in the code that sends a request of the same message type uses the same response handler. I suspect that:

    • most message types are sent from exactly one place in the code

    • when one message type is sent from multiple places, then that message has an enumeration field to distinguish them (with “type” in its name)

    • each message type was designed for exactly the place in the code that needed to send it, which makes them hard to reuse

  • Most request message types are different from their response message types. The one notable exception is TMGetObjectByHash which has a Boolean query field that distinguishes a request (true) from a response (false).

There is some room for uniform handling of each message type:

  • A message is generally expected to be independently verifiable. If a response says it has the header for ledger ABCD, then the handler expects it can hash the header to get the digest ABCD.

  • A response is generally expected to correspond to a request.

If these expectations are violated, the peer that sent the message is penalized. Our server tracks a “fee” for each peer that measures its reliability. Bad messages are “charged” various fees based on the kind of violation. These fees only exist on the server. They have no bearing on the ledger.

PeerImp objects are obtained from the Overlay object. There is exactly one Overlay per Application, obtained by calling its overlay() method.

Upvotes: 0

Related Questions