Line data Source code
1 : #ifndef HEADER_fd_src_discof_repair_fd_repair_h
2 : #define HEADER_fd_src_discof_repair_fd_repair_h
3 :
4 : /* fd_repair implements the Solana Repair protocol. In a nutshell,
5 : Repair is a protocol for recovering shreds that the validator is
6 : expecting but has not received from Turbine. Neither the logic for
7 : how the validator determines it should be expecting a shred nor the
8 : logic for determining which peer validator to request the shred from
9 : is implemented in this file (see fd_policy.h instead); rather, this
10 : is an implementation of the protocol itself for requesting shreds
11 : from other validators.
12 :
13 : The repair protocol supports four different message types on the
14 : client side:
15 :
16 : - Pong( ping_token )
17 :
18 : This is a response message to address validation via a Ping-Pong
19 : protocol. When a validator receives a repair request from another
20 : validator it does not recognize, it will ignore the request and
21 : instead respond with its own Ping message to the requesting
22 : validator. The Ping contains a 32-byte token that the requesting
23 : validator needs to hash and sign as part of a Pong response. The
24 : scheme is as follows: the Ping token is concatenated with the
25 : prefix "SOLANA_PING_PONG", which is then piped in as the preimage
26 : into SHA-256. The resulting hash is then signed and the signature
27 : along with the actual hash is packed into a Pong.
28 :
29 : - Shred( slot, shred_idx )
30 :
31 : This is a request for a specific shred in the provided slot. The
32 : (slot, shred index) generally uniquely identifies a shred except in
33 : certain exceptional conditions (equivocation). The responding
34 : validator will return the shred if it has it.
35 :
36 : - HighestShred( slot, shred_idx )
37 :
38 : This is a request for the highest shred in the provided slot that
39 : is greater than or equal to shred_idx. Note this is not
40 : necessarily the last shred in the slot, as it depends on what the
41 : responding validator has available. The responding validator will
42 : return the highest shred it has that meets the condition.
43 :
44 : - Orphan( slot )
45 :
46 : This is a request for up to 10 shreds, where each shred is the
47 : prior one's ancestor, beginning from but excluding slot. For
48 : example, an orphan request for slot 10 will return a single shred
49 : for slots 9, 8, 7, 6, 5, 4, 3, 2 and 1 (assuming no skips). Also,
50 : the responding validator will return the highest shred index it has
51 : for every ancestor it knows about.
52 :
53 : All 3 repair request types are prefixed with a common header of from
54 : pubkey, to pubkey, ulong timestamp and uint nonce. The timestamp is
55 : a standard UNIX epoch (milliseconds since 1970-01-01T00:00:00Z) and
56 : the nonce is echoed back by the responding validator in the repair
57 : response. Unlike a typical cryptographic nonce that prevents replay
58 : attacks, the repair server implementation ignores the nonce and this
59 : implementation leaves it up to the calling application to manage the
60 : nonce. Note the 4 nonce bytes are appended after the end of a shred
61 : in a repair response.
62 :
63 : All communication across the wire is done with bincode serialization
64 : encoding. Serialization and deserialization is code-generated by
65 : fd_types. */
66 :
67 : #include "../../ballet/ed25519/fd_ed25519.h"
68 : #include "../../flamenco/types/fd_types_custom.h"
69 :
70 : /* FD_REPAIR_USE_HANDHOLDING: Define this to non-zero at compile time
71 : to turn on additional runtime checks and logging. */
72 :
73 : #ifndef FD_REPAIR_USE_HANDHOLDING
74 : #define FD_REPAIR_USE_HANDHOLDING 1
75 : #endif
76 :
77 : /* FD_REPAIR_KIND_{PONG,SHRED,HIGHEST_SHRED,ORPHAN} specify discriminant
78 : values the protocol uses to distinguish message types. */
79 :
80 0 : #define FD_REPAIR_KIND_PING (0U)
81 0 : #define FD_REPAIR_KIND_PONG (7U)
82 0 : #define FD_REPAIR_KIND_SHRED (8U)
83 0 : #define FD_REPAIR_KIND_HIGHEST_SHRED (9U)
84 0 : #define FD_REPAIR_KIND_ORPHAN (10U)
85 :
86 : /* fd_repair_pong describes the schema of a Pong. */
87 :
88 : struct __attribute__((packed)) fd_repair_pong {
89 : fd_pubkey_t from; /* pubkey of the validator responding with the pong */
90 : fd_hash_t hash; /* sha-256 hash generated from a ping hash */
91 : fd_ed25519_sig_t sig; /* from's signature over the preceding hash field */
92 : };
93 : typedef struct fd_repair_pong fd_repair_pong_t;
94 :
95 : /* REQ_HDR defines the common header of Repair request types. */
96 :
97 : #define REQ_HDR \
98 : fd_ed25519_sig_t sig; /* ed25519 signature over all the subsequent fields */ \
99 : fd_pubkey_t from; /* pubkey of the validator that sent the request */ \
100 : fd_pubkey_t to; /* pubkey of the validator that is being requested */ \
101 : ulong ts; /* timestamp in milliseconds since unix epoch */ \
102 : uint nonce; /* nonce to be echoed back by the responding validator */ \
103 :
104 : /* fd_repair_shred requests the specific shred at slot and shred_idx
105 : from a peer validator. */
106 :
107 : /* TODO: remove _req suffix from below after we remove all fd_types inclusions in repair_tile */
108 : struct __attribute__((packed)) fd_repair_shred_req {
109 : REQ_HDR
110 : ulong slot;
111 : ulong shred_idx;
112 : };
113 : typedef struct fd_repair_shred_req fd_repair_shred_req_t;
114 :
115 : /* fd_repair_highest_shred requests the highest shred in slot greater
116 : than shred_idx that a peer validator has. Note this is not
117 : necessarily the last shred in the slot, as it depends on what the
118 : peer has available. */
119 :
120 : struct __attribute__((packed)) fd_repair_highest_shred_req {
121 : REQ_HDR
122 : ulong slot;
123 : ulong shred_idx;
124 : };
125 : typedef struct fd_repair_highest_shred_req fd_repair_highest_shred_req_t;
126 :
127 : /* fd_repair_orphan requests the ancestors of slot (an "orphaned" slot)
128 : from a peer validator. The peer can respond with shreds for up to 10
129 : ancestor slots, where every shred is the last shred for that slot. */
130 :
131 : struct __attribute__((packed)) fd_repair_orphan_req {
132 : REQ_HDR
133 : ulong slot;
134 : };
135 : typedef struct fd_repair_orphan_req fd_repair_orphan_req_t;
136 :
137 : /* fd_repair_msg_t defines the schema of all Repair message types. */
138 :
139 : struct __attribute__((packed)) fd_repair_msg {
140 : uint kind; /* FD_REPAIR_KIND_{PONG,SHRED,HIGHEST_SHRED,ORPHAN} */
141 : union {
142 : fd_repair_pong_t pong;
143 : fd_repair_shred_req_t shred;
144 : fd_repair_highest_shred_req_t highest_shred;
145 : fd_repair_orphan_req_t orphan;
146 : };
147 : };
148 : typedef struct fd_repair_msg fd_repair_msg_t;
149 :
150 : struct __attribute__((packed)) fd_repair_ping {
151 : uint kind;
152 : fd_repair_pong_t ping;
153 : };
154 : typedef struct fd_repair_ping fd_repair_ping_t;
155 :
156 : /* FD_REPAIR_PONG_PREIMAGE_PREFIX is used by Repair's Ping-Pong protocol.
157 : Both a Ping and Pong contain a hash token, that is generated from a
158 : preimage prefixed with the below. */
159 :
160 0 : #define FD_REPAIR_PONG_PREIMAGE_PREFIX "SOLANA_PING_PONG"
161 0 : #define FD_REPAIR_PONG_PREIMAGE_SZ (48UL)
162 :
163 : /* FD_REPAIR_MAX_PREIMAGE_SZ is the maximum size of a preimage for a
164 : repair request. This is the size of the largest repair request
165 : (highest_shred or shred) without the signature. */
166 :
167 3 : #define FD_REPAIR_MAX_PREIMAGE_SZ (sizeof(fd_repair_msg_t) - sizeof(fd_ed25519_sig_t))
168 :
169 : static const fd_pubkey_t null_pubkey = {{ 0 }};
170 :
171 : /* fd_repair_sign_fn defines the function signature for a user-provided
172 : signing callback. */
173 :
174 : typedef void (fd_repair_sign_fn)( void * ctx, fd_repair_msg_t * msg, uchar * sig );
175 :
176 : struct fd_repair {
177 : fd_pubkey_t identity_key; /* validator identity key */
178 : fd_repair_sign_fn * sign_fn; /* user-provided signing callback */
179 : void * sign_ctx; /* user-provided context for signing callback */
180 : fd_repair_msg_t msg; /* buffer for outgoing repair requests */
181 : };
182 : typedef struct fd_repair fd_repair_t;
183 :
184 : /* Constructors */
185 :
186 : /* fd_repair_{align,footprint} return the required alignment and
187 : footprint of a memory region suitable for use as repair. Declaration
188 : friendly (e.g. a memory region declared as "fd_repair_t repair[1];"
189 : will automatically have the needed alignment and footprint). */
190 :
191 : FD_FN_CONST static inline ulong
192 0 : fd_repair_align( void ) {
193 0 : return alignof(fd_repair_t);
194 0 : }
195 :
196 : FD_FN_CONST static inline ulong
197 0 : fd_repair_footprint( void ) {
198 0 : return sizeof(fd_repair_t);
199 0 : }
200 :
201 : /* fd_repair_new formats an unused memory region for use as a repair.
202 : mem is a non-NULL pointer to this region in the local address space
203 : with the required footprint and alignment.
204 : Initializes repair with the public identity key. */
205 :
206 : void *
207 : fd_repair_new( void * shmem, fd_pubkey_t * identity_key );
208 :
209 : /* fd_repair_join joins the caller to the repair. repair points to the
210 : first byte of the memory region backing the repair in the caller's
211 : address space. Returns a pointer in the local address space to
212 : repair on success. */
213 :
214 : fd_repair_t *
215 : fd_repair_join( void * repair );
216 :
217 : /* fd_repair_leave leaves a current local join. Returns a pointer to
218 : the underlying shared memory region on success and NULL on failure
219 : (logs details). Reasons for failure include repair is NULL. */
220 :
221 : void *
222 : fd_repair_leave( fd_repair_t const * repair );
223 :
224 : /* fd_repair_delete unformats a memory region used as a repair. Assumes
225 : only the nobody is joined to the region. Returns a pointer to the
226 : underlying shared memory region or NULL if used obviously in error
227 : (e.g. repair is obviously not a repair ... logs details). The
228 : ownership of the memory region is transferred to the caller. */
229 :
230 : void *
231 : fd_repair_delete( void * repair );
232 :
233 : /* fd_repair_{pong,shred,highest_shred,orphan} creates and returns a
234 : pointer to a serialized {Pong,Shred,HighestShred,Orphan} message.
235 : Does not require the caller to provide memory, as Repair itself
236 : maintains a dedicated memory region (repair->msg) for buffering
237 : requests. Assumes repair->msg is not already buffering an existing
238 : request and can be overwritten. Returns a pointer to repair->msg on
239 : success, NULL on failure. */
240 :
241 :
242 : fd_repair_msg_t * fd_repair_pong ( fd_repair_t * repair, fd_hash_t * ping_token );
243 : fd_repair_msg_t * fd_repair_shred ( fd_repair_t * repair, fd_pubkey_t const * to, ulong ts, uint nonce, ulong slot, ulong shred_idx );
244 : fd_repair_msg_t * fd_repair_highest_shred( fd_repair_t * repair, fd_pubkey_t const * to, ulong ts, uint nonce, ulong slot, ulong shred_idx );
245 : fd_repair_msg_t * fd_repair_orphan ( fd_repair_t * repair, fd_pubkey_t const * to, ulong ts, uint nonce, ulong slot );
246 :
247 : /* fd_repair_sz returns the bincode-serialized sz of msg. */
248 :
249 : FD_FN_PURE static inline ulong
250 0 : fd_repair_sz( fd_repair_msg_t const * msg ) {
251 0 : switch( msg->kind ) {
252 0 : case FD_REPAIR_KIND_PONG: return sizeof(uint) + sizeof(fd_repair_pong_t);
253 0 : case FD_REPAIR_KIND_SHRED: return sizeof(uint) + sizeof(fd_repair_shred_req_t);
254 0 : case FD_REPAIR_KIND_HIGHEST_SHRED: return sizeof(uint) + sizeof(fd_repair_highest_shred_req_t);
255 0 : case FD_REPAIR_KIND_ORPHAN: return sizeof(uint) + sizeof(fd_repair_orphan_req_t);
256 0 : default: FD_LOG_ERR(( "Unhandled repair kind %u", msg->kind ));
257 0 : }
258 0 : }
259 :
260 : /* preimage_pong takes a pong and takes the token in a ping and a
261 : FD_REPAIR_PONG_PREIMAGE_SZ-byte buffer and returns a pointer to a
262 : preimage that can be signed. */
263 :
264 : static inline uchar *
265 0 : preimage_pong( fd_hash_t const * ping_token, uchar * preimage_buf, ulong preimage_sz ) {
266 0 : # if FD_REPAIR_USE_HANDHOLDING
267 0 : if( FD_UNLIKELY( preimage_sz != FD_REPAIR_PONG_PREIMAGE_SZ ) ) {
268 0 : FD_LOG_ERR(( "preimage_sz %lu must be %lu", preimage_sz, FD_REPAIR_PONG_PREIMAGE_SZ ));
269 0 : }
270 0 : # endif
271 0 : ulong prefix_sz = sizeof(FD_REPAIR_PONG_PREIMAGE_PREFIX) - 1 /* subtract NUL */;
272 0 : memcpy( preimage_buf, FD_REPAIR_PONG_PREIMAGE_PREFIX, prefix_sz );
273 0 : memcpy( preimage_buf + prefix_sz, ping_token, sizeof(fd_hash_t) );
274 0 : return preimage_buf;
275 0 : }
276 :
277 : /* preimage_req takes a repair request populated with all fields except
278 : for the signature, and returns a pointer to a preimage that can be
279 : signed. Modifies the msg in place.
280 :
281 : At the start of this function, the repair_msg_t should contain
282 :
283 : [ discriminant ] [ empty sig ] [ repair request fields ]
284 : ^ ^ ^
285 : 0 4 68
286 :
287 : https://github.com/solana-labs/solana/blob/master/core/src/repair/serve_repair.rs#L1258
288 :
289 : We want to sign over
290 : [ discriminant ] [ payload ]
291 : ^ ^
292 : buffer buffer+4
293 :
294 : We can do this without using any extra memory copying the discriminant
295 : to the last 4 bytes of the sig field, and returning a pointer to
296 : that copied location. The sig field should be overwritten with the
297 : actual signature value later, else the fd_repair_msg_t will be
298 : incorrect. */
299 :
300 : static inline uchar *
301 0 : preimage_req( fd_repair_msg_t * msg, ulong * preimage_sz ) {
302 0 : uchar * preimage = (uchar *)fd_type_pun(msg);
303 0 : preimage += sizeof(fd_ed25519_sig_t);
304 0 : FD_STORE( uint, preimage, msg->kind ); /* copy discriminant over */
305 0 : *preimage_sz = fd_repair_sz( msg ) - sizeof(fd_ed25519_sig_t);
306 0 : return preimage;
307 0 : }
308 : #endif /* HEADER_fd_src_discof_repair_fd_repair_h */
|