Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SIMD-0248: TPU feedback #248

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions proposals/0248-tpu-feedback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
---
simd: '0248'
title: TPU Feedback
authors:
- Lijun Wang <[email protected]>
category: Standard
type: Core
status: Review
created: 2025-02-14
development:
- Anza - WIP
- Firedancer - Not started
---

## Summary

This document defines the protocol to send feedback on TPU processing from the
servers to the clients.

## Motivation

A client sends TPU transactions to the leader as QUIC uni-streams. Currently we
only send transport level acknowledememt to the clients. I.e we only tell if a
transaction has been received by the server. A transaction received by the
server may not be processed due to resource contention. For example, if there
are too many transactions with higher paying fees than this transaction, the
transaction is not included in the block. Providing further information on the
transaction state will help the client to make more informed decision on
handling transaction failures and reduce the incentive to blindly resubmitting
transactions and help reduce congestion condition in the network. The QUIC
client library can be enhanced to use this information to not send transactions
with fees too low.

In addition, on the server side, the TPU server could use the feedback
information to more accurately perform QOS. For example, if a peer is
persistently submitting high percentage pf transactions which are rejected,
its permitted throughput might be reduced.

## Alternatives Considered

There are different transport mechanism to send the feedback such as
bidirectional streams, reverse unistream from the server to the client and
QUIC datagrams. We choose datagrams due to its simplicity. One reason we have
used stream over datagrams is the expectation that transaction size can be
increased in the future which can not be carried in one datagram. Sending
feedback has no such concern as we can send multiple datagrams if there are
too many transactions to acknowledge.

## New Terminology

In this document we define the following,

TPU Feedback -- information sent by the server about transaction processing.
Server -- the validator receiving the TPU transactions and sending the feedback.
Client -- the validator or application using QUIC TPU client sending the TPU
transactions and receiving the feedback.

## Detailed Design

When the client sends transactions to the server, it must first open a QUIC
connection to the server. The feedback is sent by the server using the same
connection. The feedback is sent using a QUIC datagram. The client wishing to
receive feedback from the server must be ready to receive application datagrams
from its connections.

A client connection notifies the server of its interest to receive feedback by
sending a datagram soliciting feedback. This helps server to avoid sending
datagrams to clients which are not interested in receiving feedback or clients
which are not upgraded to have such capabilities.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is best to assume we ask for feedback in the transaction request itself. Sending the feedback request in a separate datagram is adding statful behavior where it is not needed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a problem with that -- the transaction format need to be modified and changes that protocol-- I think we run out of free flags. I think this is a way to show the interest with minimal performance and protocol impact.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we not have some legacy variants we could reuse?


The client and server must enable the support of datagrams QUIC frame types
via transport parameter `max_datagram_frame_size`.

The server sends the feedback datagrams to interested clients periodically.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this info should only be provided if a client has submitted a valid transaction in the last 3-4 blocks. Keeping this active for ALL transactions sounds like overkill.

The frequency is decided by the server and consideration should be given to
coalesce feedback on more transactions to reduce datagram counts and short
enough feedback latency. For example 100ms can be considered. If there are
more transactions to be acknowledged in one datagram, more datagrams
can be sent without waiting for the feedback interval.

### The format of the TPU feedback

struct TpuFeedback {
version: u8,
timestamp: u64,
transaction_state: TransactionState,
priority_fee_info: PriorityFeeInfo
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wdyt to define two separate feedback types, one for transaction_state, one for priority_fee_info?

my reasoning:

