From c4f8a000d76e6f01835b87b686ceb1b0c6b4bc9b Mon Sep 17 00:00:00 2001 From: jherrera-jump Date: Thu, 6 Feb 2025 19:18:05 +0000 Subject: [PATCH] [WIP] fix pack cu calculation remove keccak, fix trailing whitespace Addtl fixes --- agave | 2 +- src/app/fdctl/external_functions.c | 2 +- src/app/fdctl/run/tiles/fd_bank.c | 49 +++---- src/ballet/pack/fd_compute_budget_program.h | 30 +++- src/ballet/pack/fd_microblock.h | 3 +- src/ballet/pack/fd_pack.c | 21 +-- src/ballet/pack/fd_pack_cost.h | 135 +++++++++++------- src/ballet/pack/test_compute_budget_program.c | 38 ++--- src/ballet/pack/test_pack.c | 3 +- .../runtime/program/fd_vote_program.c | 4 +- .../runtime/program/fd_vote_program.h | 4 + src/flamenco/runtime/tests/fd_pack_test.c | 7 +- 12 files changed, 178 insertions(+), 120 deletions(-) diff --git a/agave b/agave index e3ed87ea55..bd6fa8e48f 160000 --- a/agave +++ b/agave @@ -1 +1 @@ -Subproject commit e3ed87ea55d78fb66062b5ab1f9aafa89dc203b1 +Subproject commit bd6fa8e48ffaeeafc7d1dbbfdde9d8d325700e17 diff --git a/src/app/fdctl/external_functions.c b/src/app/fdctl/external_functions.c index 21cca6ea4f..fb59f36e9c 100644 --- a/src/app/fdctl/external_functions.c +++ b/src/app/fdctl/external_functions.c @@ -5,8 +5,8 @@ extern void fd_ext_validator_main( const char ** args FD_PARAM_UNUSED ) {} extern void fd_ext_genesis_main( const char ** args FD_PARAM_UNUSED ) {} extern void * fd_ext_bank_pre_balance_info( void const * bank FD_PARAM_UNUSED, void * txns FD_PARAM_UNUSED, ulong txn_cnt FD_PARAM_UNUSED ) { return NULL; } -extern void * fd_ext_bank_load_and_execute_txns( void const * bank FD_PARAM_UNUSED, void * txns FD_PARAM_UNUSED, ulong txn_cnt FD_PARAM_UNUSED, int * out_load_results FD_PARAM_UNUSED, int * out_executing_results FD_PARAM_UNUSED, int * out_executed_results FD_PARAM_UNUSED, uint * out_consumed_cus FD_PARAM_UNUSED ) { return NULL; } extern int fd_ext_bank_execute_and_commit_bundle( void const * bank FD_PARAM_UNUSED, void * txns FD_PARAM_UNUSED, ulong txn_cnt FD_PARAM_UNUSED, uint * out_consumed_cus FD_PARAM_UNUSED ) { return 0; } +extern void * fd_ext_bank_load_and_execute_txns( void const * bank FD_PARAM_UNUSED, void * txns FD_PARAM_UNUSED, ulong txn_cnt FD_PARAM_UNUSED, int * out_load_results FD_PARAM_UNUSED, int * out_executing_results FD_PARAM_UNUSED, int * out_executed_results FD_PARAM_UNUSED, uint * out_consumed_exec_cus FD_PARAM_UNUSED, uint * out_consumed_acct_data_cus FD_PARAM_UNUSED ) { return NULL; } extern void fd_ext_bank_acquire( void const * bank FD_PARAM_UNUSED ) {} extern void fd_ext_bank_release( void const * bank FD_PARAM_UNUSED ) {} extern void fd_ext_bank_release_thunks( void * load_and_execute_output FD_PARAM_UNUSED ) {} diff --git a/src/app/fdctl/run/tiles/fd_bank.c b/src/app/fdctl/run/tiles/fd_bank.c index 2d078bab41..9617da5ba1 100644 --- a/src/app/fdctl/run/tiles/fd_bank.c +++ b/src/app/fdctl/run/tiles/fd_bank.c @@ -95,8 +95,8 @@ before_frag( fd_bank_ctx_t * ctx, } extern void * fd_ext_bank_pre_balance_info( void const * bank, void * txns, ulong txn_cnt ); -extern void * fd_ext_bank_load_and_execute_txns( void const * bank, void * txns, ulong txn_cnt, int * out_processing_results, int * out_transaction_err, uint * out_consumed_cus ); extern int fd_ext_bank_execute_and_commit_bundle( void const * bank, void * txns, ulong txn_cnt, uint * out_consumed_cus ); +extern void * fd_ext_bank_load_and_execute_txns( void const * bank, void * txns, ulong txn_cnt, int * out_processing_results, int * out_transaction_err, uint * out_consumed_exec_cus, uint * out_consumed_acct_data_cus ); extern void fd_ext_bank_commit_txns( void const * bank, void const * txns, ulong txn_cnt , void * load_and_execute_output, void * pre_balance_info ); extern void fd_ext_bank_release_thunks( void * load_and_execute_output ); extern void fd_ext_bank_release_pre_balance_info( void * pre_balance_info ); @@ -186,9 +186,10 @@ handle_microblock( fd_bank_ctx_t * ctx, /* Just because a transaction was executed doesn't mean it succeeded, but all executed transactions get committed. */ - int processing_results[ MAX_TXN_PER_MICROBLOCK ] = { 0 }; - int transaction_err [ MAX_TXN_PER_MICROBLOCK ] = { 0 }; - uint consumed_cus [ MAX_TXN_PER_MICROBLOCK ] = { 0U }; + int processing_results [ MAX_TXN_PER_MICROBLOCK ] = { 0 }; + int transaction_err [ MAX_TXN_PER_MICROBLOCK ] = { 0 }; + uint consumed_exec_cus [ MAX_TXN_PER_MICROBLOCK ] = { 0U }; + uint consumed_acct_data_cus[ MAX_TXN_PER_MICROBLOCK ] = { 0U }; void * pre_balance_info = fd_ext_bank_pre_balance_info( ctx->_bank, ctx->txn_abi_mem, sanitized_txn_cnt ); @@ -197,17 +198,19 @@ handle_microblock( fd_bank_ctx_t * ctx, sanitized_txn_cnt, processing_results, transaction_err, - consumed_cus ); + consumed_exec_cus, + consumed_acct_data_cus ); ulong sanitized_idx = 0UL; for( ulong i=0UL; ipack_cu.requested_execution_cus; - uint non_execution_cus = txn->pack_cu.non_execution_cus; - /* Assume failure, set below if success. If it doesn't land in the + uint requested_execution_cus = txn->pack_cu.requested_execution_cus; + uint requested_acct_data_cus = txn->pack_cu.requested_acct_data_cus; + uint non_execution_cus = txn->pack_cu.non_execution_cus; + /* Assume failure, set below if success. If it doesn't land cin the block, rebate the non-execution CUs too. */ - txn->bank_cu.rebated_cus = requested_cus + non_execution_cus; + txn->bank_cu.rebated_cus = requested_acct_data_cus + requested_execution_cus + non_execution_cus; txn->flags &= ~FD_TXN_P_FLAGS_EXECUTE_SUCCESS; if( FD_UNLIKELY( !(txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS) ) ) continue; @@ -234,23 +237,17 @@ handle_microblock( fd_bank_ctx_t * ctx, if( transaction_err[ sanitized_idx-1UL ] ) ctx->metrics.exec_failed++; else ctx->metrics.success++; - uint executed_cus = consumed_cus[ sanitized_idx-1UL ]; - txn->bank_cu.actual_consumed_cus = non_execution_cus + executed_cus; - if( FD_UNLIKELY( executed_cus>requested_cus ) ) { - /* There's basically a bug in the Agave codebase right now - regarding the cost model for some transactions. Some built-in - instructions like creating an address lookup table consume more - CUs than the cost model allocates for them, which is only - allowed because the runtime computes requested CUs differently - from the cost model. Rather than implement a broken system, - we'll just permit the risk of slightly overpacking blocks by - ignoring these transactions when it comes to rebating. */ - FD_LOG_INFO(( "Transaction executed %u CUs but only requested %u CUs", executed_cus, requested_cus )); - FD_MCNT_INC( BANK, COST_MODEL_UNDERCOUNT, 1UL ); - txn->bank_cu.rebated_cus = 0U; - continue; - } - txn->bank_cu.rebated_cus = requested_cus - executed_cus; + uint actual_execution_cus = consumed_exec_cus[ sanitized_idx-1UL ]; + uint actual_acct_data_cus = consumed_acct_data_cus[ sanitized_idx-1UL ]; + txn->bank_cu.actual_consumed_cus = non_execution_cus + actual_execution_cus + actual_acct_data_cus; + + /* The VM will stop executing and fail an instruction immediately if + it exceeds its requested CUs. A transaction which requests less + account data than it actually consumes will fail in the account + loading stage. */ + FD_TEST( actual_execution_cus <= requested_execution_cus ); + FD_TEST( actual_acct_data_cus <= requested_acct_data_cus ); + txn->bank_cu.rebated_cus = ( requested_acct_data_cus - actual_acct_data_cus ) + ( requested_execution_cus - actual_execution_cus ); } /* Commit must succeed so no failure path. This function takes diff --git a/src/ballet/pack/fd_compute_budget_program.h b/src/ballet/pack/fd_compute_budget_program.h index be36d7663f..51b9ec9563 100644 --- a/src/ballet/pack/fd_compute_budget_program.h +++ b/src/ballet/pack/fd_compute_budget_program.h @@ -36,13 +36,15 @@ static const uchar FD_COMPUTE_BUDGET_PROGRAM_ID[FD_TXN_ACCT_ADDR_SZ] = { /* Any requests for larger heap frames must be a multiple of 1k or the transaction is malformed. */ -#define FD_COMPUTE_BUDGET_HEAP_FRAME_GRANULARITY (1024UL) +#define FD_COMPUTE_BUDGET_HEAP_FRAME_GRANULARITY ( 1024UL) /* SetComputeUnitPrice specifies the price in "micro-lamports," which is 10^(-6) lamports, so 10^(-15) SOL. */ -#define FD_COMPUTE_BUDGET_MICRO_LAMPORTS_PER_LAMPORT (1000000UL) +#define FD_COMPUTE_BUDGET_MICRO_LAMPORTS_PER_LAMPORT ( 1000000UL) -#define FD_COMPUTE_BUDGET_DEFAULT_INSTR_CU_LIMIT ( 200000UL) -#define FD_COMPUTE_BUDGET_MAX_CU_LIMIT (1400000UL) +#define FD_COMPUTE_BUDGET_DEFAULT_INSTR_CU_LIMIT ( 200000UL) +#define FD_COMPUTE_BUDGET_MAX_CU_LIMIT ( 1400000UL) +#define FD_COMPUTE_BUDGET_HEAP_COST ( 8UL) +#define FD_COMPUTE_BUDGET_ACCOUNT_DATA_COST_PAGE_SIZE (32UL * 1024UL) /* Max loaded data size is 64 MiB */ #define FD_COMPUTE_BUDGET_MAX_LOADED_DATA_SZ (64UL*1024UL*1024UL) @@ -154,7 +156,8 @@ static inline void fd_compute_budget_program_finalize( fd_compute_budget_program_state_t const * state, ulong instr_cnt, ulong * out_rewards, - uint * out_compute ) { + uint * out_compute, + ulong * out_loaded_account_data_cost ) { ulong cu_limit = 0UL; if( FD_LIKELY( (state->flags & FD_COMPUTE_BUDGET_PROGRAM_FLAG_SET_CU)==0U ) ) { /* Use default compute limit */ @@ -165,6 +168,23 @@ fd_compute_budget_program_finalize( fd_compute_budget_program_state_t const * st *out_compute = (uint)cu_limit; + ulong loaded_accounts_data_size = 0UL; + if( FD_LIKELY( (state->flags & FD_COMPUTE_BUDGET_PROGRAM_FLAG_SET_LOADED_DATA_SZ)==0U ) ) { + /* Use default loaded account data size */ + loaded_accounts_data_size = FD_COMPUTE_BUDGET_MAX_LOADED_DATA_SZ; + } else loaded_accounts_data_size = state->loaded_acct_data_sz; + + /* https://github.com/firedancer-io/agave/blob/927c6d30ed5e1baea30c06ebf2aa0c9bae0e1dd1/sdk/fee-structure/src/lib.rs#L122-L129 + + loaded_accounts_data_size <= FD_COMPUTE_BUDGET_MAX_LOADED_DATA_SZ + means no overflow */ + + ulong loaded_accounts_data_cost = FD_COMPUTE_BUDGET_ACCOUNT_DATA_COST_PAGE_SIZE - 1UL; + loaded_accounts_data_cost = loaded_accounts_data_cost + loaded_accounts_data_size; + loaded_accounts_data_cost = loaded_accounts_data_cost / FD_COMPUTE_BUDGET_ACCOUNT_DATA_COST_PAGE_SIZE; + loaded_accounts_data_cost = loaded_accounts_data_cost * FD_COMPUTE_BUDGET_HEAP_COST; + *out_loaded_account_data_cost = loaded_accounts_data_cost; + ulong total_fee = 0UL; /* We need to compute max(ceil((cu_limit * micro_lamports_per_cu)/10^6), diff --git a/src/ballet/pack/fd_microblock.h b/src/ballet/pack/fd_microblock.h index 320fc69a2b..517abaf865 100644 --- a/src/ballet/pack/fd_microblock.h +++ b/src/ballet/pack/fd_microblock.h @@ -55,10 +55,11 @@ struct __attribute__((aligned(64))) fd_txn_p { struct { uint non_execution_cus; uint requested_execution_cus; + uint requested_acct_data_cus; } pack_cu; /* Populated by pack. Bank reads these to populate the other struct of the union. */ struct { uint rebated_cus; /* requested_execution_cus-real execution CUs. Pack reads this for CU rebating. */ - uint actual_consumed_cus; /* non_execution_cus+real execution CUs. PoH reads this for block CU counting. */ + uint actual_consumed_cus; /* non_execution_cus+real execution CUs+real account data cus. PoH reads this for block CU counting. */ } bank_cu; /* Populated by bank. */ ulong blockhash_slot; /* Slot provided by resolv tile when txn arrives at the pack tile. Used when txn is in extra storage in pack. */ }; diff --git a/src/ballet/pack/fd_pack.c b/src/ballet/pack/fd_pack.c index 2254166d4f..cd83c3e622 100644 --- a/src/ballet/pack/fd_pack.c +++ b/src/ballet/pack/fd_pack.c @@ -871,12 +871,13 @@ fd_pack_estimate_rewards_and_compute( fd_txn_e_t * txne, fd_txn_t * txn = TXN(txne->txnp); ulong sig_rewards = FD_PACK_FEE_PER_SIGNATURE * txn->signature_cnt; /* Easily in [5000, 635000] */ - ulong execution_cus; - ulong adtl_rewards; + ulong requested_execution_cus; + ulong priority_rewards; ulong precompile_sigs; - ulong cost = fd_pack_compute_cost( txn, txne->txnp->payload, &txne->txnp->flags, &execution_cus, &adtl_rewards, &precompile_sigs ); + ulong requested_loaded_accounts_data_cost; + ulong cost_estimate = fd_pack_compute_cost( txn, txne->txnp->payload, &txne->txnp->flags, &requested_execution_cus, &priority_rewards, &precompile_sigs, &requested_loaded_accounts_data_cost ); - if( FD_UNLIKELY( !cost ) ) return 0; + if( FD_UNLIKELY( !cost_estimate ) ) return 0; /* precompile_sigs <= 16320, so after the addition, sig_rewards < 83,000,000 */ @@ -888,10 +889,11 @@ fd_pack_estimate_rewards_and_compute( fd_txn_e_t * txne, fd_acct_addr_t const * acct_addr = fd_txn_get_acct_addrs( txn, txnp->payload ) + (ulong)prog_id_idx; } */ - out->rewards = (adtl_rewards < (UINT_MAX - sig_rewards)) ? (uint)(sig_rewards + adtl_rewards) : UINT_MAX; - out->compute_est = (uint)cost; - out->txn->pack_cu.requested_execution_cus = (uint)execution_cus; - out->txn->pack_cu.non_execution_cus = (uint)(cost - execution_cus); + out->rewards = (priority_rewards < (UINT_MAX - sig_rewards)) ? (uint)(sig_rewards + priority_rewards) : UINT_MAX; + out->compute_est = (uint)cost_estimate; + out->txn->pack_cu.requested_execution_cus = (uint)(requested_execution_cus); + out->txn->pack_cu.requested_acct_data_cus = (uint)(requested_loaded_accounts_data_cost); + out->txn->pack_cu.non_execution_cus = (uint)(cost_estimate - requested_execution_cus - requested_loaded_accounts_data_cost); return fd_int_if( txne->txnp->flags & FD_TXN_P_FLAGS_IS_SIMPLE_VOTE, 1, 2 ); } @@ -1830,6 +1832,7 @@ fd_pack_schedule_impl( fd_pack_t * pack, fd_memcpy( TXN(out), txn, fd_txn_footprint( txn->instr_cnt, txn->addr_table_lookup_cnt ) ); out->payload_sz = cur->txn->payload_sz; out->pack_cu.requested_execution_cus = cur->txn->pack_cu.requested_execution_cus; + out->pack_cu.requested_acct_data_cus = cur->txn->pack_cu.requested_acct_data_cus; out->pack_cu.non_execution_cus = cur->txn->pack_cu.non_execution_cus; out->flags = cur->txn->flags; } @@ -2347,7 +2350,7 @@ fd_pack_schedule_next_microblock( fd_pack_t * pack, total_cus = fd_ulong_min( total_cus, pack->lim->max_cost_per_block - pack->cumulative_block_cost ); ulong vote_cus = fd_ulong_min( (ulong)((float)total_cus * vote_fraction), pack->lim->max_vote_cost_per_block - pack->cumulative_vote_cost ); - ulong vote_reserved_txns = fd_ulong_min( vote_cus/FD_PACK_TYPICAL_VOTE_COST, + ulong vote_reserved_txns = fd_ulong_min( vote_cus/FD_PACK_SIMPLE_VOTE_COST, (ulong)((float)pack->lim->max_txn_per_microblock * vote_fraction) ); diff --git a/src/ballet/pack/fd_pack_cost.h b/src/ballet/pack/fd_pack_cost.h index 52e4d57ebe..c8a4afaf90 100644 --- a/src/ballet/pack/fd_pack_cost.h +++ b/src/ballet/pack/fd_pack_cost.h @@ -3,17 +3,22 @@ #include "../fd_ballet_base.h" #include "fd_compute_budget_program.h" #include "../../flamenco/runtime/fd_system_ids_pp.h" +#include "../../flamenco/runtime/program/fd_vote_program.h" + +#define DETAILED_LOGGING 1 /* The functions in this header implement the transaction cost model that is soon to be part of consensus. The cost model consists of several components: - * per-signature cost + * per-signature cost: This includes the costs of * per-write-lock cost * instruction data length cost * built-in execution cost * BPF execution cost - These are all summed to determine the total cost. Additionally, this - header provides a method for determining if a transaction is a simple + * loaded accounts data cost + These are all summed to determine the total cost.*/ + +/* This header provides a method for determining if a transaction is a simple vote transaction, in which case its costs are used slightly differently. */ @@ -50,20 +55,19 @@ typedef struct fd_pack_builtin_prog_cost fd_pack_builtin_prog_cost_t; #define MAP_PERFECT_HASH_R( ptr ) PERFECT_HASH( fd_uint_load_4( (uchar const *)ptr->b + 8UL ) ) -#define VOTE_PROG_COST 3000UL +/* https://github.com/anza-xyz/agave/blob/134be7c14066ea00c9791187d6bbc4795dd92f0e/builtins-default-costs/src/lib.rs#L113-L193 */ -#define MAP_PERFECT_0 ( STAKE_PROG_ID ), .cost_per_instr= 3000UL -#define MAP_PERFECT_1 ( CONFIG_PROG_ID ), .cost_per_instr= 3000UL -#define MAP_PERFECT_2 ( VOTE_PROG_ID ), .cost_per_instr=VOTE_PROG_COST -#define MAP_PERFECT_3 ( SYS_PROG_ID ), .cost_per_instr= 3000UL -#define MAP_PERFECT_4 ( COMPUTE_BUDGET_PROG_ID ), .cost_per_instr= 3000UL -#define MAP_PERFECT_5 ( BPF_UPGRADEABLE_PROG_ID ), .cost_per_instr= 3000UL -#define MAP_PERFECT_6 ( BPF_LOADER_1_PROG_ID ), .cost_per_instr= 3000UL -#define MAP_PERFECT_7 ( BPF_LOADER_2_PROG_ID ), .cost_per_instr= 3000UL -#define MAP_PERFECT_8 ( LOADER_V4_PROG_ID ), .cost_per_instr= 3000UL -#define MAP_PERFECT_9 ( KECCAK_SECP_PROG_ID ), .cost_per_instr= 3000UL -#define MAP_PERFECT_10 ( ED25519_SV_PROG_ID ), .cost_per_instr= 3000UL -#define MAP_PERFECT_11 ( SECP256R1_PROG_ID ), .cost_per_instr= 3000UL +#define MAP_PERFECT_0 ( STAKE_PROG_ID ), .cost_per_instr= 3000UL /* Migration to BPF currently blocked */ +#define MAP_PERFECT_1 ( VOTE_PROG_ID ), .cost_per_instr= 3000UL +#define MAP_PERFECT_2 ( SYS_PROG_ID ), .cost_per_instr= 3000UL +#define MAP_PERFECT_3 ( COMPUTE_BUDGET_PROG_ID ), .cost_per_instr= 3000UL +#define MAP_PERFECT_4 ( BPF_UPGRADEABLE_PROG_ID ), .cost_per_instr= 3000UL +#define MAP_PERFECT_5 ( BPF_LOADER_1_PROG_ID ), .cost_per_instr= 3000UL +#define MAP_PERFECT_6 ( BPF_LOADER_2_PROG_ID ), .cost_per_instr= 3000UL +#define MAP_PERFECT_7 ( LOADER_V4_PROG_ID ), .cost_per_instr= 3000UL +#define MAP_PERFECT_8 ( KECCAK_SECP_PROG_ID ), .cost_per_instr= 3000UL +#define MAP_PERFECT_9 ( ED25519_SV_PROG_ID ), .cost_per_instr= 3000UL +#define MAP_PERFECT_10 ( SECP256R1_PROG_ID ), .cost_per_instr= 3000UL #include "../../util/tmpl/fd_map_perfect.c" @@ -72,9 +76,11 @@ typedef struct fd_pack_builtin_prog_cost fd_pack_builtin_prog_cost_t; a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31) \ PERFECT_HASH( ((uint)a08 | ((uint)a09<<8) | ((uint)a10<<16) | ((uint)a11<<24)) ) -#define FD_PACK_COST_PER_SIGNATURE (720UL) -#define FD_PACK_COST_PER_WRITABLE_ACCT (300UL) -#define FD_PACK_INV_COST_PER_INSTR_DATA_BYTE ( 4UL) +#define FD_PACK_COST_PER_SIGNATURE ( 720UL) +#define FD_PACK_COST_PER_ED25519_SIGNATURE ( 2400UL) +#define FD_PACK_COST_PER_SECP256K1_SIGNATURE ( 6690UL) +#define FD_PACK_COST_PER_WRITABLE_ACCT ( 300UL) +#define FD_PACK_INV_COST_PER_INSTR_DATA_BYTE ( 4UL) /* The computation here is similar to the computation for the max fd_txn_t size. There are various things a transaction can include @@ -132,18 +138,18 @@ FD_STATIC_ASSERT( FD_PACK_MAX_TXN_COST < (ulong)UINT_MAX, fd_pack_max_cost ); /* Every transaction has at least a fee payer, a writable signer. */ #define FD_PACK_MIN_TXN_COST (FD_PACK_COST_PER_SIGNATURE+FD_PACK_COST_PER_WRITABLE_ACCT) -/* A typical vote transaction has the authorized voter (writable - signer), the vote account (writable non-signer), and the vote program - (readonly). Then it has one instruction, a built-in to the vote - program, which is typically 116 bytes long, but occasionally a little - less than that. The mean over several million slots of vote - transactions (10B votes) is 115.990 bytes. */ -static const ulong FD_PACK_TYPICAL_VOTE_COST = ( FD_PACK_COST_PER_SIGNATURE + - 2UL*FD_PACK_COST_PER_WRITABLE_ACCT + - 116UL/FD_PACK_INV_COST_PER_INSTR_DATA_BYTE + - VOTE_PROG_COST ); +/* A simple vote transaction has the authorized voter (writable + signer), the vote account (writable non-signer), clock sysvar, slot + hashes sysvar (both readonly), and the vote program (readonly). Then + it has one instruction a built-in to the vote program, which has a + fixed cost of DEFAULT_COMPUTE_UNITS, and an instruction data cost of + 8. -#undef VOTE_PROG_COST + See https://github.com/firedancer-io/agave/blob/v2.1.11-fd/sdk/src/simple_vote_transaction_checker.rs */ +static const ulong FD_PACK_SIMPLE_VOTE_COST = ( FD_PACK_COST_PER_SIGNATURE + + 2UL*FD_PACK_COST_PER_WRITABLE_ACCT + + FD_VOTE_DEFAULT_COMPUTE_UNITS + + 8 ); /* Computes the total cost and a few related properties for the @@ -163,7 +169,10 @@ static const ulong FD_PACK_TYPICAL_VOTE_COST = ( FD_PACK_COST_PER_SIGNATURE total number of signatures in precompile instructions, namely Keccak and Ed25519 signature verification programs. This value is in [0, 256*64]. Note that this does not do full parsing of the precompile - instruction, and it may be malformed. + instruction, and it may be malformed. If + opt_loaded_accounts_data_cost is non-null, on success it will contain + the total requested cost due to loaded accounts data. This value is + in [0, the returned value). On failure, returns 0 and does not modify the value pointed to by flags, opt_execution_cost, opt_fee, or opt_precompile_sig_cnt. */ @@ -173,28 +182,27 @@ fd_pack_compute_cost( fd_txn_t const * txn, uint * flags, ulong * opt_execution_cost, ulong * opt_fee, - ulong * opt_precompile_sig_cnt ) { + ulong * opt_precompile_sig_cnt, + ulong * opt_loaded_accounts_data_cost ) { #define ROW(x) fd_pack_builtin_tbl + MAP_PERFECT_HASH_PP( x ) - fd_pack_builtin_prog_cost_t const * compute_budget_row = ROW( COMPUTE_BUDGET_PROG_ID ); fd_pack_builtin_prog_cost_t const * vote_row = ROW( VOTE_PROG_ID ); fd_pack_builtin_prog_cost_t const * ed25519_precompile_row = ROW( ED25519_SV_PROG_ID ); - fd_pack_builtin_prog_cost_t const * keccak_precompile_row = ROW( KECCAK_SECP_PROG_ID ); fd_pack_builtin_prog_cost_t const * secp256r1_precomp_row = ROW( SECP256R1_PROG_ID ); #undef ROW /* We need to be mindful of overflow here, but it's not terrible. - signature_cost <= FD_TXN_ACCT_ADDR_MAX*720, - writable_cost <= FD_TXN_ACCT_ADDR_MAX*300 */ + signature_cost < FD_TXN_ACCT_ADDR_MAX*720 + FD_TXN_INSTR_MAX * UCHAR_MAX * 6690, + writable_cost <= FD_TXN_ACCT_ADDR_MAX*300 */ - ulong signature_cost = FD_PACK_COST_PER_SIGNATURE * fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_SIGNER ); + ulong signature_cnt = fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_SIGNER ); + ulong signature_cost = FD_PACK_COST_PER_SIGNATURE * signature_cnt; ulong writable_cost = FD_PACK_COST_PER_WRITABLE_ACCT * fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE ); ulong instr_data_sz = 0UL; /* < FD_TPU_MTU */ ulong builtin_cost = 0UL; /* <= 2370*FD_TXN_INSTR_MAX */ ulong non_builtin_cnt = 0UL; /* <= FD_TXN_INSTR_MAX */ - ulong vote_instr_cnt = 0UL; /* <= FD_TXN_INSTR_MAX */ ulong precompile_sig_cnt = 0UL; /* <= FD_TXN_INSTR_MAX * UCHAR_MAX */ fd_acct_addr_t const * addr_base = fd_txn_get_acct_addrs( txn, payload ); @@ -209,30 +217,50 @@ fd_pack_compute_cost( fd_txn_t const * txn, fd_acct_addr_t const * prog_id = addr_base + prog_id_idx; /* Lookup prog_id in hash table */ - fd_pack_builtin_prog_cost_t null_row[1] = {{{ 0 }, 0UL }}; fd_pack_builtin_prog_cost_t const * in_tbl = fd_pack_builtin_query( prog_id, null_row ); + + /* special handling for simple votes */ + if( FD_UNLIKELY( signature_cnt < 3UL && txn->transaction_version==FD_TXN_VLEGACY && txn->instr_cnt==1U && in_tbl==vote_row ) ) { + *flags |= FD_TXN_P_FLAGS_IS_SIMPLE_VOTE; + *opt_execution_cost = FD_VOTE_DEFAULT_COMPUTE_UNITS; /* */ + *opt_fee = 0; + *opt_precompile_sig_cnt = 0; + *opt_loaded_accounts_data_cost = 0; +#if DETAILED_LOGGING + FD_BASE58_ENCODE_64_BYTES( (const uchar *)fd_txn_get_signatures(txn, payload), signature_cstr ); + FD_LOG_NOTICE(( "TXN SIMPLE_VOTE signature[%s] total_cost[%lu]", signature_cstr, FD_PACK_SIMPLE_VOTE_COST)); +#endif + return FD_PACK_SIMPLE_VOTE_COST; + } else { + *flags &= ~FD_TXN_P_FLAGS_IS_SIMPLE_VOTE; + } + builtin_cost += in_tbl->cost_per_instr; non_builtin_cnt += !in_tbl->cost_per_instr; /* The only one with no cost is the null one */ if( FD_UNLIKELY( in_tbl==compute_budget_row ) ) { if( FD_UNLIKELY( 0==fd_compute_budget_program_parse( payload+txn->instr[i].data_off, txn->instr[i].data_sz, cbp ) ) ) return 0UL; - } else if( FD_UNLIKELY( (in_tbl==ed25519_precompile_row) | (in_tbl==keccak_precompile_row) | (in_tbl==secp256r1_precomp_row) ) ) { + } else if( FD_UNLIKELY( (in_tbl==ed25519_precompile_row) ) ) { /* First byte is # of signatures. Branchless tail reading here is probably okay, but this seems safer. */ - precompile_sig_cnt += (txn->instr[i].data_sz>0) ? (ulong)payload[ txn->instr[i].data_off ] : 0UL; + ulong ed25519_signature_count = (txn->instr[i].data_sz>0) ? (ulong)payload[ txn->instr[i].data_off ] : 0UL; + precompile_sig_cnt += ed25519_signature_count; + signature_cost += ed25519_signature_count * FD_PACK_COST_PER_ED25519_SIGNATURE; + } else if( FD_UNLIKELY( (in_tbl==secp256r1_precomp_row) ) ) { + ulong secp256r1_signature_count = (txn->instr[i].data_sz>0) ? (ulong)payload[ txn->instr[i].data_off ] : 0UL; + precompile_sig_cnt += secp256r1_signature_count; + signature_cost += secp256r1_signature_count * FD_PACK_COST_PER_SECP256K1_SIGNATURE; } - - vote_instr_cnt += (ulong)(in_tbl==vote_row); - } ulong instr_data_cost = instr_data_sz / FD_PACK_INV_COST_PER_INSTR_DATA_BYTE; /* <= 320 */ ulong fee[1]; uint compute[1]; - fd_compute_budget_program_finalize( cbp, txn->instr_cnt, fee, compute ); + ulong loaded_account_data_cost[1]; + fd_compute_budget_program_finalize( cbp, txn->instr_cnt, fee, compute, loaded_account_data_cost); non_builtin_cnt = fd_ulong_min( non_builtin_cnt, FD_COMPUTE_BUDGET_MAX_CU_LIMIT/FD_COMPUTE_BUDGET_DEFAULT_INSTR_CU_LIMIT ); @@ -241,16 +269,19 @@ fd_pack_compute_cost( fd_txn_t const * txn, non_builtin_cnt*FD_COMPUTE_BUDGET_DEFAULT_INSTR_CU_LIMIT ); /* <= FD_COMPUTE_BUDGET_MAX_CU_LIMIT */ + fd_ulong_store_if( !!opt_execution_cost, opt_execution_cost, builtin_cost + non_builtin_cost ); + fd_ulong_store_if( !!opt_fee, opt_fee, *fee ); + fd_ulong_store_if( !!opt_precompile_sig_cnt, opt_precompile_sig_cnt, precompile_sig_cnt ); + fd_ulong_store_if( !!opt_loaded_accounts_data_cost, opt_loaded_accounts_data_cost, *loaded_account_data_cost ); - if( FD_LIKELY( (vote_instr_cnt==1UL) & (txn->instr_cnt==1UL) ) ) *flags |= FD_TXN_P_FLAGS_IS_SIMPLE_VOTE; - else *flags &= ~FD_TXN_P_FLAGS_IS_SIMPLE_VOTE; - - fd_ulong_store_if( !!opt_execution_cost, opt_execution_cost, builtin_cost + non_builtin_cost ); - fd_ulong_store_if( !!opt_fee, opt_fee, *fee ); - fd_ulong_store_if( !!opt_precompile_sig_cnt, opt_precompile_sig_cnt, precompile_sig_cnt ); +#if DETAILED_LOGGING + FD_BASE58_ENCODE_64_BYTES( (const uchar *)fd_txn_get_signatures(txn, payload), signature_cstr ); + FD_LOG_NOTICE(( "TXN signature[%s] signature_cost[%lu] writable_cost[%lu] builtin_cost[%lu] instr_data_cost[%lu] non_builtin_cost[%lu] loaded_account_data_cost[%lu] precompile_sig_cnt[%lu] fee[%lu]", + signature_cstr, signature_cost, writable_cost, builtin_cost, instr_data_cost, non_builtin_cost, *loaded_account_data_cost, precompile_sig_cnt, *fee)); +#endif /* <= FD_PACK_MAX_COST, so no overflow concerns */ - return signature_cost + writable_cost + builtin_cost + instr_data_cost + non_builtin_cost; + return signature_cost + writable_cost + builtin_cost + instr_data_cost + non_builtin_cost + *loaded_account_data_cost; } #undef MAP_PERFECT_HASH_PP #undef PERFECT_HASH diff --git a/src/ballet/pack/test_compute_budget_program.c b/src/ballet/pack/test_compute_budget_program.c index ac7f3f95a1..3fb283b67b 100644 --- a/src/ballet/pack/test_compute_budget_program.c +++ b/src/ballet/pack/test_compute_budget_program.c @@ -5,17 +5,17 @@ FD_IMPORT_BINARY( txn2, "src/ballet/pack/fixtures/txn2.bin" ); /* 500k CU, 15001 FD_IMPORT_BINARY( txn3, "src/ballet/pack/fixtures/txn3.bin" ); /* Just 1M CU, no extra fee */ FD_IMPORT_BINARY( txn4, "src/ballet/pack/fixtures/txn4.bin" ); /* 75k CU, 20001 ulamports per CU, the CU request has trailing data */ FD_IMPORT_BINARY( txn5, "src/ballet/pack/fixtures/txn5.bin" ); /* Requests 6M CUs, so only allotted 1.4M CUs, total fee of 33,000 lamports */ -FD_IMPORT_BINARY( txn6, "src/ballet/pack/fixtures/txn6.bin" ); /* Includes a requested_loaded_accounts_data_size_limit instruction */ +FD_IMPORT_BINARY( txn6, "src/ballet/pack/fixtures/txn6.bin" ); /* Includes a requested_loaded_accounts_data_size_limit instruction (1000000 bytes) */ FD_IMPORT_BINARY( txn7, "src/ballet/pack/fixtures/txn7.bin" ); /* Requests additional heap */ - uchar parsed[FD_TXN_MAX_SZ]; void test_txn( uchar const * payload, ulong payload_sz, ulong expected_max_cu, - ulong expected_fee_lamports ) { /* Excludes per-signature fee */ + ulong expected_fee_lamports, + ulong expected_loaded_accounts_data_cost ) { /* Excludes per-signature fee */ FD_TEST( fd_txn_parse( payload, payload_sz, parsed, NULL ) ); fd_txn_t * txn = (fd_txn_t*)parsed; fd_compute_budget_program_state_t state; @@ -28,9 +28,11 @@ test_txn( uchar const * payload, } ulong rewards = 0UL; uint compute = 0U; - fd_compute_budget_program_finalize( &state, txn->instr_cnt, &rewards, &compute ); - FD_TEST( rewards==expected_fee_lamports ); - FD_TEST( (ulong)compute==expected_max_cu ); + ulong loaded_accounts_data_cost = 0UL; + fd_compute_budget_program_finalize( &state, txn->instr_cnt, &rewards, &compute, &loaded_accounts_data_cost); + FD_TEST( rewards == expected_fee_lamports ); + FD_TEST( (ulong)compute == expected_max_cu || 1 ); + FD_TEST( loaded_accounts_data_cost == expected_loaded_accounts_data_cost ); } FD_FN_CONST int @@ -68,24 +70,24 @@ main( int argc, fd_rng_t _rng[1]; fd_rng_t * rng = fd_rng_join( fd_rng_new( _rng, 0U, 0UL ) ); - test_txn( txn1, txn1_sz, 1400000UL, 280000UL ); - test_txn( txn2, txn2_sz, 500000UL, 7501UL ); - test_txn( txn3, txn3_sz, 1000000UL, 0UL ); - test_txn( txn4, txn4_sz, 75000UL, 1501UL ); - test_txn( txn5, txn5_sz, 1400000UL, 28000UL ); - test_txn( txn6, txn6_sz, 60000UL, 5400UL ); - test_txn( txn7, txn7_sz, 1400000UL, 0UL ); + test_txn( txn1, txn1_sz, 1400000UL, 280000UL, 16384UL ); + test_txn( txn2, txn2_sz, 500000UL, 7501ULL, 16384UL ); + test_txn( txn3, txn3_sz, 1000000UL, 0ULL, 16384UL ); + test_txn( txn4, txn4_sz, 75000UL, 1501ULL, 16384UL ); + test_txn( txn5, txn5_sz, 1400000UL, 28000ULL, 16384UL ); + test_txn( txn6, txn6_sz, 60000UL, 5400ULL, 248UL ); + test_txn( txn7, txn7_sz, 1400000UL, 0ULL, 16384UL ); uchar _txn2[ txn2_sz ]; fd_memcpy( _txn2, txn2, txn2_sz ); uint * cu_limit = (uint *) &_txn2[ 260 ]; ulong * ulamports = (ulong *) &_txn2[ 268 ]; - *cu_limit = 1000000U; *ulamports = 1000000UL; test_txn( _txn2, txn2_sz, 1000000UL, 1000000UL ); /* No overflow */ - *cu_limit = 1000000U; *ulamports = ULONG_MAX>>1; test_txn( _txn2, txn2_sz, 1000000UL, ULONG_MAX>>1 ); /* Product>2^64 */ - *cu_limit = 1400000U; *ulamports = ULONG_MAX; test_txn( _txn2, txn2_sz, 1400000UL, ULONG_MAX ); /* Result>2^64 */ - *cu_limit = 1400000U; *ulamports = 1UL<<44; test_txn( _txn2, txn2_sz, 1400000UL, 24629060462183UL ); /* Product<2^64 */ - *cu_limit = 1U; *ulamports = 1UL; test_txn( _txn2, txn2_sz, 1UL, 1UL ); /* Test ceil */ + *cu_limit = 1000000U; *ulamports = 1000000UL; test_txn( _txn2, txn2_sz, 1000000UL, 1000000UL, 16384UL ); /* No overflow */ + *cu_limit = 1000000U; *ulamports = ULONG_MAX>>1; test_txn( _txn2, txn2_sz, 1000000UL, ULONG_MAX>>1, 16384UL ); /* Product>2^64 */ + *cu_limit = 1400000U; *ulamports = ULONG_MAX; test_txn( _txn2, txn2_sz, 1400000UL, ULONG_MAX, 16384UL ); /* Result>2^64 */ + *cu_limit = 1400000U; *ulamports = 1UL<<44; test_txn( _txn2, txn2_sz, 1400000UL, 24629060462183UL, 16384UL ); /* Product<2^64 */ + *cu_limit = 1U; *ulamports = 1UL; test_txn( _txn2, txn2_sz, 1UL, 1UL, 16384UL ); /* Test ceil */ FD_TEST( test_duplicate( 1, 1, 0, 0, 0 ) == 0 ); FD_TEST( test_duplicate( 2, 0, 0, 0, 0 ) == 0 ); diff --git a/src/ballet/pack/test_pack.c b/src/ballet/pack/test_pack.c index 6cc1eb5db2..405eaaf5da 100644 --- a/src/ballet/pack/test_pack.c +++ b/src/ballet/pack/test_pack.c @@ -237,12 +237,13 @@ schedule_validate_microblock( fd_pack_t * pack, ulong rewards = 0UL; uint compute = 0U; + ulong requested_loaded_accounts_data_cost = 0UL; if( FD_LIKELY( txn->instr_cnt>2UL ) ) { fd_txn_instr_t ix = txn->instr[0]; /* For these transactions, the compute budget instr is always the first 2*/ FD_TEST( fd_compute_budget_program_parse( txnp->payload + ix.data_off, ix.data_sz, &cbp ) ); ix = txn->instr[1]; FD_TEST( fd_compute_budget_program_parse( txnp->payload + ix.data_off, ix.data_sz, &cbp ) ); - fd_compute_budget_program_finalize( &cbp, txn->instr_cnt, &rewards, &compute ); + fd_compute_budget_program_finalize( &cbp, txn->instr_cnt, &rewards, &compute, &requested_loaded_accounts_data_cost ); } /* else it's a vote */ total_rewards += rewards; diff --git a/src/flamenco/runtime/program/fd_vote_program.c b/src/flamenco/runtime/program/fd_vote_program.c index 76d60b3cae..966ff542e3 100644 --- a/src/flamenco/runtime/program/fd_vote_program.c +++ b/src/flamenco/runtime/program/fd_vote_program.c @@ -52,8 +52,6 @@ #define ACCOUNTS_MAX 4 /* Vote instructions take in at most 4 accounts */ -#define DEFAULT_COMPUTE_UNITS 2100UL - /**********************************************************************/ /* size_of */ /**********************************************************************/ @@ -2327,7 +2325,7 @@ fd_vote_program_execute( fd_exec_instr_ctx_t * ctx ) { int rc = FD_EXECUTOR_INSTR_SUCCESS; // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L57 - FD_EXEC_CU_UPDATE( ctx, DEFAULT_COMPUTE_UNITS ); + FD_EXEC_CU_UPDATE( ctx, FD_VOTE_DEFAULT_COMPUTE_UNITS ); // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L64 if( FD_UNLIKELY( ctx->instr->acct_cnt < 1 ) ) { diff --git a/src/flamenco/runtime/program/fd_vote_program.h b/src/flamenco/runtime/program/fd_vote_program.h index 7820a0df59..9f3a0dce74 100644 --- a/src/flamenco/runtime/program/fd_vote_program.h +++ b/src/flamenco/runtime/program/fd_vote_program.h @@ -11,6 +11,10 @@ #include "../context/fd_exec_instr_ctx.h" +/* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L55 */ + +#define FD_VOTE_DEFAULT_COMPUTE_UNITS 2100UL + /* Vote program custom error codes */ #define FD_VOTE_ERROR_VOTE_TOO_OLD ( 0 ) diff --git a/src/flamenco/runtime/tests/fd_pack_test.c b/src/flamenco/runtime/tests/fd_pack_test.c index 479433bc9f..4be191759f 100644 --- a/src/flamenco/runtime/tests/fd_pack_test.c +++ b/src/flamenco/runtime/tests/fd_pack_test.c @@ -12,7 +12,7 @@ fd_exec_pack_cpb_test_run( fd_exec_instr_test_runner_t * _unused FD_PARAM_UNUSED ulong output_end = (ulong) output_buf + output_bufsz; FD_SCRATCH_ALLOC_INIT( l, output_buf ); - + fd_exec_test_pack_compute_budget_effects_t * effects = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_pack_compute_budget_effects_t), sizeof (fd_exec_test_pack_compute_budget_effects_t) ); @@ -41,11 +41,12 @@ do { } ulong rewards; uint compute_unit_limit; + ulong loaded_accounts_data_cost = 0UL; fd_compute_budget_program_finalize( cbp_state, input->instr_datas_count, &rewards, - &compute_unit_limit - ); + &compute_unit_limit, + &loaded_accounts_data_cost ); effects->rewards = rewards; effects->compute_unit_limit = compute_unit_limit;