Line data Source code
1 : #include "fd_bpf_loader_serialization.h"
2 : #include "../fd_borrowed_account.h"
3 : #include "../fd_runtime.h"
4 :
5 : /* This file is responsible for serializing and deserializing
6 : the input region of the BPF virtual machine. The input region contains
7 : instruction information, account metadata, and account data. The high level
8 : format is as follows:
9 :
10 : [ account 1 metadata, account 1 data, account 2 metadata, account 2 data, ...,
11 : account N metadata, account N data, instruction info. ]
12 :
13 : This format by no means comprehensive, but it should give an idea of how
14 : the input region is laid out. When direct mapping is not enabled, the input
15 : region is stored as a single contiguous buffer. This buffer in the host
16 : address space is then mapped to the VM virtual address space (the range
17 : starting with 0x400...). This means to serialize into the input region, we
18 : need to copy in the account metadata and account data into the buffer for
19 : each account. Everything must get copied out after execution is complete.
20 : A consequence of this is that a memcpy for the account data is required
21 : for each serialize and deserialize operation: this can potentially become
22 : expensive if there are many accounts and many nested CPI calls. Also, the
23 : entire memory region is treated as writable even though many accounts are
24 : read-only. This means that for all read-only accounts, a memcmp must be done
25 : while deserializing to make sure that the account (meta)data has not changed.
26 :
27 : Direct mapping offers a solution to this by introducing a more sophisticated
28 : memory translation protocol. Now the account data is not copied into a single
29 : contiguous buffer, but instead a borrowed account's data is directly mapped
30 : into the VM's virtual address space. The host memory for the input region is
31 : now represented by a list of fragmented memory regions. These sub regions
32 : also have different write permissions. This should solve the problem of
33 : having to memcpy/memcmp account data regions (which can be up to 10MiB each).
34 : There is some nuance to this, as the account data can be resized. This means
35 : that memcpys for account data regions can't totally be avoided.
36 :
37 : SERIALIZATION BEHAVIOR
38 : ==========================================
39 :
40 : This implementation supports three distinct serialization modes based on two
41 : feature flags: stricter_abi_and_runtime_constraints and
42 : account_data_direct_mapping.
43 :
44 : MODE 1
45 : --------------------------------------
46 : stricter_abi_and_runtime_constraints = false
47 : account_data_direct_mapping = false
48 :
49 : Memory Layout:
50 : - Single contiguous buffer in host memory
51 : - Buffer contains: [metadata1, data1, realloc_buffer1, metadata2, data2,
52 : realloc_buffer2, ..., metadataN, dataN, realloc_bufferN, instruction_info]
53 : - Each account gets: original data + MAX_PERMITTED_DATA_INCREASE (10KiB)
54 : - Padding added to maintain 16-byte alignment between accounts
55 : - Entire buffer is writable
56 :
57 : Memory Regions:
58 : - The entire input region buffer is mapped as one contiguous VM address
59 : space region
60 :
61 : Serialization Process:
62 : - Account data is memcpy'd into the buffer
63 : - 10KiB realloc buffer is zeroed out and appended after each account's data
64 : - Alignment padding is zeroed and added after realloc buffer
65 :
66 : Deserialization Process:
67 : - Account data must be memcpy'd back from buffer to borrowed account
68 : - For writable accounts: always copy data back
69 : - For read-only accounts: memcmp to verify data unchanged, error if modified
70 : - Account resizing allowed if account permissions permit it
71 :
72 : MODE 2
73 : -------------------------------------------
74 : stricter_abi_and_runtime_constraints = true
75 : account_data_direct_mapping = false
76 :
77 : Memory Layout:
78 : - Still uses a single contiguous buffer, but organized into fragmented
79 : regions.
80 : - Each account now has separate regions for metadata and data+realloc.
81 : - Buffer contains: [metadata1, data1+realloc1, metadata2, data2+realloc2, ...,
82 : metadataN, dataN+reallocN, instruction_info].
83 : - Each metadata region and data region tracked separately in
84 : input_mem_regions.
85 :
86 : Memory Regions:
87 : - For each account:
88 : * Region 0: Account metadata (writable)
89 : * Region 1: Account data + realloc space (writable if account is writable)
90 : - If the account is owned by the deprecated loader, no realloc region is
91 : created as the deprecated loader does not support resizing accounts.
92 :
93 : Serialization:
94 : - Account metadata serialized first, added as a memory region.
95 : - Account data memcpy'd into buffer - not directly mapped.
96 : - 10KiB realloc buffer zeroed and appended (not direct mapped).
97 : - Data region created pointing to copied data in buffer.
98 :
99 : MODE 3: Direct Mapping (requires stricter_abi_and_runtime_constraints)
100 : -----------------------------------------------
101 : stricter_abi_and_runtime_constraints = true
102 : account_data_direct_mapping = true
103 :
104 : This is very similar to stricter_abi_and_runtime_constraints, but account
105 : data is NOT copied into the input region buffer.
106 :
107 : Instead, the data region points directly to the staging area for the
108 : account in the transaction account's data. This staging area has enough
109 : space to hold the account data and the realloc buffer. Changes to this
110 : staging area will be written back to the account database in transaction
111 : finalization.
112 : */
113 :
114 : /* Add a new memory region to represent the input region. All of the memory
115 : regions here have sorted virtual addresses. These regions may or may not
116 : correspond to an account's data region. If it corresponds to metadata,
117 : the pubkey for the region will be NULL. */
118 : static void
119 : new_input_mem_region( fd_vm_input_region_t * input_mem_regions,
120 : uint * input_mem_regions_cnt,
121 : const uchar * buffer,
122 : ulong region_sz,
123 : ulong address_space_reserved,
124 : uchar is_writable,
125 0 : ulong acc_region_meta_idx ) {
126 :
127 : /* The start vaddr of the new region should be equal to start of the previous
128 : region added to the address space reserved for the region. */
129 0 : ulong vaddr_offset = *input_mem_regions_cnt==0UL ? 0UL : input_mem_regions[ *input_mem_regions_cnt-1U ].vaddr_offset +
130 0 : input_mem_regions[ *input_mem_regions_cnt-1U ].address_space_reserved;
131 0 : input_mem_regions[ *input_mem_regions_cnt ].is_writable = is_writable;
132 0 : input_mem_regions[ *input_mem_regions_cnt ].haddr = (ulong)buffer;
133 0 : input_mem_regions[ *input_mem_regions_cnt ].region_sz = (uint)region_sz;
134 0 : input_mem_regions[ *input_mem_regions_cnt ].address_space_reserved = address_space_reserved;
135 0 : input_mem_regions[ *input_mem_regions_cnt ].vaddr_offset = vaddr_offset;
136 0 : input_mem_regions[ *input_mem_regions_cnt ].acc_region_meta_idx = acc_region_meta_idx;
137 0 : (*input_mem_regions_cnt)++;
138 0 : }
139 :
140 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L127-L189 */
141 : /* This function handles casing for direct mapping being enabled as well as if
142 : the alignment is being stored. In the case where direct mapping is not
143 : enabled, we copy in the account data and a 10KiB buffer into the input region.
144 : These both go into the same memory buffer. However, when direct mapping is
145 : enabled, the account data and resizing buffers are represented by two
146 : different memory regions. In both cases, padding is used to maintain 8 byte
147 : alignment. If alignment is not required, then a resizing buffer is not used
148 : as the deprecated loader doesn't allow for resizing accounts. */
149 : static ulong
150 : write_account( fd_borrowed_account_t * account,
151 : uchar instr_acc_idx,
152 : uchar * * serialized_params,
153 : uchar * * serialized_params_start,
154 : fd_vm_input_region_t * input_mem_regions,
155 : uint * input_mem_regions_cnt,
156 : fd_vm_acc_region_meta_t * acc_region_metas,
157 : int is_loader_v1,
158 : int stricter_abi_and_runtime_constraints,
159 0 : int direct_mapping ) {
160 :
161 0 : uchar const * data = account ? fd_borrowed_account_get_data( account ) : NULL;
162 0 : ulong dlen = account ? fd_borrowed_account_get_data_len( account ) : 0UL;
163 :
164 0 : acc_region_metas[instr_acc_idx].original_data_len = dlen;
165 0 : acc_region_metas[instr_acc_idx].acct = account->acct;
166 :
167 : /* Legacy behavior: no stricter_abi_and_runtime_constraints (also implies no direct mapping)
168 : https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L131-L140 */
169 0 : if( !stricter_abi_and_runtime_constraints ) {
170 : /* Copy the account data into input region buffer */
171 0 : fd_memcpy( *serialized_params, data, dlen );
172 0 : *serialized_params += dlen;
173 :
174 0 : if( FD_LIKELY( !is_loader_v1 ) ) {
175 : /* Zero out padding bytes and max permitted data increase */
176 0 : ulong align_offset = fd_ulong_align_up( dlen, FD_BPF_ALIGN_OF_U128 ) - dlen;
177 0 : fd_memset( *serialized_params, 0, MAX_PERMITTED_DATA_INCREASE + align_offset );
178 0 : *serialized_params += MAX_PERMITTED_DATA_INCREASE + align_offset;
179 0 : }
180 0 : acc_region_metas[instr_acc_idx].region_idx = UINT_MAX;
181 0 : } else { /* stricter_abi_and_runtime_constraints == true */
182 :
183 : /* Set up account region metadata */
184 0 : acc_region_metas[instr_acc_idx].region_idx = *input_mem_regions_cnt;
185 :
186 : /* First, push on the region for the metadata that has just been serialized.
187 : This function will push the metadata in the serialized_params from
188 : serialized_params_start to serialized_params as a region to the input
189 : memory regions array.
190 :
191 : https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L142 */
192 0 : ulong region_sz = (ulong)(*serialized_params) - (ulong)(*serialized_params_start);
193 0 : new_input_mem_region( input_mem_regions, input_mem_regions_cnt, *serialized_params_start, region_sz, region_sz, 1U, ULONG_MAX );
194 :
195 : /* If direct mapping isn't enabled, then copy the account data in directly
196 : https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L144-L150 */
197 0 : if( !direct_mapping ) {
198 0 : fd_memcpy( *serialized_params, data, dlen );
199 0 : *serialized_params += dlen;
200 0 : if( FD_LIKELY( !is_loader_v1 ) ) {
201 0 : fd_memset( *serialized_params, 0, MAX_PERMITTED_DATA_INCREASE );
202 0 : *serialized_params += MAX_PERMITTED_DATA_INCREASE;
203 0 : }
204 0 : }
205 :
206 : /* Calculate address space reserved for account (data + realloc space)
207 : https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L151-L158 */
208 0 : ulong address_space_reserved = !is_loader_v1 ?
209 0 : fd_ulong_sat_add( dlen, MAX_PERMITTED_DATA_INCREASE ) : dlen;
210 :
211 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L159-L169 */
212 0 : if( address_space_reserved > 0 ) {
213 0 : int err = 0;
214 0 : uchar is_writable = !!(fd_borrowed_account_can_data_be_changed( account, &err ) && !err);
215 :
216 0 : if( !direct_mapping ) {
217 : /* Create region pointing to the copied data in buffer
218 : https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L160-L164 */
219 0 : uchar * data_start = *serialized_params - address_space_reserved;
220 0 : new_input_mem_region( input_mem_regions, input_mem_regions_cnt, data_start, dlen, address_space_reserved, is_writable, instr_acc_idx );
221 0 : } else {
222 : /* Direct mapping: create region pointing directly to account data */
223 0 : new_input_mem_region( input_mem_regions, input_mem_regions_cnt, data, dlen, address_space_reserved, is_writable, instr_acc_idx );
224 0 : }
225 0 : }
226 :
227 0 : *serialized_params_start = *serialized_params;
228 :
229 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L170-L186 */
230 0 : if( FD_LIKELY( !is_loader_v1 ) ) {
231 0 : ulong align_offset = fd_ulong_align_up( dlen, FD_BPF_ALIGN_OF_U128 ) - dlen;
232 0 : if( !direct_mapping ) {
233 : /* If direct mapping is not enabled, we do not align the start of each
234 : region metadata to FD_BPF_ALIGN_OF_U128, but we do align the start
235 : of the actual contents of the metadata region.
236 :
237 : This follows Agave's logic
238 : https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L173-L176 */
239 0 : fd_memset( *serialized_params, 0, align_offset );
240 0 : *serialized_params += align_offset;
241 0 : } else {
242 : /* If direct mapping is enabled, we align the start of each region
243 : metadata to FD_BPF_ALIGN_OF_U128. */
244 0 : fd_memset( *serialized_params, 0, FD_BPF_ALIGN_OF_U128 );
245 0 : *serialized_params += FD_BPF_ALIGN_OF_U128;
246 0 : *serialized_params_start += fd_ulong_sat_sub( FD_BPF_ALIGN_OF_U128, align_offset );
247 0 : }
248 0 : }
249 :
250 0 : return region_sz + address_space_reserved;
251 0 : }
252 :
253 0 : return 0UL;
254 0 : }
255 :
256 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L466 */
257 : static uchar *
258 : fd_bpf_loader_input_serialize_aligned( fd_exec_instr_ctx_t * ctx,
259 : ulong * sz,
260 : ulong * pre_lens,
261 : fd_vm_input_region_t * input_mem_regions,
262 : uint * input_mem_regions_cnt,
263 : fd_vm_acc_region_meta_t * acc_region_metas,
264 : int stricter_abi_and_runtime_constraints,
265 : int direct_mapping,
266 0 : ulong * instr_data_offset ) {
267 0 : fd_pubkey_t * txn_accs = ctx->txn_out->accounts.account_keys;
268 :
269 0 : uchar acc_idx_seen[ FD_INSTR_ACCT_MAX ] = {0};
270 0 : ushort dup_acc_idx[ FD_INSTR_ACCT_MAX ] = {0};
271 :
272 : /* 16-byte aligned buffer:
273 : https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L60
274 : */
275 0 : uchar * serialized_params = ctx->runtime->bpf_loader_serialization.serialization_mem[ ctx->runtime->instr.stack_sz-1UL ];
276 0 : uchar * serialized_params_start = serialized_params;
277 0 : uchar * curr_serialized_params_start = serialized_params;
278 :
279 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L522 */
280 0 : FD_STORE( ulong, serialized_params, ctx->instr->acct_cnt );
281 0 : serialized_params += sizeof(ulong);
282 :
283 : /* Iterate over accounts in the instruction to populate input region.
284 : https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L523-L557 */
285 0 : for( ushort i=0; i<ctx->instr->acct_cnt; i++ ) {
286 0 : uchar acc_idx = (uchar)ctx->instr->accounts[i].index_in_transaction;
287 0 : fd_pubkey_t * acc = &txn_accs[acc_idx];
288 :
289 0 : if( FD_UNLIKELY( acc_idx_seen[acc_idx] && dup_acc_idx[acc_idx] != i ) ) {
290 : /* Duplicate. Store 8 byte buffer to maintain alignment but store the
291 : account index in the first byte.
292 :
293 : https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L551-L555 */
294 0 : FD_STORE( ulong, serialized_params, 0UL );
295 0 : FD_STORE( uchar, serialized_params, (uchar)dup_acc_idx[acc_idx] );
296 0 : serialized_params += sizeof(ulong);
297 0 : } else {
298 0 : acc_idx_seen[acc_idx] = 1;
299 0 : dup_acc_idx[acc_idx] = i;
300 :
301 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L526 */
302 0 : FD_STORE( uchar, serialized_params, FD_NON_DUP_MARKER );
303 0 : serialized_params += sizeof(uchar);
304 :
305 : /* Borrow the account without checking the error, as it is guaranteed to exist
306 : https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L244-L257 */
307 0 : fd_guarded_borrowed_account_t view_acc = {0};
308 0 : fd_exec_instr_ctx_try_borrow_instr_account( ctx, i, &view_acc );
309 :
310 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L525 */
311 0 : fd_account_meta_t const * metadata = fd_borrowed_account_get_acc_meta( &view_acc );
312 :
313 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L527 */
314 0 : uchar is_signer = (uchar)fd_instr_acc_is_signer_idx( ctx->instr, (uchar)i, NULL );
315 0 : FD_STORE( uchar, serialized_params, is_signer );
316 0 : serialized_params += sizeof(uchar);
317 :
318 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L528 */
319 0 : uchar is_writable = (uchar)fd_instr_acc_is_writable_idx( ctx->instr, (uchar)i );
320 0 : FD_STORE( uchar, serialized_params, is_writable );
321 0 : serialized_params += sizeof(uchar);
322 :
323 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L529-L530 */
324 0 : uchar is_executable = (uchar)metadata->executable;
325 0 : FD_STORE( uchar, serialized_params, is_executable );
326 0 : serialized_params += sizeof(uchar);
327 :
328 : /* The original data len field is intentionally NOT populated. */
329 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L531 */
330 0 : uint padding_0 = 0U;
331 0 : FD_STORE( uint, serialized_params, padding_0 );
332 0 : serialized_params += sizeof(uint);
333 :
334 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L532 */
335 0 : fd_pubkey_t key = *acc;
336 0 : FD_STORE( fd_pubkey_t, serialized_params, key );
337 0 : acc_region_metas[i].expected_pubkey_offset = (uint)(serialized_params - curr_serialized_params_start);
338 0 : serialized_params += sizeof(fd_pubkey_t);
339 :
340 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L533 */
341 0 : fd_pubkey_t owner = *(fd_pubkey_t *)&metadata->owner;
342 0 : FD_STORE( fd_pubkey_t, serialized_params, owner );
343 0 : acc_region_metas[i].expected_owner_offset = (uint)(serialized_params - curr_serialized_params_start);
344 0 : serialized_params += sizeof(fd_pubkey_t);
345 :
346 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L534 */
347 0 : ulong lamports = metadata->lamports;
348 0 : FD_STORE( ulong, serialized_params, lamports );
349 0 : acc_region_metas[i].expected_lamports_offset = (uint)(serialized_params - curr_serialized_params_start);
350 0 : serialized_params += sizeof(ulong);
351 :
352 0 : ulong acc_data_len = metadata->dlen;
353 0 : pre_lens[i] = acc_data_len;
354 :
355 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L535 */
356 0 : ulong data_len = acc_data_len;
357 0 : FD_STORE( ulong, serialized_params, data_len );
358 0 : serialized_params += sizeof(ulong);
359 :
360 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L536 */
361 0 : write_account(
362 0 : &view_acc,
363 0 : (uchar)i,
364 0 : &serialized_params,
365 0 : &curr_serialized_params_start,
366 0 : input_mem_regions,
367 0 : input_mem_regions_cnt,
368 0 : acc_region_metas,
369 0 : 0,
370 0 : stricter_abi_and_runtime_constraints,
371 0 : direct_mapping );
372 :
373 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L537-L541 */
374 0 : FD_STORE( ulong, serialized_params, ULONG_MAX );
375 0 : serialized_params += sizeof(ulong);
376 0 : }
377 :
378 0 : }
379 :
380 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L558 */
381 0 : ulong instr_data_len = ctx->instr->data_sz;
382 0 : FD_STORE( ulong, serialized_params, instr_data_len );
383 0 : serialized_params += sizeof(ulong);
384 :
385 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/program-runtime/src/serialization.rs#L568 */
386 0 : ulong region_vaddr_offset = 0UL;
387 0 : if( *input_mem_regions_cnt > 0 ) {
388 0 : region_vaddr_offset = input_mem_regions[*input_mem_regions_cnt-1U].vaddr_offset +
389 0 : input_mem_regions[*input_mem_regions_cnt-1U].address_space_reserved;
390 0 : }
391 0 : *instr_data_offset = FD_VM_MEM_MAP_INPUT_REGION_START + region_vaddr_offset + (ulong)(serialized_params - curr_serialized_params_start);
392 :
393 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L559 */
394 0 : fd_memcpy( serialized_params, ctx->instr->data, instr_data_len );
395 0 : serialized_params += instr_data_len;
396 :
397 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L560 */
398 0 : FD_STORE( fd_pubkey_t, serialized_params, txn_accs[ctx->instr->program_id] );
399 0 : serialized_params += sizeof(fd_pubkey_t);
400 :
401 : /* Write out the final region. */
402 0 : ulong region_sz = (ulong)(serialized_params - curr_serialized_params_start);
403 0 : new_input_mem_region( input_mem_regions, input_mem_regions_cnt, curr_serialized_params_start,
404 0 : region_sz, region_sz, 1U, ULONG_MAX );
405 :
406 0 : *sz = (ulong)(serialized_params - serialized_params_start);
407 :
408 0 : return serialized_params_start;
409 0 : }
410 :
411 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L566-L653 */
412 : static int
413 : fd_bpf_loader_input_deserialize_aligned( fd_exec_instr_ctx_t * ctx,
414 : ulong const * pre_lens,
415 : uchar * buffer,
416 : ulong FD_FN_UNUSED buffer_sz,
417 : int stricter_abi_and_runtime_constraints,
418 0 : int direct_mapping ) {
419 : /* TODO: An optimization would be to skip ahead through non-writable accounts */
420 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L573 */
421 0 : ulong start = 0UL;
422 :
423 0 : uchar acc_idx_seen[256] = {0};
424 :
425 0 : start += sizeof(ulong); // number of accounts
426 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L574-L650 */
427 0 : for( ushort i=0; i<ctx->instr->acct_cnt; i++ ) {
428 0 : uchar acc_idx = (uchar)ctx->instr->accounts[i].index_in_transaction;
429 :
430 0 : start++; // position
431 :
432 : /* get the borrowed account
433 : https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L584-L585 */
434 0 : fd_guarded_borrowed_account_t view_acc = {0};
435 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, i, &view_acc );
436 :
437 0 : if( FD_UNLIKELY( acc_idx_seen[acc_idx] ) ) {
438 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L582 */
439 0 : start += 7UL;
440 0 : } else {
441 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L586-L590 */
442 0 : acc_idx_seen[acc_idx] = 1;
443 0 : start += sizeof(uchar) // is_signer
444 0 : + sizeof(uchar) // is_writable
445 0 : + sizeof(uchar) // executable
446 0 : + sizeof(uint) // original_data_len
447 0 : + sizeof(fd_pubkey_t); // key
448 :
449 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L591-L593 */
450 :
451 0 : fd_pubkey_t * owner = (fd_pubkey_t *)(buffer+start);
452 0 : start += sizeof(fd_pubkey_t); // owner
453 :
454 0 : ulong lamports = FD_LOAD( ulong, buffer+start );
455 0 : if( lamports!=fd_borrowed_account_get_lamports( &view_acc ) ) {
456 0 : int err = fd_borrowed_account_set_lamports( &view_acc, lamports );
457 0 : if( FD_UNLIKELY( err ) ) {
458 0 : return err;
459 0 : }
460 0 : }
461 0 : start += sizeof(ulong); // lamports
462 :
463 0 : ulong post_len = FD_LOAD( ulong, buffer+start );
464 0 : start += sizeof(ulong); // data length
465 :
466 0 : ulong pre_len = pre_lens[i];
467 0 : ulong alignment_offset = fd_ulong_align_up( pre_len, FD_BPF_ALIGN_OF_U128 ) - pre_len;
468 :
469 0 : uchar * post_data = buffer+start;
470 :
471 0 : fd_account_meta_t const * metadata_check = fd_borrowed_account_get_acc_meta( &view_acc );
472 0 : if( FD_UNLIKELY( fd_ulong_sat_sub( post_len, metadata_check->dlen )>MAX_PERMITTED_DATA_INCREASE ||
473 0 : post_len>MAX_PERMITTED_DATA_LENGTH ) ) {
474 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC;
475 0 : }
476 :
477 0 : int can_data_be_changed_err = 0;
478 0 : if( !stricter_abi_and_runtime_constraints ) {
479 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L617-L627 */
480 :
481 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L618-L620 */
482 0 : if( FD_UNLIKELY( start + post_len > buffer_sz ) ) {
483 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
484 0 : }
485 :
486 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L621-L626 */
487 0 : int can_data_be_resized_err = 0;
488 0 : if( fd_borrowed_account_can_data_be_resized( &view_acc, post_len, &can_data_be_resized_err ) &&
489 0 : fd_borrowed_account_can_data_be_changed( &view_acc, &can_data_be_changed_err ) ) {
490 0 : int set_data_err = fd_borrowed_account_set_data_from_slice( &view_acc, post_data, post_len );
491 0 : if( FD_UNLIKELY( set_data_err ) ) {
492 0 : return set_data_err;
493 0 : }
494 0 : } else {
495 0 : if( FD_UNLIKELY( fd_borrowed_account_get_data_len( &view_acc )!=post_len ||
496 0 : memcmp( fd_borrowed_account_get_data( &view_acc ), post_data, post_len ) ) ) {
497 0 : return can_data_be_resized_err ? can_data_be_resized_err : can_data_be_changed_err;
498 0 : }
499 0 : }
500 :
501 0 : } else if( !direct_mapping && fd_borrowed_account_can_data_be_changed( &view_acc, &can_data_be_changed_err ) ) {
502 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L629-L631 */
503 0 : if( FD_UNLIKELY( start + post_len > buffer_sz ) ) {
504 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
505 0 : }
506 :
507 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L627-L633 */
508 0 : int set_data_err = fd_borrowed_account_set_data_from_slice( &view_acc, post_data, post_len );
509 0 : if( FD_UNLIKELY( set_data_err ) ) {
510 0 : return set_data_err;
511 0 : }
512 0 : } else if( fd_borrowed_account_get_data_len( &view_acc ) != post_len ) {
513 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L633-L635 */
514 0 : int set_data_length_err = fd_borrowed_account_set_data_length( &view_acc, post_len );
515 0 : if( FD_UNLIKELY( set_data_length_err ) ) {
516 0 : return set_data_length_err;
517 0 : }
518 0 : }
519 :
520 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L636-L644 */
521 0 : if( !( stricter_abi_and_runtime_constraints && direct_mapping ) ) {
522 0 : start += fd_ulong_sat_add( MAX_PERMITTED_DATA_INCREASE, fd_ulong_sat_add( pre_len, alignment_offset ) );
523 0 : } else {
524 0 : start += FD_BPF_ALIGN_OF_U128;
525 0 : }
526 :
527 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L645 */
528 0 : start += sizeof(ulong); // rent epoch
529 0 : if( memcmp( fd_borrowed_account_get_owner( &view_acc ), owner, sizeof(fd_pubkey_t) ) ) {
530 0 : int err = fd_borrowed_account_set_owner( &view_acc, owner );
531 0 : if( FD_UNLIKELY( err ) ) {
532 0 : return err;
533 0 : }
534 0 : }
535 0 : }
536 0 : }
537 :
538 0 : return FD_EXECUTOR_INSTR_SUCCESS;
539 0 : }
540 :
541 : static uchar *
542 : fd_bpf_loader_input_serialize_unaligned( fd_exec_instr_ctx_t * ctx,
543 : ulong * sz,
544 : ulong * pre_lens,
545 : fd_vm_input_region_t * input_mem_regions,
546 : uint * input_mem_regions_cnt,
547 : fd_vm_acc_region_meta_t * acc_region_metas,
548 : int stricter_abi_and_runtime_constraints,
549 : int direct_mapping,
550 0 : ulong * instr_data_offset ) {
551 0 : fd_pubkey_t const * txn_accs = ctx->txn_out->accounts.account_keys;
552 :
553 0 : uchar acc_idx_seen[FD_INSTR_ACCT_MAX] = {0};
554 0 : ushort dup_acc_idx[FD_INSTR_ACCT_MAX] = {0};
555 :
556 : /* 16-byte aligned buffer:
557 : https://github.com/anza-xyz/agave/blob/v2.2.13/programs/bpf_loader/src/serialization.rs#L32
558 : */
559 0 : uchar * serialized_params = ctx->runtime->bpf_loader_serialization.serialization_mem[ ctx->runtime->instr.stack_sz-1UL ];
560 0 : uchar * serialized_params_start = serialized_params;
561 0 : uchar * curr_serialized_params_start = serialized_params;
562 :
563 0 : FD_STORE( ulong, serialized_params, ctx->instr->acct_cnt );
564 0 : serialized_params += sizeof(ulong);
565 :
566 0 : for( ushort i=0; i<ctx->instr->acct_cnt; i++ ) {
567 0 : uchar acc_idx = (uchar)ctx->instr->accounts[i].index_in_transaction;
568 0 : fd_pubkey_t const * acc = &txn_accs[acc_idx];
569 :
570 0 : if( FD_UNLIKELY( acc_idx_seen[acc_idx] && dup_acc_idx[acc_idx] != i ) ) {
571 : // Duplicate
572 0 : FD_STORE( uchar, serialized_params, (uchar)dup_acc_idx[acc_idx] );
573 0 : serialized_params += sizeof(uchar);
574 0 : } else {
575 0 : acc_idx_seen[acc_idx] = 1;
576 0 : dup_acc_idx[acc_idx] = i;
577 :
578 0 : FD_STORE( uchar, serialized_params, FD_NON_DUP_MARKER );
579 0 : serialized_params += sizeof(uchar);
580 :
581 : /* Borrow the account without checking the error, as it is guaranteed to exist
582 : https://github.com/anza-xyz/agave/blob/v2.1.4/programs/bpf_loader/src/serialization.rs#L225 */
583 0 : fd_guarded_borrowed_account_t view_acc = {0};
584 0 : fd_exec_instr_ctx_try_borrow_instr_account( ctx, i, &view_acc );
585 :
586 0 : fd_account_meta_t const * metadata = fd_borrowed_account_get_acc_meta( &view_acc );
587 :
588 0 : pre_lens[i] = metadata->dlen;
589 :
590 0 : uchar is_signer = (uchar)fd_instr_acc_is_signer_idx( ctx->instr, (uchar)i, NULL );
591 0 : FD_STORE( uchar, serialized_params, is_signer );
592 0 : serialized_params += sizeof(uchar);
593 :
594 0 : uchar is_writable = (uchar)fd_instr_acc_is_writable_idx( ctx->instr, (uchar)i );
595 0 : FD_STORE( uchar, serialized_params, is_writable );
596 0 : serialized_params += sizeof(uchar);
597 :
598 0 : fd_pubkey_t key = *acc;
599 0 : FD_STORE( fd_pubkey_t, serialized_params, key );
600 0 : acc_region_metas[i].expected_pubkey_offset = (uint)(serialized_params - curr_serialized_params_start);
601 0 : serialized_params += sizeof(fd_pubkey_t);
602 :
603 0 : ulong lamports = metadata->lamports;
604 0 : FD_STORE( ulong, serialized_params, lamports );
605 0 : acc_region_metas[i].expected_lamports_offset = (uint)(serialized_params - curr_serialized_params_start);
606 0 : serialized_params += sizeof(ulong);
607 :
608 0 : ulong acc_data_len = metadata->dlen;
609 0 : FD_STORE( ulong, serialized_params, acc_data_len );
610 0 : serialized_params += sizeof(ulong);
611 :
612 0 : ulong next_region_offset = write_account( &view_acc, (uchar)i,
613 0 : &serialized_params, &curr_serialized_params_start,
614 0 : input_mem_regions, input_mem_regions_cnt, acc_region_metas, 1,
615 0 : stricter_abi_and_runtime_constraints, direct_mapping );
616 :
617 0 : fd_pubkey_t owner = *(fd_pubkey_t *)&metadata->owner;
618 0 : FD_STORE( fd_pubkey_t, serialized_params, owner );
619 0 : acc_region_metas[i].expected_owner_offset = (uint)next_region_offset;
620 0 : serialized_params += sizeof(fd_pubkey_t);
621 :
622 0 : uchar is_executable = (uchar)metadata->executable;
623 0 : FD_STORE( uchar, serialized_params, is_executable );
624 0 : serialized_params += sizeof(uchar);
625 :
626 0 : FD_STORE( ulong, serialized_params, ULONG_MAX );
627 0 : serialized_params += sizeof(ulong);
628 0 : }
629 0 : }
630 :
631 0 : ulong instr_data_len = ctx->instr->data_sz;
632 0 : FD_STORE( ulong, serialized_params, instr_data_len );
633 0 : serialized_params += sizeof(ulong);
634 :
635 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/program-runtime/src/serialization.rs#L400 */
636 0 : ulong region_vaddr_offset = 0UL;
637 0 : if( *input_mem_regions_cnt > 0 ) {
638 0 : region_vaddr_offset = input_mem_regions[*input_mem_regions_cnt-1U].vaddr_offset +
639 0 : input_mem_regions[*input_mem_regions_cnt-1U].address_space_reserved;
640 0 : }
641 0 : *instr_data_offset = FD_VM_MEM_MAP_INPUT_REGION_START + region_vaddr_offset + (ulong)(serialized_params - curr_serialized_params_start);
642 :
643 0 : fd_memcpy( serialized_params, ctx->instr->data, instr_data_len );
644 0 : serialized_params += instr_data_len;
645 :
646 0 : FD_STORE( fd_pubkey_t, serialized_params, txn_accs[ctx->instr->program_id] );
647 0 : serialized_params += sizeof(fd_pubkey_t);
648 :
649 0 : *sz = (ulong)(serialized_params - serialized_params_start);
650 :
651 0 : ulong region_sz = (ulong)(serialized_params - curr_serialized_params_start);
652 0 : new_input_mem_region( input_mem_regions, input_mem_regions_cnt, curr_serialized_params_start,
653 0 : region_sz, region_sz, 1U, ULONG_MAX );
654 :
655 0 : return serialized_params_start;
656 0 : }
657 :
658 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L404 */
659 : static int
660 : fd_bpf_loader_input_deserialize_unaligned( fd_exec_instr_ctx_t * ctx,
661 : ulong const * pre_lens,
662 : uchar * input,
663 : ulong input_sz,
664 : int stricter_abi_and_runtime_constraints,
665 0 : int direct_mapping ) {
666 0 : uchar * input_cursor = input;
667 0 : uchar acc_idx_seen[256] = {0};
668 :
669 0 : input_cursor += sizeof(ulong);
670 :
671 0 : for( ushort i=0; i<ctx->instr->acct_cnt; i++ ) {
672 0 : uchar acc_idx = (uchar)ctx->instr->accounts[i].index_in_transaction;
673 :
674 0 : input_cursor++; /* is_dup */
675 0 : if( FD_UNLIKELY( acc_idx_seen[acc_idx] ) ) {
676 : /* no-op */
677 0 : } else {
678 0 : acc_idx_seen[acc_idx] = 1;
679 0 : input_cursor += sizeof(uchar) + /* is_signer */
680 0 : sizeof(uchar) + /* is_writable */
681 0 : sizeof(fd_pubkey_t); /* key */
682 :
683 : /* https://github.com/anza-xyz/agave/blob/v2.1.4/programs/bpf_loader/src/serialization.rs#L378 */
684 0 : fd_guarded_borrowed_account_t view_acc = {0};
685 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, i, &view_acc );
686 :
687 0 : ulong lamports = FD_LOAD( ulong, input_cursor );
688 0 : if( fd_borrowed_account_get_acc_meta( &view_acc ) && fd_borrowed_account_get_lamports( &view_acc )!=lamports ) {
689 0 : int err = fd_borrowed_account_set_lamports( &view_acc, lamports );
690 0 : if( FD_UNLIKELY( err ) ) {
691 0 : return err;
692 0 : }
693 0 : }
694 :
695 0 : input_cursor += sizeof(ulong); /* lamports */
696 :
697 0 : ulong post_len = FD_LOAD( ulong, input_cursor );
698 0 : input_cursor += sizeof(ulong); /* data length */
699 :
700 0 : ulong pre_len = pre_lens[i];
701 0 : uchar * post_data = input_cursor;
702 :
703 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L436-L446 */
704 0 : int can_data_be_changed_err = 0;
705 0 : if( !stricter_abi_and_runtime_constraints ) {
706 0 : int can_data_be_resized_err = 0;
707 0 : if( fd_borrowed_account_can_data_be_resized( &view_acc, pre_len, &can_data_be_resized_err ) &&
708 0 : fd_borrowed_account_can_data_be_changed( &view_acc, &can_data_be_changed_err ) ) {
709 0 : int set_data_err = fd_borrowed_account_set_data_from_slice( &view_acc, post_data, pre_len );
710 0 : if( FD_UNLIKELY( set_data_err ) ) {
711 0 : return set_data_err;
712 0 : }
713 0 : } else if( fd_borrowed_account_get_data_len( &view_acc ) != pre_len ||
714 0 : memcmp( post_data, fd_borrowed_account_get_data( &view_acc ), pre_len ) ) {
715 0 : return can_data_be_resized_err ? can_data_be_resized_err : can_data_be_changed_err;
716 0 : }
717 0 : } else if( !direct_mapping && fd_borrowed_account_can_data_be_changed( &view_acc, &can_data_be_changed_err ) ) {
718 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L446-L452 */
719 0 : int set_data_err = fd_borrowed_account_set_data_from_slice( &view_acc, post_data, post_len );
720 0 : if( FD_UNLIKELY( set_data_err ) ) {
721 0 : return set_data_err;
722 0 : }
723 0 : } else if( fd_borrowed_account_get_data_len( &view_acc ) != pre_len ) {
724 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L452-L454 */
725 0 : int set_data_length_err = fd_borrowed_account_set_data_length( &view_acc, pre_len );
726 0 : if( FD_UNLIKELY( set_data_length_err ) ) {
727 0 : return set_data_length_err;
728 0 : }
729 0 : }
730 :
731 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L455-L457 */
732 0 : if( !( stricter_abi_and_runtime_constraints && direct_mapping ) ) {
733 0 : input_cursor += pre_len;
734 0 : }
735 0 : input_cursor += sizeof(fd_pubkey_t) + /* owner */
736 0 : sizeof(uchar) + /* executable */
737 0 : sizeof(ulong); /* rent_epoch*/
738 0 : }
739 0 : }
740 :
741 0 : if( FD_UNLIKELY( input_cursor>input+input_sz ) ) {
742 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
743 0 : }
744 :
745 0 : return 0;
746 0 : }
747 :
748 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L221 */
749 : int
750 : fd_bpf_loader_input_serialize_parameters( fd_exec_instr_ctx_t * instr_ctx,
751 : ulong * sz,
752 : ulong * pre_lens,
753 : fd_vm_input_region_t * input_mem_regions,
754 : uint * input_mem_regions_cnt,
755 : fd_vm_acc_region_meta_t * acc_region_metas,
756 : int stricter_abi_and_runtime_constraints,
757 : int direct_mapping,
758 : uchar is_deprecated,
759 : ulong * instr_data_offset,
760 0 : uchar ** out /* output */ ) {
761 :
762 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/program-runtime/src/serialization.rs#L234-L237 */
763 0 : ulong num_ix_accounts = instr_ctx->instr->acct_cnt;
764 0 : if( FD_UNLIKELY( num_ix_accounts>=FD_INSTR_ACCT_MAX ) ) {
765 0 : return FD_EXECUTOR_INSTR_ERR_MAX_ACCS_EXCEEDED;
766 0 : }
767 :
768 : /* TODO: Like Agave's serialization functions, ours should probably return error codes
769 :
770 : https://github.com/anza-xyz/agave/blob/v2.1.11/programs/bpf_loader/src/serialization.rs#L237-L251 */
771 0 : if( FD_UNLIKELY( is_deprecated ) ) {
772 0 : *out = fd_bpf_loader_input_serialize_unaligned( instr_ctx, sz, pre_lens,
773 0 : input_mem_regions, input_mem_regions_cnt,
774 0 : acc_region_metas, stricter_abi_and_runtime_constraints,
775 0 : direct_mapping, instr_data_offset );
776 0 : } else {
777 0 : *out = fd_bpf_loader_input_serialize_aligned( instr_ctx, sz, pre_lens,
778 0 : input_mem_regions, input_mem_regions_cnt,
779 0 : acc_region_metas, stricter_abi_and_runtime_constraints,
780 0 : direct_mapping, instr_data_offset );
781 0 : }
782 :
783 0 : return FD_EXECUTOR_INSTR_SUCCESS;
784 0 : }
785 :
786 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L284-L311 */
787 : int
788 : fd_bpf_loader_input_deserialize_parameters( fd_exec_instr_ctx_t * ctx,
789 : ulong const * pre_lens,
790 : uchar * input,
791 : ulong input_sz,
792 : int stricter_abi_and_runtime_constraints,
793 : int direct_mapping,
794 0 : uchar is_deprecated ) {
795 0 : if( FD_UNLIKELY( is_deprecated ) ) {
796 0 : return fd_bpf_loader_input_deserialize_unaligned(
797 0 : ctx, pre_lens, input, input_sz, stricter_abi_and_runtime_constraints, direct_mapping );
798 0 : } else {
799 0 : return fd_bpf_loader_input_deserialize_aligned(
800 0 : ctx, pre_lens, input, input_sz, stricter_abi_and_runtime_constraints, direct_mapping );
801 0 : }
802 0 : }
|