  • transaction_state is connection oriented, communication is peer-to-peer; TransactionStats can be sent to Client as soon as become available, or never if Client didn't submit transactions.
  • priority_fee_info is aggregated at slot level, fits pub/sub system; PriorityFeeInfo updates can be published at pre-determine frequency.

Separating them into single-function message types provides flexibility in implementation and bandwidth management, perhaps worth the slight message overhead cost.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allow the same datagram to be able to carry both type of information or priority_fee_info -- so we could reduce datagram count. I think priority_fee_info is always good to have to understand better why it failed. In the case only priority_fee_info is transmitted, the TransactionState's transaction count is set to 0.

If we have a separate message -- we would need another flag field to indicate the message type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your intention to reduce datagram count.

Per our chat offline, it's possible to add 3rd type message to indicate the lowest tx priority Scheduler is accepting. Adding a message_type: u8 to header might not be bad idea; at least different type messages don't have to always go out in same datagram, especially some messages don't have new updates.

priority_fee_info is always good to have to understand better why it failed.

If transaction failed due to prio fee, then the reason should be clear by TransactionStateValue::FeeTooLow. With priority_fee_info, user would know "how low".


The version is a 8 bit unsinged integer, it is set to 1 for first version. It
is designed to allow the protocol to support for future extension. This should
have enough space for the versions in the relatively long future. It could also
be wrapped around in the future to differentiate as we do not expect to support
many current different versions.

The timestamp is the 64 bit unsigned integer representing the number of seconds
since midnight 01/01/1970.

TransactionState is defined as the following:

struct TransactionState {
transactions_count: u8
(transaction, state) pairs
}

The transactions_count is 8 bit unsgined integer representing the count of

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest we use TLV encoding here rather than a fixed format. Typically, TLV encodings provide far better ability for upgrade. Also with TLV we do not need to include total count at all.

transactions in this feedback. It is followed by the count of
(transaction, state) pairs. Each transaction is represented by the first 8 bytes
of the transaction signature and each state is represented by an 32 bit unsigned
integer.

The transaction state value is defined as the below enum:

enum TransactionStateValue {
Packed = 0,
FeeTooLow = 1,
FeePayerBalanceTooLow = 2,
NotPackedDueToUnspecifiedReason = 2^32 -1,
}

Newer state value can be defined as needed in the future with its own unique
value. Client should disregard the value if it does not understand its meaning.

The proirity fee information is defined as the following,

struct PriorityFeeInfo {
priority_fee_stats: {
min_priority_fee/compute unit ratio
median_priority_fee/compute unit ratio
max_priority_fee/compute unit ratio
}
}

These ratio are represented as u64 numbers using 8 bytes.

## Impact

There will be higher cost for the server to send the feedback to all connected
clients in term of network bandwidth and compute resources. With expected
2500 connections, and the nax 1232 bytes per, and assuming packets are sent
per 100ms the network cost is:

2500*1232*10 bytes /s ~= 30 MB/s

For sending only priority info to idle connections, the size of the feedback
datagram is smaller -- 33 bytes.

The cost is:

2500*21*10 bytes /s = 0.8 MB

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are not counting the headers. IP + UDP+QUIC are already over 30 bytes, so you get some 12 Mbps.


As not all connections are actively sending transactions, optimization can be
done to reduce the cost to send feedback to clients only having sent
transactions in the last report period.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validators should be able to charge fees for this service I think.


## Security Considerations

As described above the server and clients open new communication channel to
receive datagrams. This opens another interface for attacks. They can safegaurd
against attacks from malicious counterparts by checking the rate of datagrams
received. For example, if it received more than 3 times of expected number of
datagrams, the endpoint can deem it is under attack and decide to terminate
the connection.

## Backwards Compatibility

Introducing the capability of sending feedback and receive feedback on server
and client respectively should not cause any regression on existing
communication mechanism.

There are following possible configurations of mismatched versions on the
server and client:

* New Server with send capability <--> Old Client without receive capability.
* Old Server without send capability <--> New Client with receive capability.

In the first, the server should ignore any datagram send failures, if it can
detect send failures, it can optimize to stop sending future datagrams in the
connection.

In the second, the client should ignore any failures of missing receiving the
feedback. The feedback datagrams is best effort by the server and there is
no guarantee of its delivery even when both server and client are at the updated
version.

## References

An Unreliable Datagram Extension to QUIC:
https://datatracker.ietf.org/doc/html/rfc9221
Loading