Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pack: fix compute units #4113

Merged
merged 1 commit into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion agave
4 changes: 2 additions & 2 deletions src/app/fdctl/external_functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 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 * actual_execution_cus FD_PARAM_UNUSED, uint * actual_acct_data_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 ) {}
Expand Down
101 changes: 51 additions & 50 deletions src/app/fdctl/run/tiles/fd_bank.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "../../../../disco/tiles.h"

#include "../../../../disco/pack/fd_pack.h"
#include "../../../../disco/pack/fd_pack_cost.h"
#include "../../../../ballet/blake3/fd_blake3.h"
#include "../../../../ballet/bmtree/fd_bmtree.h"
#include "../../../../disco/metrics/fd_metrics.h"
Expand Down Expand Up @@ -95,8 +96,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 int fd_ext_bank_execute_and_commit_bundle( void const * bank, void * txns, ulong txn_cnt, uint * actual_execution_cus, uint * actual_acct_data_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 );
Expand Down Expand Up @@ -186,9 +187,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 );

Expand All @@ -197,17 +199,27 @@ 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; i<txn_cnt; i++ ) {
fd_txn_p_t * txn = (fd_txn_p_t *)( dst + (i*sizeof(fd_txn_p_t)) );

uint requested_cus = txn->pack_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_exec_plus_acct_data_cus = txn->pack_cu.requested_exec_plus_acct_data_cus;
uint non_execution_cus = txn->pack_cu.non_execution_cus;

if( FD_UNLIKELY( fd_txn_is_simple_vote_transaction( TXN(txn), txn->payload ) ) ) {
/* Simple votes are charged fixed amounts of compute regardless of
the real cost they incur. fd_ext_bank_load_and_execute_txns
returns the real cost, however, so we override it here. */
consumed_exec_cus[i] = FD_PACK_VOTE_DEFAULT_COMPUTE_UNITS;
consumed_acct_data_cus[i] = 0;
}

/* 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_exec_plus_acct_data_cus + non_execution_cus;
txn->flags &= ~FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
if( FD_UNLIKELY( !(txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS) ) ) continue;

Expand All @@ -234,23 +246,16 @@ 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 + actual_acct_data_cus <= requested_exec_plus_acct_data_cus );
txn->bank_cu.rebated_cus = requested_exec_plus_acct_data_cus - ( actual_execution_cus + actual_acct_data_cus );
}

/* Commit must succeed so no failure path. This function takes
Expand Down Expand Up @@ -340,20 +345,30 @@ handle_bundle( fd_bank_ctx_t * ctx,
sidecar_footprint_bytes += FD_BANK_ABI_TXN_FOOTPRINT_SIDECAR( txn1->acct_addr_cnt, txn1->addr_table_adtl_cnt, txn1->instr_cnt, txn1->addr_table_lookup_cnt );
}

uint consumed_cus[ MAX_TXN_PER_MICROBLOCK ] = { 0U };
uint actual_execution_cus[ MAX_TXN_PER_MICROBLOCK ] = { 0U };
uint actual_acct_data_cus[ MAX_TXN_PER_MICROBLOCK ] = { 0U };
uint consumed_cus [ MAX_TXN_PER_MICROBLOCK ] = { 0U };
if( FD_LIKELY( execution_success ) ) {
/* TODO: Plumb through errors. */
execution_success = fd_ext_bank_execute_and_commit_bundle( ctx->_bank, ctx->txn_abi_mem, txn_cnt, consumed_cus );
execution_success = fd_ext_bank_execute_and_commit_bundle( ctx->_bank, ctx->txn_abi_mem, txn_cnt, actual_execution_cus, actual_acct_data_cus );
}

