Line data Source code
1 : #include "fd_txn_build.h"
2 : #include "fd_txn.h"
3 : #include "fd_compact_u16.h"
4 : #include "../../flamenco/runtime/fd_system_ids_pp.h"
5 :
6 : FD_STATIC_ASSERT( FD_TXN_ACCT_CAT_PIN > FD_TXN_ACCT_CAT_ALL, flags );
7 : FD_STATIC_ASSERT( FD_TXN_ACCT_CAT_FEE_PAYER > FD_TXN_ACCT_CAT_ALL, flags );
8 :
9 : /* Implementation */
10 :
11 : fd_txn_builder_t *
12 : fd_txn_builder_new( fd_txn_builder_t * mem,
13 105 : ulong seed ) {
14 :
15 : /* Sanity check */
16 105 : if( FD_UNLIKELY(
17 105 : !mem ||
18 105 : !fd_ulong_is_aligned( (ulong)mem, alignof(fd_txn_builder_t) ) ) ) {
19 0 : return NULL;
20 0 : }
21 :
22 105 : fd_txn_builder_t * builder = mem;
23 105 : memset( builder, 0, sizeof(fd_txn_builder_t) );
24 105 : builder->alut_i = UCHAR_MAX; /* sentinel: no ALUT open */
25 105 : FD_TEST( fd_txn_b_addr_map_new( &builder->map, FD_TXN_B_ADDR_CHAIN_CNT, seed ) );
26 :
27 105 : return builder;
28 105 : }
29 :
30 : void *
31 105 : fd_txn_builder_delete( fd_txn_builder_t * builder ) {
32 105 : FD_TEST( fd_txn_b_addr_map_delete( &builder->map ) );
33 105 : return builder;
34 105 : }
35 :
36 : static ulong
37 : fd_txn_b_acct_acquire( fd_txn_builder_t * builder,
38 348 : fd_acct_addr_t const * acct_addr ) {
39 :
40 348 : ulong idx = fd_txn_b_addr_map_idx_query( &builder->map, acct_addr, ULONG_MAX, builder->acct );
41 348 : if( idx!=ULONG_MAX ) return idx;
42 :
43 324 : if( FD_UNLIKELY( builder->acct_cnt >= FD_TXN_ACCT_ADDR_MAX ) ) return ULONG_MAX;
44 324 : idx = builder->acct_cnt;
45 324 : fd_txn_b_acct_t * acct = &builder->acct[ idx ];
46 324 : acct->key = *acct_addr;
47 324 : builder->acct_map[ idx ] = (uchar)idx;
48 324 : builder->acct_rev[ idx ] = (uchar)idx;
49 324 : builder->acct_cnt++;
50 :
51 324 : fd_txn_b_addr_map_idx_insert( &builder->map, idx, builder->acct );
52 324 : return idx;
53 324 : }
54 :
55 : fd_txn_builder_t *
56 : fd_txn_builder_fee_payer_set( fd_txn_builder_t * builder,
57 105 : void const * fee_payer ) {
58 105 : if( FD_UNLIKELY( builder->fee_payer_set ) ) return NULL;
59 105 : fd_acct_addr_t addr = FD_LOAD( fd_acct_addr_t, fee_payer );
60 105 : ulong acct_idx = fd_txn_b_acct_acquire( builder, &addr );
61 105 : if( FD_UNLIKELY( acct_idx==ULONG_MAX ) ) return NULL;
62 105 : fd_txn_b_acct_t * acct = &builder->acct[ acct_idx ];
63 105 : acct->cat &= (uchar)~FD_TXN_ACCT_CAT_ALL;
64 105 : acct->cat |= FD_TXN_ACCT_CAT_WRITABLE_SIGNER | FD_TXN_ACCT_CAT_FEE_PAYER;
65 105 : builder->fee_payer_acct = (uchar)acct_idx;
66 105 : builder->fee_payer_set = 1;
67 105 : return builder;
68 105 : }
69 :
70 : static uint
71 : fd_txn_b_acct_cat_promote( uint prev_cat,
72 243 : uint req_cat ) {
73 243 : int const req_signer = ( (req_cat & FD_TXN_ACCT_CAT_SIGNER ) == FD_TXN_ACCT_CAT_SIGNER );
74 243 : int const req_writable = ( (req_cat & FD_TXN_ACCT_CAT_WRITABLE) == FD_TXN_ACCT_CAT_WRITABLE );
75 243 : int const is_signer = !!( prev_cat & FD_TXN_ACCT_CAT_SIGNER ) || req_signer;
76 243 : int const is_writable = !!( prev_cat & FD_TXN_ACCT_CAT_WRITABLE ) || req_writable;
77 243 : uint new_raw = is_signer
78 243 : ? ( is_writable ? FD_TXN_ACCT_CAT_WRITABLE_SIGNER : FD_TXN_ACCT_CAT_READONLY_SIGNER )
79 243 : : ( is_writable ? FD_TXN_ACCT_CAT_WRITABLE_NONSIGNER_IMM : FD_TXN_ACCT_CAT_READONLY_NONSIGNER_IMM );
80 :
81 243 : return (prev_cat & ~((uint)FD_TXN_ACCT_CAT_ALL)) | new_raw;
82 243 : }
83 :
84 : fd_txn_builder_t *
85 : fd_txn_builder_instr_open( fd_txn_builder_t * builder,
86 : void const * program_id,
87 : void const * data,
88 105 : ulong data_sz ) {
89 :
90 105 : if( FD_UNLIKELY( builder->alut_set ) ) return NULL;
91 :
92 : /* Program account */
93 :
94 105 : fd_acct_addr_t addr = FD_LOAD( fd_acct_addr_t, program_id );
95 105 : ulong program_acct_idx = fd_txn_b_acct_acquire( builder, &addr );
96 105 : if( FD_UNLIKELY( program_acct_idx==ULONG_MAX ) ) return NULL;
97 105 : fd_txn_b_acct_t * program_acct = &builder->acct[ program_acct_idx ];
98 105 : program_acct->cat = (uchar)fd_txn_b_acct_cat_promote( program_acct->cat, FD_TXN_ACCT_CAT_NONE );
99 105 : program_acct->cat |= FD_TXN_ACCT_CAT_PIN;
100 :
101 : /* Instruction data */
102 :
103 105 : ulong data_off = builder->data_bump_sz;
104 105 : ulong data_end = data_off + data_sz;
105 105 : if( FD_UNLIKELY( data_end>FD_TXN_B_DATA_BUMP_MAX ) ) return NULL;
106 105 : uchar * saved_data = builder->data_bump + data_off;
107 105 : fd_memcpy( saved_data, data, data_sz );
108 105 : builder->data_bump_sz = (ushort)( (ulong)builder->data_bump_sz + data_sz );
109 :
110 : /* Instruction */
111 :
112 105 : if( FD_UNLIKELY( builder->instr_cnt >= FD_TXN_INSTR_MAX ) ) return NULL;
113 105 : fd_txn_b_instr_t * instr = &builder->instr[ builder->instr_cnt++ ];
114 105 : instr->program_id = (uchar)program_acct_idx;
115 105 : instr->instr_acct0 = builder->instr_acct_cnt;
116 105 : instr->instr_acct_cnt = 0;
117 105 : instr->data_off = (ushort)data_off;
118 105 : instr->data_sz = (ushort)data_sz;
119 :
120 105 : return builder;
121 105 : }
122 :
123 : fd_txn_builder_t *
124 : fd_txn_builder_instr_account_push(
125 : fd_txn_builder_t * builder,
126 : void const * acct_addr,
127 : uint acct_cat
128 138 : ) {
129 138 : if( FD_UNLIKELY( builder->instr_cnt==0 ) ) return NULL;
130 138 : fd_txn_b_instr_t * instr = &builder->instr[ builder->instr_cnt-1 ];
131 :
132 : /* Transaction Account */
133 :
134 138 : fd_acct_addr_t addr = FD_LOAD( fd_acct_addr_t, acct_addr );
135 138 : ulong acct_idx = fd_txn_b_acct_acquire( builder, &addr );
136 138 : if( FD_UNLIKELY( acct_idx==ULONG_MAX ) ) return NULL;
137 138 : fd_txn_b_acct_t * acct = &builder->acct[ acct_idx ];
138 :
139 : /* Instruction Account */
140 :
141 138 : if( FD_UNLIKELY( instr->instr_acct_cnt >= FD_TXN_ACCT_ADDR_MAX ) ) return NULL;
142 138 : if( FD_UNLIKELY( builder->instr_acct_cnt >= FD_TXN_ACCT_ADDR_MAX ) ) return NULL;
143 138 : ulong instr_acct_idx = builder->instr_acct_cnt++;
144 138 : ulong exp_instr_acct_idx = (ulong)instr->instr_acct0 + (instr->instr_acct_cnt++);
145 138 : FD_TEST( instr_acct_idx==exp_instr_acct_idx );
146 138 : builder->instr_acct[ instr_acct_idx ] = (uchar)acct_idx;
147 :
148 : /* Promote access category */
149 :
150 138 : acct->cat = (uchar)fd_txn_b_acct_cat_promote( acct->cat, acct_cat );
151 :
152 138 : return builder;
153 138 : }
154 :
155 : void
156 105 : fd_txn_builder_instr_close( fd_txn_builder_t * builder ) {
157 105 : (void)builder;
158 105 : }
159 :
160 : fd_txn_builder_t *
161 : fd_txn_builder_nonce_set( fd_txn_builder_t * builder,
162 : void const * nonce_account,
163 0 : void const * nonce_authority ) {
164 : /* The advance nonce instruction must be the first instruction in the
165 : transaction, so reject if any instruction was already opened. */
166 0 : if( FD_UNLIKELY( builder->instr_cnt!=0 ) ) return NULL;
167 :
168 0 : static fd_acct_addr_t const system_prog_id = { .b = { SYS_PROG_ID } };
169 0 : static fd_acct_addr_t const recent_blockhashes_id = { .b = { SYSVAR_RECENT_BLKHASH_ID } };
170 0 : uchar const data[4] = { 0x04, 0x00, 0x00, 0x00 };
171 0 : if( FD_UNLIKELY( !fd_txn_builder_instr_open(
172 0 : builder, &system_prog_id, data, sizeof(data) ) ) ) return NULL;
173 0 : if( FD_UNLIKELY( !fd_txn_builder_instr_account_push(
174 0 : builder, nonce_account, FD_TXN_ACCT_CAT_WRITABLE ) ) ) return NULL;
175 0 : if( FD_UNLIKELY( !fd_txn_builder_instr_account_push(
176 0 : builder, &recent_blockhashes_id, FD_TXN_ACCT_CAT_NONE ) ) ) return NULL;
177 0 : if( FD_UNLIKELY( !fd_txn_builder_instr_account_push(
178 0 : builder, nonce_authority, FD_TXN_ACCT_CAT_SIGNER ) ) ) return NULL;
179 0 : fd_txn_builder_instr_close( builder );
180 0 : return builder;
181 0 : }
182 :
183 : fd_txn_builder_t *
184 : fd_txn_builder_alut_open( fd_txn_builder_t * builder,
185 0 : void const * alut_addr ) {
186 : /* The builder only serializes a single ALUT, so reject a second open. */
187 0 : if( FD_UNLIKELY( builder->alut_i!=UCHAR_MAX ) ) return NULL;
188 0 : fd_acct_addr_t addr = FD_LOAD( fd_acct_addr_t, alut_addr );
189 0 : ulong alut_i = fd_txn_b_acct_acquire( builder, &addr );
190 0 : if( FD_UNLIKELY( alut_i==ULONG_MAX ) ) return NULL;
191 0 : fd_txn_b_acct_t * alut_acct = &builder->acct[ alut_i ];
192 0 : alut_acct->cat = (uchar)fd_txn_b_acct_cat_promote( alut_acct->cat, FD_TXN_ACCT_CAT_NONE );
193 0 : alut_acct->cat |= FD_TXN_ACCT_CAT_PIN; /* ALUT itself cannot be in ALUT */
194 0 : builder->alut_i = (uchar)alut_i;
195 0 : return builder;
196 0 : }
197 :
198 : void
199 : fd_txn_builder_alut_address_push(
200 : fd_txn_builder_t * builder,
201 : void const * acct_addr,
202 : uint const alut_j
203 0 : ) {
204 0 : uint const alut_i = builder->alut_i;
205 :
206 : /* Requires an opened ALUT. alut_j is serialized as a u8. */
207 0 : if( FD_UNLIKELY( alut_i==UCHAR_MAX ) ) return;
208 0 : if( FD_UNLIKELY( alut_j>UCHAR_MAX ) ) return;
209 :
210 0 : fd_acct_addr_t addr = FD_LOAD( fd_acct_addr_t, acct_addr );
211 0 : ulong const acct_idx = fd_txn_b_addr_map_idx_query(
212 0 : &builder->map, &addr, ULONG_MAX, builder->acct );
213 0 : if( FD_UNLIKELY( acct_idx==ULONG_MAX ) ) return;
214 :
215 : /* Can account be demoted? */
216 :
217 0 : fd_txn_b_acct_t * acct = &builder->acct[ acct_idx ];
218 0 : if( FD_UNLIKELY( acct->cat & FD_TXN_ACCT_CAT_IS_PIN ) ) return;
219 :
220 : /* Already demoted to an ALUT? Ignore subsequent calls. */
221 :
222 0 : if( FD_UNLIKELY( acct->cat & FD_TXN_ACCT_CAT_ALT ) ) return;
223 :
224 : /* Demote account to ALUT */
225 :
226 0 : if( acct->cat & FD_TXN_ACCT_CAT_WRITABLE ) {
227 0 : acct->cat = FD_TXN_ACCT_CAT_WRITABLE_ALT;
228 0 : } else {
229 0 : acct->cat = FD_TXN_ACCT_CAT_READONLY_ALT;
230 0 : }
231 0 : acct->alut_i = (uchar)alut_i;
232 0 : acct->alut_j = alut_j;
233 :
234 0 : builder->alut_set = 1;
235 0 : }
236 :
237 : void
238 0 : fd_txn_builder_alut_close( fd_txn_builder_t * builder ) {
239 0 : (void)builder;
240 0 : }
241 :
242 : static inline uint
243 648 : fd_txn_b_acct_prio( fd_txn_b_acct_t const * acct ) {
244 648 : uint const cat = acct->cat;
245 648 : return (cat & FD_TXN_ACCT_CAT_FEE_PAYER) ? 0U : (uint)( fd_uint_find_lsb( cat & FD_TXN_ACCT_CAT_ALL )+1 );
246 648 : }
247 :
248 840 : #define FD_TXN_B_ACCT_PRIO_CNT 7UL
249 :
250 : /* fd_txn_b_bake prepares a transaction for building. For now, only
251 : sorts accounts. */
252 :
253 : static void
254 : fd_txn_b_bake( fd_txn_builder_t * builder,
255 105 : ulong cnt[ FD_TXN_B_ACCT_PRIO_CNT ] ) {
256 105 : ulong const acct_cnt = builder->acct_cnt;
257 :
258 105 : fd_memset( cnt, 0, FD_TXN_B_ACCT_PRIO_CNT*sizeof(ulong) );
259 429 : for( ulong i=0UL; i<acct_cnt; i++ ) cnt[ fd_txn_b_acct_prio( &builder->acct[ builder->acct_map[ i ] ] ) ]++;
260 :
261 105 : ulong off[ FD_TXN_B_ACCT_PRIO_CNT ];
262 105 : off[ 0 ] = 0UL;
263 735 : for( ulong i=1UL; i<FD_TXN_B_ACCT_PRIO_CNT; i++ ) off[ i ] = off[ i-1UL ] + cnt[ i-1UL ];
264 :
265 105 : uchar scratch[ FD_TXN_ACCT_ADDR_MAX ];
266 429 : for( ulong i=0UL; i<acct_cnt; i++ ) {
267 324 : uchar const acct_i = builder->acct_map[ i ];
268 324 : scratch[ off[ fd_txn_b_acct_prio( &builder->acct[ acct_i ] ) ]++ ] = acct_i;
269 324 : }
270 :
271 105 : fd_memcpy( builder->acct_map, scratch, acct_cnt );
272 429 : for( ulong i=0UL; i<acct_cnt; i++ ) builder->acct_rev[ builder->acct_map[ i ] ] = (uchar)i;
273 105 : }
274 :
275 : static inline uchar *
276 : fd_txn_b_cu16_write( uchar * out,
277 : uchar * end,
278 420 : ulong val ) {
279 420 : if( FD_UNLIKELY( val>USHORT_MAX ) ) return NULL;
280 420 : ulong enc_sz = 1UL + ( val>0x7FUL ) + ( val>0x3FFFUL );
281 420 : if( FD_UNLIKELY( out+enc_sz>end ) ) return NULL;
282 420 : out += fd_cu16_enc( (ushort)val, out );
283 420 : return out;
284 420 : }
285 :
286 : __attribute__((always_inline)) static inline uint
287 : fd_txn_build_core( fd_txn_builder_t * builder,
288 105 : uchar out_[ FD_TXN_MTU_V0 ] ) {
289 324 : #define ACCT( i ) (&builder->acct[ builder->acct_map[ i ] ])
290 :
291 105 : ulong cnt[ FD_TXN_B_ACCT_PRIO_CNT ];
292 105 : fd_txn_b_bake( builder, cnt );
293 :
294 105 : uchar * out = out_;
295 105 : uchar * const end = out + FD_TXN_MTU_V0;
296 :
297 105 : ulong const rw_sig_cnt = cnt[ 0 ] + cnt[ 1 ];
298 105 : ulong const ro_sig_cnt = cnt[ 2 ];
299 105 : ulong const rw_uns_cnt = cnt[ 3 ];
300 105 : ulong const ro_uns_cnt = cnt[ 4 ];
301 105 : ulong const rw_alt_cnt = cnt[ 5 ];
302 105 : ulong const ro_alt_cnt = cnt[ 6 ];
303 :
304 105 : ulong const sig_cnt = rw_sig_cnt + ro_sig_cnt;
305 105 : ulong const imm_cnt = sig_cnt + rw_uns_cnt + ro_uns_cnt;
306 105 : ulong const alt_cnt = rw_alt_cnt + ro_alt_cnt;
307 :
308 105 : if( FD_UNLIKELY( sig_cnt > FD_TXN_SIG_MAX ) ) return 0;
309 105 : if( FD_UNLIKELY( !builder->fee_payer_set ) ) return 0;
310 105 : if( FD_UNLIKELY( builder->acct_rev[ builder->fee_payer_acct ]!=0U ) ) return 0;
311 :
312 105 : out[0] = (uchar)sig_cnt;
313 105 : out++;
314 105 : fd_memset( out, 0, sig_cnt*FD_TXN_SIGNATURE_SZ );
315 105 : out += sig_cnt*FD_TXN_SIGNATURE_SZ;
316 105 : if( FD_UNLIKELY( out>end ) ) return 0;
317 :
318 105 : if( alt_cnt ) {
319 0 : if( FD_UNLIKELY( out>=end ) ) return 0;
320 0 : out[0] = 0x80; /* txn v0 */
321 0 : out++;
322 0 : }
323 :
324 105 : if( FD_UNLIKELY( out+3>end ) ) return 0;
325 105 : out[0] = (uchar)sig_cnt;
326 105 : out[1] = (uchar)ro_sig_cnt;
327 105 : out[2] = (uchar)ro_uns_cnt;
328 105 : out += 3;
329 :
330 105 : out = fd_txn_b_cu16_write( out, end, imm_cnt );
331 105 : if( FD_UNLIKELY( !out ) ) return 0;
332 :
333 105 : ulong addr_tbl_sz = imm_cnt*FD_TXN_ACCT_ADDR_SZ;
334 105 : if( FD_UNLIKELY( out+addr_tbl_sz>end ) ) return 0;
335 429 : for( ulong i=0UL; i<imm_cnt; i++ ) {
336 324 : fd_txn_b_acct_t * acct = ACCT( i );
337 324 : fd_memcpy( out, &acct->key, FD_TXN_ACCT_ADDR_SZ );
338 324 : out += FD_TXN_ACCT_ADDR_SZ;
339 324 : }
340 :
341 105 : if( FD_UNLIKELY( out+FD_TXN_ACCT_ADDR_SZ>end ) ) return 0;
342 105 : fd_memcpy( out, &builder->recent_blockhash, FD_TXN_ACCT_ADDR_SZ );
343 105 : out += FD_TXN_ACCT_ADDR_SZ;
344 :
345 105 : if( FD_UNLIKELY( out>=end ) ) return 0;
346 105 : ulong const instr_cnt = builder->instr_cnt;
347 105 : out = fd_txn_b_cu16_write( out, end, instr_cnt );
348 105 : if( FD_UNLIKELY( !out ) ) return 0;
349 :
350 210 : for( ulong j=0UL; j<instr_cnt; j++ ) {
351 105 : fd_txn_b_instr_t * instr = &builder->instr[ j ];
352 105 : ulong const instr_acct_cnt = instr->instr_acct_cnt;
353 :
354 105 : if( FD_UNLIKELY( out+1>end ) ) return 0;
355 105 : out[0] = builder->acct_rev[ instr->program_id ];
356 105 : out++;
357 :
358 105 : out = fd_txn_b_cu16_write( out, end, instr_acct_cnt );
359 105 : if( FD_UNLIKELY( !out ) ) return 0;
360 :
361 105 : if( FD_UNLIKELY( out+instr_acct_cnt>end ) ) return 0;
362 243 : for( ulong k=0UL; k<instr_acct_cnt; k++ ) {
363 138 : ulong const acct_idx = builder->acct_rev[ builder->instr_acct[ instr->instr_acct0 + k ] ];
364 138 : out[0] = (uchar)acct_idx;
365 138 : out++;
366 138 : }
367 :
368 105 : ulong const data_sz = instr->data_sz;
369 105 : out = fd_txn_b_cu16_write( out, end, data_sz );
370 105 : if( FD_UNLIKELY( !out ) ) return 0;
371 :
372 105 : if( FD_UNLIKELY( out+data_sz>end ) ) return 0;
373 105 : fd_memcpy( out, &builder->data_bump[ instr->data_off ], data_sz );
374 105 : out += data_sz;
375 105 : }
376 :
377 105 : if( alt_cnt ) {
378 0 : out = fd_txn_b_cu16_write( out, end, 1UL );
379 0 : if( FD_UNLIKELY( !out ) ) return 0;
380 :
381 0 : fd_txn_b_acct_t * alut_acct = &builder->acct[ builder->alut_i ];
382 0 : if( FD_UNLIKELY( out+FD_TXN_ACCT_ADDR_SZ>end ) ) return 0;
383 0 : fd_memcpy( out, &alut_acct->key, FD_TXN_ACCT_ADDR_SZ );
384 0 : out += FD_TXN_ACCT_ADDR_SZ;
385 :
386 0 : out = fd_txn_b_cu16_write( out, end, rw_alt_cnt );
387 0 : if( FD_UNLIKELY( !out ) ) return 0;
388 0 : if( FD_UNLIKELY( out+rw_alt_cnt>end ) ) return 0;
389 0 : for( ulong j=imm_cnt; j<imm_cnt+rw_alt_cnt; j++ ) {
390 0 : if( FD_UNLIKELY( ACCT( j )->alut_j>255U ) ) return 0;
391 0 : out[0] = (uchar)ACCT( j )->alut_j;
392 0 : out++;
393 0 : }
394 :
395 0 : out = fd_txn_b_cu16_write( out, end, ro_alt_cnt );
396 0 : if( FD_UNLIKELY( !out ) ) return 0;
397 0 : if( FD_UNLIKELY( out+ro_alt_cnt>end ) ) return 0;
398 0 : for( ulong j=imm_cnt+rw_alt_cnt; j<imm_cnt+alt_cnt; j++ ) {
399 0 : if( FD_UNLIKELY( ACCT( j )->alut_j>255U ) ) return 0;
400 0 : out[0] = (uchar)ACCT( j )->alut_j;
401 0 : out++;
402 0 : }
403 0 : }
404 :
405 105 : #undef ACCT
406 105 : return (uint)( out - out_ );
407 105 : }
408 :
409 : uint
410 : fd_txn_build_raw( fd_txn_builder_t * builder,
411 0 : uchar out[ FD_TXN_MTU_V0 ] ) {
412 0 : return fd_txn_build_core( builder, out );
413 0 : }
414 :
415 : uint
416 : fd_txn_build( fd_txn_builder_t * builder,
417 : uchar out[ FD_TXN_MTU_V0 ],
418 : fd_txn_t * restrict out_txn,
419 105 : ushort * opt_out_txn_t_sz ) {
420 105 : uint payload_sz = fd_txn_build_core( builder, out );
421 105 : if( FD_UNLIKELY( !payload_sz ) ) return 0U;
422 :
423 105 : ulong txn_t_sz = fd_txn_parse( out, payload_sz, out_txn, NULL );
424 105 : if( FD_UNLIKELY( !txn_t_sz ) ) return 0U;
425 105 : if( opt_out_txn_t_sz ) *opt_out_txn_t_sz = (ushort)txn_t_sz;
426 105 : return payload_sz;
427 105 : }
|