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. */
65 :
66 : #include "../../ballet/ed25519/fd_ed25519.h"
67 : #include "../../flamenco/fd_flamenco_base.h"
68 :
69 : /* FD_REPAIR_USE_HANDHOLDING: Define this to non-zero at compile time
70 : to turn on additional runtime checks and logging. */
71 :
72 : #ifndef FD_REPAIR_USE_HANDHOLDING
73 : #define FD_REPAIR_USE_HANDHOLDING 1
74 : #endif
75 :
76 : /* FD_REPAIR_KIND_{PONG,SHRED,HIGHEST_SHRED,ORPHAN} specify discriminant
77 : values the protocol uses to distinguish message types. */
78 :
79 0 : #define FD_REPAIR_KIND_PING (0U)
80 0 : #define FD_REPAIR_KIND_PONG (7U)
81 0 : #define FD_REPAIR_KIND_SHRED (8U)
82 0 : #define FD_REPAIR_KIND_HIGHEST_SHRED (9U)
83 0 : #define FD_REPAIR_KIND_ORPHAN (10U)
84 :
85 : /* fd_repair_pong describes the schema of a Pong. */
86 :
87 : struct __attribute__((packed)) fd_repair_pong {
88 : fd_pubkey_t from; /* pubkey of the validator responding with the pong */
89 : fd_hash_t hash; /* sha-256 hash generated from a ping hash */
90 : fd_ed25519_sig_t sig; /* from's signature over the preceding hash field */
91 : };
92 : typedef struct fd_repair_pong fd_repair_pong_t;
93 :
94 : /* REQ_HDR defines the common header of Repair request types. */
95 :
96 : #define REQ_HDR \
97 : fd_ed25519_sig_t sig; /* ed25519 signature over all the subsequent fields */ \
98 : fd_pubkey_t from; /* pubkey of the validator that sent the request */ \
99 : fd_pubkey_t to; /* pubkey of the validator that is being requested */ \
100 : ulong ts; /* timestamp in milliseconds since unix epoch */ \
101 : uint nonce; /* nonce to be echoed back by the responding validator */ \
102 :
103 : /* fd_repair_shred requests the specific shred at slot and shred_idx
104 : from a peer validator. */
105 :
106 : /* TODO: remove _req suffix from below */
107 : struct __attribute__((packed)) fd_repair_shred_req {
108 : REQ_HDR
109 : ulong slot;
110 : ulong shred_idx;
111 : };
112 : typedef struct fd_repair_shred_req fd_repair_shred_req_t;
113 :
114 : /* fd_repair_highest_shred requests the highest shred in slot greater
115 : than shred_idx that a peer validator has. Note this is not
116 : necessarily the last shred in the slot, as it depends on what the
117 : peer has available. */
118 :
119 : struct __attribute__((packed)) fd_repair_highest_shred_req {
120 : REQ_HDR
121 : ulong slot;
122 : ulong shred_idx;
123 : };
124 : typedef struct fd_repair_highest_shred_req fd_repair_highest_shred_req_t;
125 :
126 : /* fd_repair_orphan requests the ancestors of slot (an "orphaned" slot)
127 : from a peer validator. The peer can respond with shreds for up to 10
128 : ancestor slots, where every shred is the last shred for that slot. */
129 :
130 : struct __attribute__((packed)) fd_repair_orphan_req {
131 : REQ_HDR
132 : ulong slot;
133 : };
134 : typedef struct fd_repair_orphan_req fd_repair_orphan_req_t;
135 :
136 : /* fd_repair_msg_t defines the schema of all Repair message types. */
137 :
138 : struct __attribute__((packed)) fd_repair_msg {
139 : uint kind; /* FD_REPAIR_KIND_{PONG,SHRED,HIGHEST_SHRED,ORPHAN} */
140 : union {
141 : fd_repair_pong_t pong;
142 : fd_repair_shred_req_t shred;
143 : fd_repair_highest_shred_req_t highest_shred;
144 : fd_repair_orphan_req_t orphan;
145 : };
146 : };
147 : typedef struct fd_repair_msg fd_repair_msg_t;
148 :
149 : struct __attribute__((packed)) fd_repair_ping {
150 : uint kind;
151 : fd_repair_pong_t ping;
152 : };
153 : typedef struct fd_repair_ping fd_repair_ping_t;
154 :
155 : /* FD_REPAIR_PONG_PREIMAGE_PREFIX is used by Repair's Ping-Pong protocol.
156 : Both a Ping and Pong contain a hash token, that is generated from a
157 : preimage prefixed with the below. */
158 :
159 0 : #define FD_REPAIR_PONG_PREIMAGE_PREFIX "SOLANA_PING_PONG"
160 0 : #define FD_REPAIR_PONG_PREIMAGE_SZ (48UL)
161 :
162 : /* FD_REPAIR_MAX_PREIMAGE_SZ is the maximum size of a preimage for a
163 : repair request. This is the size of the largest repair request
164 : (highest_shred or shred) without the signature. */
165 :
166 0 : #define FD_REPAIR_MAX_PREIMAGE_SZ (sizeof(fd_repair_msg_t) - sizeof(fd_ed25519_sig_t))
167 :
168 : static const fd_pubkey_t null_pubkey = {{ 0 }};
169 :
170 : /* fd_repair_sign_fn defines the function signature for a user-provided
171 : signing callback. */
172 :
173 : typedef void (fd_repair_sign_fn)( void * ctx, fd_repair_msg_t * msg, uchar * sig );
174 :
175 : struct fd_repair {
176 : fd_pubkey_t identity_key; /* validator identity key */
177 : fd_repair_sign_fn * sign_fn; /* user-provided signing callback */
178 : void * sign_ctx; /* user-provided context for signing callback */
179 : fd_repair_msg_t msg; /* buffer for outgoing repair requests */
180 : };
181 : typedef struct fd_repair fd_repair_t;
182 :
183 : /* Constructors */
184 :
185 : /* fd_repair_{align,footprint} return the required alignment and
186 : footprint of a memory region suitable for use as repair. Declaration
187 : friendly (e.g. a memory region declared as "fd_repair_t repair[1];"
188 : will automatically have the needed alignment and footprint). */
189 :
190 : FD_FN_CONST static inline ulong
191 0 : fd_repair_align( void ) {
192 0 : return alignof(fd_repair_t);
193 0 : }
194 :
195 : FD_FN_CONST static inline ulong
196 0 : fd_repair_footprint( void ) {
197 0 : return sizeof(fd_repair_t);
198 0 : }
199 :
200 : /* fd_repair_new formats an unused memory region for use as a repair.
201 : mem is a non-NULL pointer to this region in the local address space
202 : with the required footprint and alignment.
203 : Initializes repair with the public identity key. */
204 :
205 : void *
206 : fd_repair_new( void * shmem, fd_pubkey_t * identity_key );
207 :
208 : /* fd_repair_join joins the caller to the repair. repair points to the
209 : first byte of the memory region backing the repair in the caller's
210 : address space. Returns a pointer in the local address space to
211 : repair on success. */
212 :
213 : fd_repair_t *
214 : fd_repair_join( void * repair );
215 :
216 : /* fd_repair_leave leaves a current local join. Returns a pointer to
217 : the underlying shared memory region on success and NULL on failure
218 : (logs details). Reasons for failure include repair is NULL. */
219 :
220 : void *
221 : fd_repair_leave( fd_repair_t const * repair );
222 :
223 : /* fd_repair_delete unformats a memory region used as a repair. Assumes
224 : only the nobody is joined to the region. Returns a pointer to the
225 : underlying shared memory region or NULL if used obviously in error
226 : (e.g. repair is obviously not a repair ... logs details). The
227 : ownership of the memory region is transferred to the caller. */
228 :
229 : void *
230 : fd_repair_delete( void * repair );
231 :
232 : /* fd_repair_ping_de and fd_repair_ping_ser deserialize and serialize a
233 : ping message. */
234 : int
235 : fd_repair_ping_de( fd_repair_ping_t * ping,
236 : uchar const * buf,
237 : ulong buf_sz );
238 :
239 : int
240 : fd_repair_ping_ser( fd_repair_ping_t const * ping,
241 : uchar buf[static sizeof(fd_repair_ping_t)],
242 : ulong buf_sz );
243 :
244 : /* fd_repair_{pong,shred,highest_shred,orphan} creates and returns a
245 : pointer to a serialized {Pong,Shred,HighestShred,Orphan} message.
246 : Does not require the caller to provide memory, as Repair itself
247 : maintains a dedicated memory region (repair->msg) for buffering
248 : requests. Assumes repair->msg is not already buffering an existing
249 : request and can be overwritten. Returns a pointer to repair->msg on
250 : success, NULL on failure. */
251 :
252 : fd_repair_msg_t * fd_repair_pong ( fd_repair_t * repair, fd_hash_t * ping_token );
253 : 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 );
254 : 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 );
255 : fd_repair_msg_t * fd_repair_orphan ( fd_repair_t * repair, fd_pubkey_t const * to, ulong ts, uint nonce, ulong slot );
256 :
257 : /* fd_repair_sz returns the bincode-serialized sz of msg. */
258 :
259 : static inline ulong
260 0 : fd_repair_sz( fd_repair_msg_t const * msg ) {
261 0 : switch( msg->kind ) {
262 0 : case FD_REPAIR_KIND_PONG: return sizeof(uint) + sizeof(fd_repair_pong_t);
263 0 : case FD_REPAIR_KIND_SHRED: return sizeof(uint) + sizeof(fd_repair_shred_req_t);
264 0 : case FD_REPAIR_KIND_HIGHEST_SHRED: return sizeof(uint) + sizeof(fd_repair_highest_shred_req_t);
265 0 : case FD_REPAIR_KIND_ORPHAN: return sizeof(uint) + sizeof(fd_repair_orphan_req_t);
266 0 : default: FD_LOG_ERR(( "Unhandled repair kind %u", msg->kind ));
267 0 : }
268 0 : }
269 :
270 : /* preimage_pong takes a pong and takes the token in a ping and a
271 : FD_REPAIR_PONG_PREIMAGE_SZ-byte buffer and returns a pointer to a
272 : preimage that can be signed. */
273 :
274 : static inline uchar *
275 0 : preimage_pong( fd_hash_t const * ping_token, uchar * preimage_buf, ulong preimage_sz ) {
276 0 : # if FD_REPAIR_USE_HANDHOLDING
277 0 : if( FD_UNLIKELY( preimage_sz != FD_REPAIR_PONG_PREIMAGE_SZ ) ) {
278 0 : FD_LOG_ERR(( "preimage_sz %lu must be %lu", preimage_sz, FD_REPAIR_PONG_PREIMAGE_SZ ));
279 0 : }
280 0 : # endif
281 0 : ulong prefix_sz = sizeof(FD_REPAIR_PONG_PREIMAGE_PREFIX) - 1 /* subtract NUL */;
282 0 : memcpy( preimage_buf, FD_REPAIR_PONG_PREIMAGE_PREFIX, prefix_sz );
283 0 : memcpy( preimage_buf + prefix_sz, ping_token, sizeof(fd_hash_t) );
284 0 : return preimage_buf;
285 0 : }
286 :
287 : /* preimage_req takes a repair request populated with all fields except
288 : for the signature, and returns a pointer to a preimage that can be
289 : signed. Modifies the msg in place.
290 :
291 : At the start of this function, the repair_msg_t should contain
292 :
293 : [ discriminant ] [ empty sig ] [ repair request fields ]
294 : ^ ^ ^
295 : 0 4 68
296 :
297 : https://github.com/solana-labs/solana/blob/master/core/src/repair/serve_repair.rs#L1258
298 :
299 : We want to sign over
300 : [ discriminant ] [ payload ]
301 : ^ ^
302 : buffer buffer+4
303 :
304 : We can do this without using any extra memory copying the discriminant
305 : to the last 4 bytes of the sig field, and returning a pointer to
306 : that copied location. The sig field should be overwritten with the
307 : actual signature value later, else the fd_repair_msg_t will be
308 : incorrect. */
309 :
310 : static inline uchar *
311 0 : preimage_req( fd_repair_msg_t * msg, ulong * preimage_sz ) {
312 0 : uchar * preimage = (uchar *)fd_type_pun(msg);
313 0 : preimage += sizeof(fd_ed25519_sig_t);
314 0 : FD_STORE( uint, preimage, msg->kind ); /* copy discriminant over */
315 0 : *preimage_sz = fd_repair_sz( msg ) - sizeof(fd_ed25519_sig_t);
316 0 : return preimage;
317 0 : }
318 : #endif /* HEADER_fd_src_discof_repair_fd_repair_h */
|