if( FD_LIKELY( execution_success ) ) {
ctx->metrics.success += txn_cnt;
ctx->metrics.transaction_result[ FD_METRICS_ENUM_TRANSACTION_ERROR_V_SUCCESS_IDX ] += txn_cnt;
for( ulong i=0UL; i<txn_cnt; i++ ) txns[ i ].flags |= FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
for( ulong i=0UL; i<txn_cnt; i++ ) {
txns[ i ].flags |= FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
consumed_cus[ i ] = actual_execution_cus[ i ] + actual_acct_data_cus[ i ];
}
} else {
/* If any transaction fails in a bundle ... they all fail */
for( ulong i=0UL; i<txn_cnt; i++ ) {
fd_txn_p_t * txn = txns+i;

/* If the budle failed, we want to set actual cus = requested
so that the entire bundle is rebated. */
consumed_cus[ i ] = txn->pack_cu.requested_exec_plus_acct_data_cus;

/* Don't double count metrics for transactions that failed to
sanitize. */
if( FD_UNLIKELY( !(txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS) ) ) continue;
Expand All @@ -371,30 +386,16 @@ handle_bundle( fd_bank_ctx_t * ctx,
for( ulong i=0UL; i<txn_cnt; i++ ) {
fd_txn_p_t * txn = txns+i;

uint requested_cus = txn->pack_cu.requested_execution_cus;
uint non_execution_cus = txn->pack_cu.non_execution_cus;
uint requested_exec_plus_acct_data_cus = txn->pack_cu.requested_exec_plus_acct_data_cus;
uint non_execution_cus = txn->pack_cu.non_execution_cus;
/* Assume failure, set below if success. If it doesn't land in the
block, rebate the non-execution CUs too. */
txn->bank_cu.rebated_cus = requested_cus + non_execution_cus;
txn->bank_cu.rebated_cus = requested_exec_plus_acct_data_cus + non_execution_cus;

if( FD_LIKELY( (txn->flags & (FD_TXN_P_FLAGS_SANITIZE_SUCCESS|FD_TXN_P_FLAGS_EXECUTE_SUCCESS))==(FD_TXN_P_FLAGS_SANITIZE_SUCCESS|FD_TXN_P_FLAGS_EXECUTE_SUCCESS) ) ) {
uint executed_cus = consumed_cus[ i ];
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;
txn->bank_cu.actual_consumed_cus = non_execution_cus + consumed_cus[ i ];
FD_TEST( consumed_cus[ i ] <= requested_exec_plus_acct_data_cus );
txn->bank_cu.rebated_cus = requested_exec_plus_acct_data_cus - consumed_cus[ i ];
}
}

Expand Down
40 changes: 24 additions & 16 deletions src/ballet/txn/fd_txn.h
Original file line number Diff line number Diff line change
Expand Up @@ -440,26 +440,34 @@ fd_txn_get_instr_data( fd_txn_instr_t const * instr,
return (uchar const *)((ulong)payload + (ulong)instr->data_off);
}

/*
* 1. has 1 or 2 signatures
* 2. is legacy message
* 3. has only one instruction
* 4. which must be a Vote instruction
/* fd_txn_is_simple_vote_transaction: Returns 1 if `txn` is a simple
vote and 0 otherwise. `txn` is a non-null pointer to a Solana
transaction parsed by fd_txn_parse_core. `payload` is a non-null
pointer to serialization of `txn`, which is coupled with `txn` as
both `txn` and `payload` are different representations of the same
data.

A simple vote is a transaction that meets the following criteria:
1. has 1 or 2 signatures
2. is legacy transaction
3. has exactly one instruction
4. ...which must be a Vote instruction
*/
ptaffet-jump marked this conversation as resolved.
Show resolved Hide resolved
static inline int
fd_txn_is_simple_vote_transaction( fd_txn_t const * txn,
ptaffet-jump marked this conversation as resolved.
Show resolved Hide resolved
void const * payload,
uchar const vote_program_id[ static 32 ] ) {
fd_acct_addr_t const * addr_base = fd_txn_get_acct_addrs( txn, payload );
void const * payload ) {
/* base58 decode of Vote111111111111111111111111111111111111111 */
static const uchar vote_program_id[FD_TXN_ACCT_ADDR_SZ] = {
0x07U,0x61U,0x48U,0x1dU,0x35U,0x74U,0x74U,0xbbU,0x7cU,0x4dU,0x76U,0x24U,0xebU,0xd3U,0xbdU,0xb3U,
0xd8U,0x35U,0x5eU,0x73U,0xd1U,0x10U,0x43U,0xfcU,0x0dU,0xa3U,0x53U,0x80U,0x00U,0x00U,0x00U,0x00U };

ulong instr_cnt = txn->instr_cnt;
ulong vote_instr_cnt = 0UL;
for( ulong i=0UL; i<txn->instr_cnt; i++ ) {
ulong prog_id_idx = (ulong)txn->instr[i].program_id;
fd_acct_addr_t const * prog_id = addr_base + prog_id_idx;
vote_instr_cnt += (ulong)(0 == memcmp(prog_id->b, vote_program_id, 32UL) );
}
return (vote_instr_cnt==1UL) && (instr_cnt==1UL) && (txn->transaction_version==FD_TXN_VLEGACY) && (txn->signature_cnt<3UL);
fd_acct_addr_t const * addr_base = fd_txn_get_acct_addrs( txn, payload );
if( FD_UNLIKELY( txn->instr_cnt!=1UL ) ) return 0;
if( FD_UNLIKELY( txn->transaction_version!=FD_TXN_VLEGACY ) ) return 0;
if( FD_UNLIKELY( txn->signature_cnt>2UL ) ) return 0;
ulong prog_id_idx = (ulong)txn->instr[0].program_id;
fd_acct_addr_t const * prog_id = addr_base + prog_id_idx;
return fd_memeq( prog_id->b, vote_program_id, FD_TXN_ACCT_ADDR_SZ );
}

/* fd_txn_align returns the alignment in bytes required of a region of
Expand Down
28 changes: 23 additions & 5 deletions src/disco/pack/fd_compute_budget_program.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 */
Expand All @@ -165,6 +168,21 @@ 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_HEAP_COST * ( (
loaded_accounts_data_size + ( FD_COMPUTE_BUDGET_ACCOUNT_DATA_COST_PAGE_SIZE - 1UL )
) / FD_COMPUTE_BUDGET_ACCOUNT_DATA_COST_PAGE_SIZE );
*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),
Expand Down
6 changes: 3 additions & 3 deletions src/disco/pack/fd_microblock.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ struct __attribute__((aligned(64))) fd_txn_p {
union {
struct {
uint non_execution_cus;
uint requested_execution_cus;
uint requested_exec_plus_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 rebated_cus; /* requested_exec_plus_acct_data_cus-actual used CUs. Pack reads this for CU rebating. */
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. */
};
Expand Down
Loading
Loading