Line data Source code
1 : #include "fd_cost_tracker.h"
2 :
3 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_model.rs#L323-L328 */
4 : FD_FN_PURE static inline ulong
5 0 : calculate_loaded_accounts_data_size_cost( fd_exec_txn_ctx_t const * txn_ctx ) {
6 0 : ulong cost = fd_ulong_sat_sub( fd_ulong_sat_add( txn_ctx->loaded_accounts_data_size,
7 0 : FD_ACCOUNT_DATA_COST_PAGE_SIZE ),
8 0 : 1UL );
9 0 : cost /= FD_ACCOUNT_DATA_COST_PAGE_SIZE;
10 0 : return fd_ulong_sat_mul( cost, FD_VM_HEAP_COST );
11 0 : }
12 :
13 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_model.rs#L313-L321 */
14 : FD_FN_PURE static inline ulong
15 0 : get_instructions_data_cost( fd_exec_txn_ctx_t const * txn_ctx ) {
16 0 : ulong total_instr_data_sz = 0UL;
17 0 : for( ushort i=0; i<txn_ctx->txn_descriptor->instr_cnt; i++ ) {
18 0 : total_instr_data_sz += txn_ctx->txn_descriptor->instr[ i ].data_sz;
19 0 : }
20 0 : return total_instr_data_sz / FD_PACK_INV_COST_PER_INSTR_DATA_BYTE;
21 0 : }
22 :
23 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_model.rs#L152-L187 */
24 : FD_FN_PURE static inline ulong
25 0 : get_signature_cost( fd_exec_txn_ctx_t const * txn_ctx ) {
26 0 : fd_txn_t const * txn = txn_ctx->txn_descriptor;
27 0 : void const * payload = txn_ctx->_txn_raw->raw;
28 0 : fd_acct_addr_t const * accounts = fd_txn_get_acct_addrs( txn, payload );
29 :
30 : /* Compute signature counts (both normal + precompile)
31 : TODO: Factor this logic out into a shared function that can be used both here and in fd_pack_cost.h */
32 0 : ulong signature_cost = fd_ulong_sat_mul( txn->signature_cnt, FD_PACK_COST_PER_SIGNATURE );
33 0 : ulong num_secp256k1_instruction_signatures = 0UL;
34 0 : ulong num_ed25519_instruction_signatures = 0UL;
35 0 : ulong num_secp256r1_instruction_signatures = 0UL;
36 :
37 0 : for( ushort i=0; i<txn->instr_cnt; i++ ) {
38 0 : fd_txn_instr_t const * instr = &txn->instr[ i ];
39 0 : if( instr->data_sz==0UL ) continue;
40 :
41 0 : fd_acct_addr_t const * prog_id = accounts + instr->program_id;
42 0 : uchar const * instr_data = fd_txn_get_instr_data( instr, payload );
43 :
44 0 : if( fd_memeq( prog_id, fd_solana_ed25519_sig_verify_program_id.key, sizeof(fd_pubkey_t) ) ) {
45 0 : num_ed25519_instruction_signatures += (ulong)instr_data[ 0 ];
46 0 : } else if( fd_memeq( prog_id, fd_solana_keccak_secp_256k_program_id.key, sizeof(fd_pubkey_t) ) ) {
47 0 : num_secp256k1_instruction_signatures += (ulong)instr_data[ 0 ];
48 0 : } else if( fd_memeq( prog_id, fd_solana_secp256r1_program_id.key, sizeof(fd_pubkey_t) ) ) {
49 0 : num_secp256r1_instruction_signatures += (ulong)instr_data[ 0 ];
50 0 : }
51 0 : }
52 :
53 : /* No direct permalink, just factored out for readability */
54 0 : ulong secp256k1_verify_cost = fd_ulong_sat_mul( FD_PACK_COST_PER_SECP256K1_SIGNATURE, num_secp256k1_instruction_signatures );
55 :
56 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_model.rs#L155-L160 */
57 0 : ulong ed25519_verify_cost;
58 0 : if( FD_FEATURE_ACTIVE_BANK( txn_ctx->bank, ed25519_precompile_verify_strict ) ) {
59 0 : ed25519_verify_cost = fd_ulong_sat_mul( FD_PACK_COST_PER_ED25519_SIGNATURE, num_ed25519_instruction_signatures );
60 0 : } else {
61 0 : ed25519_verify_cost = fd_ulong_sat_mul( FD_PACK_COST_PER_NON_STRICT_ED25519_SIGNATURE, num_ed25519_instruction_signatures );
62 0 : }
63 :
64 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_model.rs#L162-L167 */
65 0 : ulong secp256r1_verify_cost = 0UL;
66 0 : if( FD_FEATURE_ACTIVE_BANK( txn_ctx->bank, enable_secp256r1_precompile ) ) {
67 0 : secp256r1_verify_cost = fd_ulong_sat_mul( FD_PACK_COST_PER_SECP256R1_SIGNATURE, num_secp256r1_instruction_signatures );
68 0 : }
69 :
70 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_model.rs#L169-L186 */
71 0 : return fd_ulong_sat_add( signature_cost,
72 0 : fd_ulong_sat_add( secp256k1_verify_cost,
73 0 : fd_ulong_sat_add( ed25519_verify_cost,
74 0 : secp256r1_verify_cost ) ) );
75 0 : }
76 :
77 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_model.rs#L190-L192 */
78 : FD_FN_PURE static inline ulong
79 0 : get_write_lock_cost( ulong num_write_locks ) {
80 0 : return fd_ulong_sat_mul( num_write_locks, FD_WRITE_LOCK_UNITS );
81 0 : }
82 :
83 : /* Loop through all instructions here and deserialize the instruction data to try to determine any
84 : system program allocations done.
85 :
86 : https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_model.rs#L367-L386 */
87 : static inline ulong
88 : calculate_allocated_accounts_data_size( fd_exec_txn_ctx_t const * txn_ctx,
89 0 : fd_spad_t * spad ) {
90 0 : FD_SPAD_FRAME_BEGIN( spad ) {
91 0 : fd_txn_t const * txn = txn_ctx->txn_descriptor;
92 0 : void const * payload = txn_ctx->_txn_raw->raw;
93 :
94 0 : ulong allocated_accounts_data_size = 0UL;
95 0 : for( ushort i=0; i<txn->instr_cnt; i++ ) {
96 0 : fd_txn_instr_t const * instr = &txn->instr[ i ];
97 0 : fd_acct_addr_t const * accounts = fd_txn_get_acct_addrs( txn, payload );
98 0 : fd_acct_addr_t const * prog_id = accounts + instr->program_id;
99 0 : uchar const * instr_data = fd_txn_get_instr_data( instr, payload );
100 :
101 0 : if( instr->data_sz==0UL || !fd_memeq( prog_id, &fd_solana_system_program_id, sizeof(fd_pubkey_t) ) ) continue;
102 :
103 0 : int decode_err;
104 0 : fd_system_program_instruction_t * instruction = fd_bincode_decode_spad(
105 0 : system_program_instruction, spad,
106 0 : instr_data,
107 0 : instr->data_sz,
108 0 : &decode_err );
109 0 : if( FD_UNLIKELY( decode_err ) ) continue;
110 :
111 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_model.rs#L330-L346 */
112 0 : ulong space = 0UL;
113 :
114 0 : switch( instruction->discriminant ) {
115 0 : case fd_system_program_instruction_enum_create_account: {
116 0 : space = instruction->inner.create_account.space;
117 0 : break;
118 0 : }
119 0 : case fd_system_program_instruction_enum_create_account_with_seed: {
120 0 : space = instruction->inner.create_account_with_seed.space;
121 0 : break;
122 0 : }
123 0 : case fd_system_program_instruction_enum_allocate: {
124 0 : space = instruction->inner.allocate;
125 0 : break;
126 0 : }
127 0 : case fd_system_program_instruction_enum_allocate_with_seed: {
128 0 : space = instruction->inner.allocate_with_seed.space;
129 0 : break;
130 0 : }
131 0 : }
132 :
133 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_model.rs#L373-L380 */
134 0 : if( FD_UNLIKELY( space>FD_ACC_SZ_MAX ) ) return 0UL;
135 :
136 0 : allocated_accounts_data_size = fd_ulong_sat_add( allocated_accounts_data_size, space );
137 0 : }
138 :
139 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_model.rs#L396-L397 */
140 0 : return fd_ulong_min( 2UL*FD_ACC_SZ_MAX, allocated_accounts_data_size );
141 0 : } FD_SPAD_FRAME_END;
142 0 : }
143 :
144 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_model.rs#L123-L149 */
145 : static inline fd_transaction_cost_t
146 : calculate_non_vote_transaction_cost( fd_exec_txn_ctx_t const * txn_ctx,
147 : ulong loaded_accounts_data_size_cost,
148 : ulong data_bytes_cost,
149 0 : fd_spad_t * spad ) {
150 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_model.rs#L132 */
151 0 : ulong signature_cost = get_signature_cost( txn_ctx );
152 :
153 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_model.rs#L133 */
154 0 : ulong write_lock_cost = get_write_lock_cost( fd_txn_account_cnt( txn_ctx->txn_descriptor, FD_TXN_ACCT_CAT_WRITABLE ) );
155 :
156 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_model.rs#L135-L136 */
157 0 : ulong allocated_accounts_data_size = calculate_allocated_accounts_data_size( txn_ctx, spad );
158 :
159 0 : return (fd_transaction_cost_t){ .discriminant = fd_transaction_cost_enum_transaction,
160 0 : .inner = {
161 0 : .transaction = {
162 0 : .signature_cost = signature_cost,
163 0 : .write_lock_cost = write_lock_cost,
164 0 : .data_bytes_cost = data_bytes_cost,
165 0 : .programs_execution_cost = fd_ulong_sat_sub( txn_ctx->compute_unit_limit,
166 0 : txn_ctx->compute_meter ),
167 0 : .loaded_accounts_data_size_cost = loaded_accounts_data_size_cost,
168 0 : .allocated_accounts_data_size = allocated_accounts_data_size,
169 0 : }
170 0 : }
171 0 : };
172 0 : }
173 :
174 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/transaction_cost.rs#L26-L42 */
175 : FD_FN_PURE static inline ulong
176 0 : transaction_cost_sum( fd_transaction_cost_t const * self ) {
177 0 : switch( self->discriminant ) {
178 0 : case fd_transaction_cost_enum_simple_vote: {
179 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/transaction_cost.rs#L38 */
180 0 : return FD_PACK_SIMPLE_VOTE_COST;
181 0 : }
182 0 : case fd_transaction_cost_enum_transaction: {
183 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/transaction_cost.rs#L164-L171 */
184 0 : fd_usage_cost_details_t const * usage_cost = &self->inner.transaction;
185 0 : ulong cost = 0UL;
186 :
187 0 : cost = fd_ulong_sat_add( cost, usage_cost->signature_cost );
188 0 : cost = fd_ulong_sat_add( cost, usage_cost->write_lock_cost );
189 0 : cost = fd_ulong_sat_add( cost, usage_cost->data_bytes_cost );
190 0 : cost = fd_ulong_sat_add( cost, usage_cost->programs_execution_cost );
191 0 : cost = fd_ulong_sat_add( cost, usage_cost->loaded_accounts_data_size_cost );
192 :
193 0 : return cost;
194 0 : }
195 0 : default: {
196 0 : __builtin_unreachable();
197 0 : }
198 0 : }
199 0 : }
200 :
201 : FD_FN_PURE static inline ulong
202 0 : get_allocated_accounts_data_size( fd_transaction_cost_t const * self ) {
203 0 : switch( self->discriminant ) {
204 0 : case fd_transaction_cost_enum_simple_vote: {
205 0 : return 0UL;
206 0 : }
207 0 : case fd_transaction_cost_enum_transaction: {
208 0 : return self->inner.transaction.allocated_accounts_data_size;
209 0 : }
210 0 : default: {
211 0 : __builtin_unreachable();
212 0 : }
213 0 : }
214 0 : }
215 :
216 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_tracker.rs#L277-L322 */
217 : static inline int
218 : would_fit( fd_cost_tracker_t const * self,
219 : fd_exec_txn_ctx_t const * txn_ctx,
220 0 : fd_transaction_cost_t const * tx_cost ) {
221 :
222 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_tracker.rs#L281 */
223 0 : ulong cost = transaction_cost_sum( tx_cost );
224 :
225 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_tracker.rs#L283-L288 */
226 0 : if( fd_transaction_cost_is_simple_vote( tx_cost ) ) {
227 0 : if( FD_UNLIKELY( fd_ulong_sat_add( self->vote_cost, cost )>self->vote_cost_limit ) ) {
228 0 : return FD_COST_TRACKER_ERROR_WOULD_EXCEED_VOTE_MAX_LIMIT;
229 0 : }
230 0 : }
231 :
232 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_tracker.rs#L290-L293 */
233 0 : if( FD_UNLIKELY( fd_ulong_sat_add( self->block_cost, cost )>self->block_cost_limit ) ) {
234 0 : return FD_COST_TRACKER_ERROR_WOULD_EXCEED_BLOCK_MAX_LIMIT;
235 0 : }
236 :
237 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_tracker.rs#L295-L298 */
238 0 : if( FD_UNLIKELY( cost>self->account_cost_limit ) ) {
239 0 : return FD_COST_TRACKER_ERROR_WOULD_EXCEED_ACCOUNT_MAX_LIMIT;
240 0 : }
241 :
242 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_tracker.rs#L300-L301 */
243 0 : ulong allocated_accounts_data_size = fd_ulong_sat_add( self->allocated_accounts_data_size,
244 0 : get_allocated_accounts_data_size( tx_cost ) );
245 :
246 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_tracker.rs#L303-L304 */
247 0 : if( FD_UNLIKELY( allocated_accounts_data_size>FD_MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA ) ) {
248 0 : return FD_COST_TRACKER_ERROR_WOULD_EXCEED_ACCOUNT_DATA_BLOCK_LIMIT;
249 0 : }
250 :
251 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_tracker.rs#L308-L319 */
252 0 : fd_account_costs_pair_t_mapnode_t * pool = self->cost_by_writable_accounts.account_costs_pool;
253 0 : fd_account_costs_pair_t_mapnode_t * root = self->cost_by_writable_accounts.account_costs_root;
254 :
255 0 : for( ulong i=0UL; i<txn_ctx->accounts_cnt; i++ ) {
256 0 : if( !fd_exec_txn_ctx_account_is_writable_idx( txn_ctx, (ushort)i ) ) continue;
257 :
258 0 : fd_pubkey_t const * writable_acc = &txn_ctx->account_keys[i];
259 0 : fd_account_costs_pair_t_mapnode_t elem;
260 0 : elem.elem.key = *writable_acc;
261 :
262 0 : fd_account_costs_pair_t_mapnode_t * chained_cost = fd_account_costs_pair_t_map_find( pool, root, &elem );
263 0 : if( chained_cost ) {
264 0 : if( FD_UNLIKELY( fd_ulong_sat_add( chained_cost->elem.cost, cost )>self->account_cost_limit ) ) {
265 0 : return FD_COST_TRACKER_ERROR_WOULD_EXCEED_ACCOUNT_MAX_LIMIT;
266 0 : }
267 0 : }
268 0 : }
269 :
270 0 : return FD_COST_TRACKER_SUCCESS;
271 0 : }
272 :
273 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_tracker.rs#L352-L372 */
274 : static inline void
275 : add_transaction_execution_cost( fd_cost_tracker_t * self,
276 : fd_exec_txn_ctx_t const * txn_ctx,
277 : fd_transaction_cost_t const * tx_cost,
278 0 : ulong adjustment ) {
279 :
280 0 : fd_account_costs_pair_t_mapnode_t * pool = self->cost_by_writable_accounts.account_costs_pool;
281 0 : fd_account_costs_pair_t_mapnode_t ** root = &self->cost_by_writable_accounts.account_costs_root;
282 :
283 0 : for( ulong i=0UL; i<txn_ctx->accounts_cnt; i++ ) {
284 0 : if( !fd_exec_txn_ctx_account_is_writable_idx( txn_ctx, (ushort)i ) ) continue;
285 :
286 0 : fd_pubkey_t const * writable_acc = &txn_ctx->account_keys[i];
287 0 : fd_account_costs_pair_t_mapnode_t elem;
288 0 : elem.elem.key = *writable_acc;
289 :
290 0 : fd_account_costs_pair_t_mapnode_t * account_cost = fd_account_costs_pair_t_map_find( pool, *root, &elem );
291 0 : if( account_cost==NULL ) {
292 0 : account_cost = fd_account_costs_pair_t_map_acquire( pool );
293 0 : account_cost->elem.key = *writable_acc;
294 0 : account_cost->elem.cost = adjustment;
295 0 : fd_account_costs_pair_t_map_insert( pool, root, account_cost );
296 0 : } else {
297 0 : account_cost->elem.cost = fd_ulong_sat_add( account_cost->elem.cost, adjustment );
298 0 : }
299 0 : }
300 :
301 0 : self->block_cost = fd_ulong_sat_add( self->block_cost, adjustment );
302 0 : if( fd_transaction_cost_is_simple_vote( tx_cost ) ) {
303 0 : self->vote_cost = fd_ulong_sat_add( self->vote_cost, adjustment );
304 0 : }
305 0 : }
306 :
307 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_tracker.rs#L325-L335 */
308 : static inline void
309 : add_transaction_cost( fd_cost_tracker_t * self,
310 : fd_exec_txn_ctx_t const * txn_ctx,
311 0 : fd_transaction_cost_t const * tx_cost ) {
312 : /* Note: We purposely omit signature counts updates since they're not relevant to cost calculations right now. */
313 0 : self->allocated_accounts_data_size += get_allocated_accounts_data_size( tx_cost );
314 0 : self->transaction_count++;
315 0 : add_transaction_execution_cost( self, txn_ctx, tx_cost, transaction_cost_sum( tx_cost ) );
316 0 : }
317 :
318 : /** PUBLIC FUNCTIONS ***/
319 :
320 : void
321 : fd_cost_tracker_init( fd_cost_tracker_t * self,
322 : fd_exec_slot_ctx_t const * slot_ctx,
323 0 : fd_spad_t * spad ) {
324 : // Set limits appropriately
325 0 : self->account_cost_limit = FD_MAX_WRITABLE_ACCOUNT_UNITS;
326 0 : self->block_cost_limit = FD_FEATURE_ACTIVE_BANK( slot_ctx->bank, raise_block_limits_to_50m ) ? FD_MAX_BLOCK_UNITS_SIMD_0207 : FD_MAX_BLOCK_UNITS;
327 0 : self->vote_cost_limit = FD_MAX_VOTE_UNITS;
328 :
329 : /* Init cost tracker map
330 : TODO: The maximum number of accounts within a block needs to be bounded out properly. It's currently
331 : hardcoded here at 4096*1024 accounts. */
332 0 : self->cost_by_writable_accounts.account_costs_root = NULL;
333 0 : uchar * pool_mem = fd_spad_alloc( spad, fd_account_costs_pair_t_map_align(), fd_account_costs_pair_t_map_footprint( FD_WRITABLE_ACCOUNTS_PER_BLOCK * 1024UL ) );
334 0 : self->cost_by_writable_accounts.account_costs_pool = fd_account_costs_pair_t_map_join( fd_account_costs_pair_t_map_new( pool_mem, FD_WRITABLE_ACCOUNTS_PER_BLOCK * 1024UL ) );
335 0 : if( FD_UNLIKELY( !self->cost_by_writable_accounts.account_costs_pool ) ) {
336 0 : FD_LOG_ERR(( "failed to allocate memory for cost tracker accounts pool" ));
337 0 : }
338 :
339 : // Reset aggregated stats for new block
340 0 : self->block_cost = 0UL;
341 0 : self->vote_cost = 0UL;
342 0 : self->transaction_count = 0UL;
343 0 : self->allocated_accounts_data_size = 0UL;
344 0 : self->transaction_signature_count = 0UL;
345 0 : self->secp256k1_instruction_signature_count = 0UL;
346 0 : self->ed25519_instruction_signature_count = 0UL;
347 0 : self->secp256r1_instruction_signature_count = 0UL;
348 0 : }
349 :
350 : fd_transaction_cost_t
351 : fd_calculate_cost_for_executed_transaction( fd_exec_txn_ctx_t const * txn_ctx,
352 0 : fd_spad_t * spad ) {
353 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/cost-model/src/cost_model.rs#L83-L85 */
354 0 : if( fd_txn_is_simple_vote_transaction( txn_ctx->txn_descriptor, txn_ctx->_txn_raw->raw ) ) {
355 0 : return (fd_transaction_cost_t){ .discriminant = fd_transaction_cost_enum_simple_vote };
356 0 : }
357 :
358 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_model.rs#L78-L81 */
359 0 : ulong loaded_accounts_data_size_cost = calculate_loaded_accounts_data_size_cost( txn_ctx );
360 :
361 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_model.rs#L82-L83 */
362 0 : ulong instructions_data_cost = get_instructions_data_cost( txn_ctx );
363 :
364 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_model.rs#L85-L93 */
365 0 : return calculate_non_vote_transaction_cost( txn_ctx, loaded_accounts_data_size_cost, instructions_data_cost, spad );
366 0 : }
367 :
368 : int
369 : fd_cost_tracker_try_add( fd_cost_tracker_t * self,
370 : fd_exec_txn_ctx_t const * txn_ctx,
371 0 : fd_transaction_cost_t const * tx_cost ) {
372 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_tracker.rs#L167 */
373 0 : int err = would_fit( self, txn_ctx, tx_cost );
374 0 : if( FD_UNLIKELY( err ) ) return err;
375 :
376 : /* We don't need `updated_costliest_account_cost` since it seems to be for a different use case
377 : other than validating block cost limits.
378 : https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_tracker.rs#L168 */
379 0 : add_transaction_cost( self, txn_ctx, tx_cost );
380 0 : return FD_COST_TRACKER_SUCCESS;
381 0 : }
|