Line data Source code
1 : #ifndef HEADER_fd_src_flamenco_runtime_program_fd_program_cache_h
2 : #define HEADER_fd_src_flamenco_runtime_program_fd_program_cache_h
3 :
4 : #include "../../fd_flamenco_base.h"
5 : #include "../fd_acc_mgr.h"
6 : #include "../context/fd_exec_slot_ctx.h"
7 : #include "../../vm/syscall/fd_vm_syscall.h"
8 : #include "../fd_system_ids.h"
9 :
10 : /* fd_program_cache contains the core logic for the program cache's
11 : behavior, including accesses, insertions, updates, and verifies.
12 : Specifically, the program cache operates in a lazy manner where
13 : programs are only inserted / reverified when they are referenced
14 : in a transaction.
15 :
16 : The program cache lives in Funk and is fork-aware. The size of the
17 : program cache is bounded by the size of the Funk instance.
18 :
19 : On bootup, the program cache starts out empty. As programs (defined
20 : as accounts owned by one of the BPF loaders that can be invoked
21 : by the user) are referenced as account keys within transactions
22 : (either statically or through address lookup tables),
23 : `fd_program_cache_update_program()` accepts a pubkey as input and
24 : will attempt to either insert the program into the cache (performing
25 : sBPF + ELF validations) if missing, or reverify the program if
26 : needed.
27 :
28 : During transaction execution, we accumulate any program pubkeys that
29 : are deployed, extended, or upgraded (basically, any calls to
30 : `fd_deploy_program()` in the BPF loaders) since those instructions
31 : may have changed the program's executable data (closed / retracted
32 : programs also do, but see comments below on how we handle that).
33 : After a transaction is executed successfully, we iterate through
34 : these accumulated program keys and queue them for reverification.
35 : To mark a program for reverification, we simply update the
36 : `last_slot_modified` field to the current slot. When the program is
37 : next referenced in a transaction, `fd_program_cache_update_program()`
38 : will check if the program was modified since the last time it was
39 : reverified, and reverify the program accordingly to update the cache
40 : entry's sBPF info, calldests, etc. Once the program has been
41 : reverified, the entry's `last_slot_verified` field will be updated
42 : to the current slot.
43 :
44 : When a program is reverified and updated in the cache, we clone the
45 : record down to the current funk transaction from an ancestor, and
46 : then modify the record within the current funk transaction.
47 :
48 : A program cache entry may also be reverified after crossing an
49 : epoch boundary, even if it was not modified recently. This is because
50 : the network's active feature set may have changed, and any previously
51 : {valid,invalid} programs may now be {valid,invalid}. Therefore,
52 : if a program is referenced in a transaction that has not been
53 : reverified since the last epoch (regardless of it having been
54 : modified or not), it will be reverified and updated.
55 :
56 : If a program fails verification due to invalid ELF headers / sBPF
57 : loading failures, then we update the `failed_verification` tag,
58 : set `last_slot_verified` to the current slot, and set
59 : `last_slot_modified` to 0. All other fields have undefined value. A
60 : future program upgrade / network feature set change could make this
61 : program valid again.
62 :
63 : A key invariant with the program cache design is that it does not
64 : evict any entries - it only grows through the lifetime of the
65 : client's execution. Because of this invariant, we do not touch cache
66 : entries for programs which are closed, and instead let
67 : transaction-level account loading and BPF loader program checks
68 : catch cases where a user tries to invoke a closed program.
69 :
70 : Another key invariant is that for any given program, we will insert /
71 : update its cache entry at most ONCE per slot, and this will happen
72 : the FIRST time the program is referenced in a transaction before it
73 : is dispatched to the exec tiles. This is important because it ensures
74 : that the replay tile does not race with the exec tiles by updating
75 : the cache entry for a program while an exec tile is already executing
76 : it. Even if a program is upgraded in the same slot after it has been
77 : reverified, the network forbids invoking programs in the same slot as
78 : they are deployed / upgraded, so we can wait for a future slot to
79 : update the program cache entry when it is invoked next.
80 :
81 : EDGE CASES (and how we handle them):
82 : - Deploying programs
83 : - Deploying a program
84 : - `fd_program_cache_queue_program_for_reverification()` will
85 : not do anything because the program does not exist in the
86 : cache yet, and so there is nothing to update. The next time
87 : the program is referenced in a transaction,
88 : `fd_program_cache_update_program()` will see that the program
89 : is missing in the cache and will perform ELF / sBPF
90 : verifications and insert the entry into the cache.
91 : - Deploying and invoking in the same slot
92 : - BPF loader checks will fail because the program account's
93 : slot value will be equal to the current slot, which
94 : gets caught by the `DelayedVisibility` checks.
95 : - Upgrading programs
96 : - Upgrading a program
97 : - The program account will have been referenced as writable
98 : in the transaction, and thus
99 : `fd_program_cache_queue_program_for_reverification()` will
100 : update the `last_slot_modified` to queue the program
101 : for reverification the next time it is referenced in a
102 : transaction.
103 : - Upgrading and invoking in the same transaction
104 : - Same as "deploy" case
105 : - Closing programs
106 : - Closing a program
107 : - We do not touch the program cache here. We let account
108 : loading / BPF loader checks handle cases where a user may try
109 : to invoke a closed program.
110 : - Closing + invoking a program in the same transaction
111 : - The program account's state will be set to uninitialized /
112 : retracted, so any future instructions that invoke the program
113 : will fail when the BPF loader checks the account state.
114 : - Closing + invoking a program in separate transactions
115 : - The program account's owner will be set to the system program
116 : and thus fail account loading checks.
117 :
118 : TL;DR: Deploys and upgrades are treated the same way - if the cache
119 : entry exists, it's queued for reverification. If it doesn't, the
120 : program cache will verify and add it to the cache the next time it's
121 : invoked. Cases where the program is closed / retracted are not
122 : explicitly handled.
123 :
124 : Q: Is the program cache concurrency-safe?
125 : A: Yes. In any given slot, the replay tile first iterates through
126 : each transaction in a single-threaded manner. For each
127 : transaction, it will read the accounts and will call
128 : `fd_program_cache_update_program()` for each program, and then
129 : dispatch the transaction to the exec tiles. This means
130 : that the first time any program is referenced in any transaction
131 : in the slot, the replay tile will update the associated program
132 : cache entry before dispatching the transaction to any of the exec
133 : tiles. Furthermore, we are not allowed to insert / reverify a
134 : program cache entry more than once within a single slot. This
135 : guarantees that any read / write accesses for a particular program
136 : in the program cache by the exec / writer tiles will occur after
137 : the cache entries have been processed by the replay tile in any
138 : given slot. Furthermore, if the program was upgraded, the writer
139 : tile simply updates a single header in the existing program cache
140 : entry `last_slot_modified`, which is behind a blocking write lock.
141 : Note that if there is read-write or write-write-contention
142 : between two transactions for any accounts, the scheduler will
143 : ensure that those two transactions are scheduled and finalized
144 : in a serial manner. */
145 :
146 : /* `fd_program_cache_entry` defines the structure for a single
147 : program cache entry. */
148 : struct fd_program_cache_entry {
149 : ulong magic;
150 :
151 : /* For any programs that fail verification, we retain this flag for
152 : to prevent any reverification attempts for the remainder of the
153 : epoch / until the program is modified again (instead of removing
154 : them from the cache). When `failed_verification` is set,
155 : the values of all other fields except for
156 : `last_slot_verified` are undefined. Any invocations of a
157 : program that fails verification will continue to fail until the
158 : program is queued for reverification by an eligible BPF loader
159 : instruction (e.g. the program is upgraded). */
160 : uchar failed_verification;
161 :
162 : /* Stores the last slot the program was modified. This field is
163 : updated at the end of a transaction for a program account that was
164 : referenced as a writable account within an instruction. For any
165 : programs that are freshly added to the cache, this field is set to
166 : 0. */
167 : ulong last_slot_modified;
168 :
169 : /* Stores the last slot verification checks were ran for a program.
170 : Programs are reverified if they are mentioned in the current
171 : transaction, and if one of the following are true:
172 : - It is the first time they are referenced in a transaction in the
173 : current epoch
174 : - The program was a writable account in a transaction
175 :
176 : We reverify referenced programs at least once every epoch
177 : regardless of whether they were modified or not because changes
178 : in the active feature set may cause existing deployed programs
179 : to be invalided (e.g. stricter ELF / VM / sBPF checks). */
180 : ulong last_slot_verified;
181 :
182 : ulong entry_pc;
183 : ulong text_cnt;
184 : ulong text_off;
185 : ulong text_sz;
186 :
187 : ulong rodata_sz;
188 :
189 : /* We use offsets to store the calldests and rodata in order to keep
190 : the cache entry gaddr-aware so it can be passed around different
191 : tiles. */
192 : ulong calldests_shmem_off;
193 : ulong rodata_off;
194 :
195 : /* SBPF version, SIMD-0161 */
196 : ulong sbpf_version;
197 : };
198 : typedef struct fd_program_cache_entry fd_program_cache_entry_t;
199 :
200 : static inline uchar *
201 33 : fd_program_cache_get_rodata( fd_program_cache_entry_t const * cache_entry ) {
202 33 : return (uchar *)cache_entry + cache_entry->rodata_off;
203 33 : }
204 :
205 : static inline fd_sbpf_calldests_t *
206 33 : fd_program_cache_get_calldests_shmem( fd_program_cache_entry_t const * cache_entry ) {
207 33 : return (fd_sbpf_calldests_t *)((uchar *)cache_entry + cache_entry->calldests_shmem_off);
208 33 : }
209 :
210 : static inline fd_sbpf_calldests_t *
211 0 : fd_program_cache_get_calldests( fd_program_cache_entry_t const * cache_entry ) {
212 0 : return fd_sbpf_calldests_join( fd_program_cache_get_calldests_shmem( cache_entry ) );
213 0 : }
214 :
215 : /* arbitrary unique value, in this case
216 : echo -n "fd_program_cache_entry" | sha512sum | head -c 16 */
217 51 : #define FD_PROGRAM_CACHE_ENTRY_MAGIC 0xb45640baf006ddf6
218 :
219 : FD_PROTOTYPES_BEGIN
220 :
221 : fd_program_cache_entry_t *
222 : fd_program_cache_entry_new( void * mem,
223 : fd_sbpf_elf_info_t const * elf_info,
224 : ulong last_slot_modified,
225 : ulong last_slot_verified );
226 :
227 : ulong
228 : fd_program_cache_entry_footprint( fd_sbpf_elf_info_t const * elf_info );
229 :
230 : /* Gets the program cache funk record key for a given program pubkey. */
231 : fd_funk_rec_key_t
232 : fd_program_cache_key( fd_pubkey_t const * pubkey );
233 :
234 : /* Loads a single program cache entry for a given pubkey. Returns 0 on
235 : success and -1 on failure. On success, `*cache_entry` holds a pointer
236 : to the program cache entry. */
237 : int
238 : fd_program_cache_load_entry( fd_funk_t const * funk,
239 : fd_funk_txn_t const * funk_txn,
240 : fd_pubkey_t const * program_pubkey,
241 : fd_program_cache_entry_t const ** cache_entry );
242 :
243 : /* Parses the programdata from a program account. Returns a pointer to
244 : the program data and sets `out_program_data_len` on success. Returns
245 : NULL on failure or if the program account is not owned by a BPF
246 : loader program ID, and leaves `out_program_data_len` in an undefined
247 : state. Reasons for failure vary on the loader version. See the
248 : respective functions in this file for more details.
249 :
250 : This is essentially load_program_with_pubkey(), sans the ELF parsing
251 : and whatnot which is done in load_program_from_bytes(). */
252 : uchar const *
253 : fd_program_cache_get_account_programdata( fd_funk_t const * funk,
254 : fd_funk_txn_t const * funk_txn,
255 : fd_txn_account_t const * program_acc,
256 : ulong * out_program_data_len );
257 :
258 : /* Updates the program cache for a single program. This function is
259 : called for every program that is referenced in a transaction, plus
260 : every single account in a lookup table referenced in the transaction.
261 : This function...
262 : - Accepts a pubkey and reads the programdata from the account
263 : - Creates a program cache entry for the program if it doesn't exist
264 : in the cache already
265 : - Reverifies programs if either...
266 : - The program was recently modified in a STRICTLY PRIOR SLOT
267 : - The program has not been verified yet for the current epoch
268 : - Invalidated programs that fail ELF / sBPF verification
269 : - Updates the program cache entry for the program after
270 : reverification (syscalls, calldests, etc)
271 :
272 : With this design, the program cache is designed to only grow as new
273 : programs are deployed / invoked. If a program fails verification, it
274 : stays in the cache so that repeated calls won't DOS the validator by
275 : forcing reverifications (since we won't be able to distinguish failed
276 : verifications from new deployments).
277 :
278 : When a program is reverified (and the cache entry already exists in
279 : some ancestor funk transaction), we clone the record down to the
280 : current funk transaction and acquire a blocking write lock on the
281 : cloned funk record. */
282 : void
283 : fd_program_cache_update_program( fd_exec_slot_ctx_t * slot_ctx,
284 : fd_pubkey_t const * program_key,
285 : fd_spad_t * runtime_spad );
286 :
287 : /* Queues a single program account for reverification. This function
288 : queries the cache for an existing entry and queues it for
289 : reverification by setting the `last_slot_modified` field in the
290 : program cache entry to the current slot. If the cache entry
291 : for the program does not exist yet (e.g. newly deployed programs),
292 : this function does nothing and instead,
293 : `fd_program_cache_publish_failed_verification_rec()` will insert
294 : the program into the cache.
295 :
296 : If the record exists in the program cache, this function will
297 : acquire a write-lock on the program cache entry. */
298 : void
299 : fd_program_cache_queue_program_for_reverification( fd_funk_t * funk,
300 : fd_funk_txn_t * funk_txn,
301 : fd_pubkey_t const * program_key,
302 : ulong current_slot );
303 :
304 : FD_PROTOTYPES_END
305 :
306 : #endif /* HEADER_fd_src_flamenco_runtime_program_fd_program_cache_h */
|