diff --git a/src/app/fdctl/run/tiles/fd_metric.c b/src/app/fdctl/run/tiles/fd_metric.c index 0319a32edf..a6531fe2bb 100644 --- a/src/app/fdctl/run/tiles/fd_metric.c +++ b/src/app/fdctl/run/tiles/fd_metric.c @@ -251,6 +251,9 @@ prometheus_print( fd_topo_t * topo, PRINT( "\n" ); result = prometheus_print1( topo, out, out_len, FD_TOPO_TILE_KIND_QUIC, FD_METRICS_QUIC_TOTAL, FD_METRICS_QUIC, PRINT_TILE ); if( FD_UNLIKELY( result<0 ) ) return result; + PRINT( "\n" ); + result = prometheus_print1( topo, out, out_len, FD_TOPO_TILE_KIND_PACK, FD_METRICS_PACK_TOTAL, FD_METRICS_PACK, PRINT_TILE ); + if( FD_UNLIKELY( result<0 ) ) return result; /* Now backfill Content-Length */ int printed = snprintf( content_len, 21, "%lu", start_len - *out_len - content_start ); diff --git a/src/app/fdctl/run/tiles/fd_pack.c b/src/app/fdctl/run/tiles/fd_pack.c index 8539eed766..4dd79ef392 100644 --- a/src/app/fdctl/run/tiles/fd_pack.c +++ b/src/app/fdctl/run/tiles/fd_pack.c @@ -132,6 +132,10 @@ typedef struct { ulong out_wmark; ulong out_chunk; + ulong insert_result[ FD_PACK_INSERT_RETVAL_CNT ]; + fd_histf_t schedule_duration[ 1 ]; + fd_histf_t insert_duration [ 1 ]; + fd_stake_ci_t stake_ci[ 1 ]; } fd_pack_ctx_t; @@ -170,6 +174,15 @@ during_housekeeping( void * _ctx ) { } } +static inline void +metrics_write( void * _ctx ) { + fd_pack_ctx_t * ctx = (fd_pack_ctx_t *)_ctx; + + FD_MCNT_ENUM_COPY( PACK, TRANSACTION_INSERTED, ctx->insert_result ); + FD_MHIST_COPY( PACK, SCHEDULE_MICROBLOCK_DURATION_SECONDS, ctx->schedule_duration ); + FD_MHIST_COPY( PACK, INSERT_TRANSACTION_DURATION_SECONDS, ctx->insert_duration ); +} + static inline int am_i_leader( fd_pack_ctx_t * ctx, ulong slot ) { @@ -262,7 +275,7 @@ before_credit( void * _ctx, } } - if( FD_UNLIKELY( ctx->packing_for != initial_packing_for ) ) fd_pack_end_block( ctx->pack ); + if( FD_UNLIKELY( (ctx->packing_for!=initial_packing_for) & (initial_packing_for!=ULONG_MAX) ) ) fd_pack_end_block( ctx->pack ); } static inline void @@ -281,7 +294,11 @@ after_credit( void * _ctx, fd_pack_microblock_complete( ctx->pack, i ); void * microblock_dst = fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk ); + long schedule_duration = -fd_tickcount(); ulong schedule_cnt = fd_pack_schedule_next_microblock( ctx->pack, CUS_PER_MICROBLOCK, VOTE_FRACTION, i, microblock_dst ); + schedule_duration += fd_tickcount(); + fd_histf_sample( ctx->schedule_duration, (ulong)schedule_duration ); + if( FD_LIKELY( schedule_cnt ) ) { ulong tspub = (ulong)fd_frag_meta_ts_comp( fd_tickcount() ); ulong chunk = ctx->out_chunk; @@ -296,8 +313,10 @@ after_credit( void * _ctx, fd_mux_publish( mux, sig, chunk, msg_sz, 0, 0UL, tspub ); ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, msg_sz, ctx->out_chunk0, ctx->out_wmark ); - ctx->out_ready_at[i] = now + MICROBLOCK_DURATION_NS; } + /* TODO: Do we want to reset the timer even if we don't get any + transactions? */ + ctx->out_ready_at[i] = now + MICROBLOCK_DURATION_NS; } } } @@ -335,6 +354,7 @@ during_frag( void * _ctx, 1 so they can be distinguished. */ if( FD_LIKELY( !sig ) ) { + FD_MCNT_INC( PACK, NORMAL_TRANSACTION_RECEIVED, 1UL ); /* Assume that the dcache entry is: Payload ....... (payload_sz bytes) 0 or 1 byte of padding (since alignof(fd_txn) is 2) @@ -350,6 +370,7 @@ during_frag( void * _ctx, } else { /* Here there is just a transaction payload, so it needs to be parsed. We can parse right out into the pack structure. */ + FD_MCNT_INC( PACK, GOSSIPED_VOTES_RECEIVED, 1UL ); payload_sz = sz; fd_memcpy( ctx->cur_spot->payload, dcache_entry, sz ); ulong txn_t_sz = fd_txn_parse( ctx->cur_spot->payload, sz, TXN(ctx->cur_spot), NULL ); @@ -392,7 +413,12 @@ after_frag( void * _ctx, fd_stake_ci_stake_msg_fini( ctx->stake_ci ); } else { /* Normal transaction case */ - fd_pack_insert_txn_fini( ctx->pack, ctx->cur_spot ); + long insert_duration = -fd_tickcount(); + int result = fd_pack_insert_txn_fini( ctx->pack, ctx->cur_spot ); + insert_duration += fd_tickcount(); + ctx->insert_result[ result + FD_PACK_INSERT_RETVAL_OFF ]++; + fd_histf_sample( ctx->insert_duration, (ulong)insert_duration ); + ctx->cur_spot = NULL; } } @@ -455,6 +481,7 @@ unprivileged_init( fd_topo_t * topo, ctx->out_busy[ i ] = tile->extra[ i ]; if( FD_UNLIKELY( !ctx->out_busy[ i ] ) ) FD_LOG_ERR(( "banking tile %lu has no busy flag", i )); ctx->out_ready_at[ i ] = 0L; + FD_TEST( 0UL==fd_fseq_query( ctx->out_busy[ i ] ) ); } for( ulong i=0; iin_cnt; i++ ) { @@ -473,6 +500,13 @@ unprivileged_init( fd_topo_t * topo, fd_stake_ci_join( fd_stake_ci_new( ctx->stake_ci, &(ctx->identity_pubkey) ) ); + /* Initialize metrics storage */ + memset( ctx->insert_result, '\0', FD_PACK_INSERT_RETVAL_CNT * sizeof(ulong) ); + fd_histf_join( fd_histf_new( ctx->schedule_duration, FD_MHIST_SECONDS_MIN( PACK, SCHEDULE_MICROBLOCK_DURATION_SECONDS ), + FD_MHIST_SECONDS_MAX( PACK, SCHEDULE_MICROBLOCK_DURATION_SECONDS ) ) ); + fd_histf_join( fd_histf_new( ctx->insert_duration, FD_MHIST_SECONDS_MIN( PACK, INSERT_TRANSACTION_DURATION_SECONDS ), + FD_MHIST_SECONDS_MAX( PACK, INSERT_TRANSACTION_DURATION_SECONDS ) ) ); + FD_LOG_INFO(( "packing blocks of at most %lu transactions to %lu bank tiles", MAX_TXN_PER_MICROBLOCK, out_cnt )); ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, 1UL ); @@ -527,6 +561,7 @@ fd_tile_config_t fd_tile_pack = { .mux_after_credit = after_credit, .mux_during_frag = during_frag, .mux_after_frag = after_frag, + .mux_metrics_write = metrics_write, .lazy = lazy, .populate_allowed_seccomp = populate_allowed_seccomp, .populate_allowed_fds = populate_allowed_fds, diff --git a/src/ballet/pack/fd_pack.c b/src/ballet/pack/fd_pack.c index cbb1a08080..5899a267fc 100644 --- a/src/ballet/pack/fd_pack.c +++ b/src/ballet/pack/fd_pack.c @@ -4,7 +4,7 @@ #include "fd_compute_budget_program.h" #include /* for sqrt */ #include /* for offsetof */ - +#include "../../disco/metrics/fd_metrics.h" /* Declare a bunch of helper structs used for pack-internal data structures. */ @@ -232,25 +232,22 @@ struct fd_pack_private { fd_pack_ord_txn_t * pool; /* Transactions in the pool can be in one of various trees. The - default situation is that the transaction is in pending + default situation is that the transaction is in pending or pending_votes, depending on whether it is a vote or not. If this were the only storage for transactions though, in the case that there are a lot of transactions that conflict, we'd end up going through transactions a bunch of times. To optimize that, when we know that we won't be able to consider a transaction until - at least the kth microblock in the future, we stick it in a "data - structure" like a bucket queue based on when it will become - available. + at least a certain microblock finishes, we stick it in a "data + structure" like a bucket queue based on which currently scheduled + microblocks it conflicts with. This is just a performance optimization and done on a best effort - basis; a transaction coming out of delayed might still not be - available because of new conflicts. Transactions in pending might - have conflicts we just haven't discovered yet. The authoritative - source for conflicts is acct_uses_{read,write}. - - Unlike typical bucket queues, the buckets here form a ring, and - each element of the ring is a tree. */ + basis; a transaction coming out of conflicting_with might still not + be available because of new conflicts. Transactions in pending + might have conflicts we just haven't discovered yet. The + authoritative source for conflicts is acct_uses_{read,write}. */ treap_t pending[1]; treap_t pending_votes[1]; @@ -272,6 +269,9 @@ struct fd_pack_private { microblock finishes. */ fd_pack_addr_use_t * use_by_bank [ FD_PACK_MAX_BANK_TILES ]; ulong use_by_bank_cnt[ FD_PACK_MAX_BANK_TILES ]; + + fd_histf_t txn_per_microblock [ 1 ]; + fd_histf_t vote_per_microblock[ 1 ]; }; typedef struct fd_pack_private fd_pack_t; @@ -292,12 +292,12 @@ fd_pack_footprint( ulong pack_depth, int lg_depth = fd_ulong_find_msb( fd_ulong_pow2_up( 2UL*pack_depth ) ); l = FD_LAYOUT_INIT; - l = FD_LAYOUT_APPEND( l, FD_PACK_ALIGN, sizeof(fd_pack_t) ); - l = FD_LAYOUT_APPEND( l, trp_pool_align (), trp_pool_footprint ( pack_depth+1UL ) ); /* pool */ - l = FD_LAYOUT_APPEND( l, acct_uses_align(), acct_uses_footprint( lg_uses_tbl_sz ) ); /* acct_in_use */ - l = FD_LAYOUT_APPEND( l, acct_uses_align(), acct_uses_footprint( lg_max_txn ) ); /* writer_costs */ - l = FD_LAYOUT_APPEND( l, sig2txn_align (), sig2txn_footprint ( lg_depth ) ); /* signature_map */ - l = FD_LAYOUT_APPEND( l, 32UL, max_acct_in_flight ); /* use_by_bank */ + l = FD_LAYOUT_APPEND( l, FD_PACK_ALIGN, sizeof(fd_pack_t) ); + l = FD_LAYOUT_APPEND( l, trp_pool_align (), trp_pool_footprint ( pack_depth+1UL ) ); /* pool */ + l = FD_LAYOUT_APPEND( l, acct_uses_align(), acct_uses_footprint( lg_uses_tbl_sz ) ); /* acct_in_use */ + l = FD_LAYOUT_APPEND( l, acct_uses_align(), acct_uses_footprint( lg_max_txn ) ); /* writer_costs */ + l = FD_LAYOUT_APPEND( l, sig2txn_align (), sig2txn_footprint ( lg_depth ) ); /* signature_map */ + l = FD_LAYOUT_APPEND( l, 32UL, sizeof(fd_pack_addr_use_t)*max_acct_in_flight ); /* use_by_bank */ return FD_LAYOUT_FINI( l, FD_PACK_ALIGN ); } @@ -361,6 +361,10 @@ fd_pack_new( void * mem, for( ulong i=0UL; iuse_by_bank[i]=use_by_bank + i*(FD_TXN_ACCT_ADDR_MAX*max_txn_per_microblock+1UL); for( ulong i=0UL; iuse_by_bank_cnt[i]=0UL; + fd_histf_join( fd_histf_new( pack->txn_per_microblock, FD_MHIST_MIN( PACK, TOTAL_TRANSACTIONS_PER_MICROBLOCK_COUNT ), + FD_MHIST_MAX( PACK, TOTAL_TRANSACTIONS_PER_MICROBLOCK_COUNT ) ) ); + fd_histf_join( fd_histf_new( pack->vote_per_microblock, FD_MHIST_MIN( PACK, VOTES_PER_MICROBLOCK_COUNT ), + FD_MHIST_MAX( PACK, VOTES_PER_MICROBLOCK_COUNT ) ) ); return mem; } @@ -386,6 +390,7 @@ fd_pack_join( void * mem ) { pack->writer_costs = acct_uses_join( FD_SCRATCH_ALLOC_APPEND( l, acct_uses_align(), acct_uses_footprint( lg_max_txn ) ) ); pack->signature_map = sig2txn_join( FD_SCRATCH_ALLOC_APPEND( l, sig2txn_align(), sig2txn_footprint ( lg_depth ) ) ); + FD_MGAUGE_SET( PACK, PENDING_TRANSACTIONS_HEAP_SIZE, pack_depth ); return pack; } @@ -453,7 +458,12 @@ fd_pack_can_fee_payer_afford( fd_acct_addr_t const * acct_addr, fd_txn_p_t * fd_pack_insert_txn_init( fd_pack_t * pack ) { return trp_pool_ele_acquire( pack->pool )->txn; } void fd_pack_insert_txn_cancel( fd_pack_t * pack, fd_txn_p_t * txn ) { trp_pool_ele_release( pack->pool, (fd_pack_ord_txn_t*)txn ); } -void +#define REJECT( reason ) do { \ + trp_pool_ele_release( pack->pool, ord ); \ + return FD_PACK_INSERT_REJECT_ ## reason; \ + } while( 0 ) + +int fd_pack_insert_txn_fini( fd_pack_t * pack, fd_txn_p_t * txnp ) { @@ -464,10 +474,7 @@ fd_pack_insert_txn_fini( fd_pack_t * pack, fd_acct_addr_t const * accts = fd_txn_get_acct_addrs( txn, payload ); - if( FD_UNLIKELY( !fd_pack_estimate_rewards_and_compute( txnp, ord ) ) ) { - trp_pool_ele_release( pack->pool, ord ); - return; - } + if( FD_UNLIKELY( !fd_pack_estimate_rewards_and_compute( txnp, ord ) ) ) REJECT( ESTIMATION_FAIL ); fd_txn_acct_iter_t ctrl[1]; int writes_to_sysvar = 0; @@ -480,37 +487,36 @@ fd_pack_insert_txn_fini( fd_pack_t * pack, /* Throw out transactions ... */ /* ... that are unfunded */ - if( FD_UNLIKELY( !fd_pack_can_fee_payer_afford( accts, ord->rewards ) ) ) { trp_pool_ele_release( pack->pool, ord ); return; } + if( FD_UNLIKELY( !fd_pack_can_fee_payer_afford( accts, ord->rewards ) ) ) REJECT( UNAFFORDABLE ); /* ... that are so big they'll never run */ - if( FD_UNLIKELY( ord->compute_est >= FD_PACK_MAX_COST_PER_BLOCK ) ) { trp_pool_ele_release( pack->pool, ord ); return; } + if( FD_UNLIKELY( ord->compute_est >= FD_PACK_MAX_COST_PER_BLOCK ) ) REJECT( TOO_LARGE ); /* ... that try to write to a sysvar */ - if( FD_UNLIKELY( writes_to_sysvar ) ) { trp_pool_ele_release( pack->pool, ord ); return; } + if( FD_UNLIKELY( writes_to_sysvar ) ) REJECT( WRITES_SYSVAR ); /* ... that we already know about */ - if( FD_UNLIKELY( sig2txn_query( pack->signature_map, sig, NULL ) ) ) { trp_pool_ele_release( pack->pool, ord ); return; } + if( FD_UNLIKELY( sig2txn_query( pack->signature_map, sig, NULL ) ) ) REJECT( DUPLICATE ); /* TODO: Add recent blockhash based expiry here */ + int replaces = 0; if( FD_UNLIKELY( pack->pending_txn_cnt == pack->pack_depth ) ) { /* If the tree is full, we'll double check to make sure this is better than the worst element in the tree before inserting. If the new transaction is better than that one, we'll delete it and insert the new transaction. Otherwise, we'll throw away this transaction. */ - /* TODO: Increment a counter to mark this is happening */ fd_pack_ord_txn_t * worst = treap_fwd_iter_ele( treap_fwd_iter_init( pack->pending, pack->pool ), pack->pool ); if( FD_UNLIKELY( !worst ) ) { /* We have nothing to sacrifice because they're all in other trees. */ - trp_pool_ele_release( pack->pool, ord ); - return; + REJECT( FULL ); } else if( !COMPARE_WORSE( worst, ord ) ) { /* What we have in the tree is better than this transaction, so just pretend this transaction never happened */ - trp_pool_ele_release( pack->pool, ord ); - return; + REJECT( PRIORITY ); } else { /* Remove the worst from the tree */ + replaces = 1; fd_ed25519_sig_t const * worst_sig = fd_txn_get_signatures( TXN( worst->txn ), worst->txn->payload ); sig2txn_remove( pack->signature_map, sig2txn_query( pack->signature_map, worst_sig, NULL ) ); @@ -524,11 +530,15 @@ fd_pack_insert_txn_fini( fd_pack_t * pack, sig2txn_insert( pack->signature_map, fd_txn_get_signatures( txn, payload ) ); - if( FD_LIKELY( ord->root == FD_ORD_TXN_ROOT_PENDING_VOTE ) ) + if( FD_LIKELY( ord->root == FD_ORD_TXN_ROOT_PENDING_VOTE ) ) { treap_ele_insert( pack->pending_votes, ord, pack->pool ); - else + return replaces ? FD_PACK_INSERT_ACCEPT_VOTE_REPLACE : FD_PACK_INSERT_ACCEPT_VOTE_ADD; + } else { treap_ele_insert( pack->pending, ord, pack->pool ); + return replaces ? FD_PACK_INSERT_ACCEPT_NONVOTE_REPLACE : FD_PACK_INSERT_ACCEPT_NONVOTE_ADD; + } } +#undef REJECT typedef struct { ulong cus_scheduled; @@ -551,8 +561,9 @@ fd_pack_schedule_microblock_impl( fd_pack_t * pack, fd_pack_addr_use_t * use_by_bank = pack->use_by_bank [bank_tile]; ulong use_by_bank_cnt = pack->use_by_bank_cnt[bank_tile]; - ulong txns_scheduled = 0UL; - ulong cus_scheduled = 0UL; + ulong txns_considered = 0UL; + ulong txns_scheduled = 0UL; + ulong cus_scheduled = 0UL; ulong bank_tile_mask = 1UL << bank_tile; @@ -561,6 +572,8 @@ fd_pack_schedule_microblock_impl( fd_pack_t * pack, (cu_limit>=FD_PACK_MIN_TXN_COST) & (txn_limit>0) & !treap_rev_iter_done( _cur ); _cur=prev ) { prev = treap_rev_iter_next( _cur, pool ); + txns_considered++; + fd_pack_ord_txn_t * cur = treap_rev_iter_ele( _cur, pool ); fd_txn_t * txn = TXN(cur->txn); @@ -657,6 +670,7 @@ fd_pack_schedule_microblock_impl( fd_pack_t * pack, treap_ele_insert( move_to, cur, pool ); } } + FD_MCNT_INC( PACK, TRANSACTION_SKIPPED, txns_considered-txns_scheduled ); pack->use_by_bank_cnt[bank_tile] = use_by_bank_cnt; @@ -706,13 +720,16 @@ fd_pack_schedule_next_microblock( fd_pack_t * pack, ulong vote_reserved_txns = fd_ulong_min( vote_cus/FD_PACK_TYPICAL_VOTE_COST, (ulong)((float)pack->max_txn_per_microblock * vote_fraction) ); - if( FD_UNLIKELY( pack->microblock_cnt >= pack->max_microblocks_per_block ) ) return 0UL; + if( FD_UNLIKELY( pack->microblock_cnt >= pack->max_microblocks_per_block ) ) { + FD_MCNT_INC( PACK, MICROBLOCK_PER_BLOCK_LIMIT, 1UL ); + return 0UL; + } ulong cu_limit = total_cus - vote_cus; ulong txn_limit = pack->max_txn_per_microblock - vote_reserved_txns; ulong scheduled = 0UL; - sched_return_t status; + sched_return_t status, status1; /* Try to schedule non-vote transactions */ status = fd_pack_schedule_microblock_impl( pack, pack->pending, 1, cu_limit, txn_limit, bank_tile, out+scheduled ); @@ -724,14 +741,14 @@ fd_pack_schedule_next_microblock( fd_pack_t * pack, /* Schedule vote transactions */ - status = fd_pack_schedule_microblock_impl( pack, pack->pending_votes, 0, vote_cus, vote_reserved_txns, bank_tile, out+scheduled ); + status1= fd_pack_schedule_microblock_impl( pack, pack->pending_votes, 0, vote_cus, vote_reserved_txns, bank_tile, out+scheduled ); - scheduled += status.txns_scheduled; - pack->cumulative_vote_cost += status.cus_scheduled; - pack->cumulative_block_cost += status.cus_scheduled; + scheduled += status1.txns_scheduled; + pack->cumulative_vote_cost += status1.cus_scheduled; + pack->cumulative_block_cost += status1.cus_scheduled; /* Add any remaining CUs/txns to the non-vote limits */ - txn_limit += vote_reserved_txns - status.txns_scheduled; - cu_limit += vote_cus - status.cus_scheduled; + txn_limit += vote_reserved_txns - status1.txns_scheduled; + cu_limit += vote_cus - status1.cus_scheduled; /* Fill any remaining space with non-vote transactions */ @@ -743,6 +760,13 @@ fd_pack_schedule_next_microblock( fd_pack_t * pack, pack->microblock_cnt += (ulong)(scheduled>0UL); pack->outstanding_microblock_mask |= 1UL << bank_tile; + /* Update metrics counters */ + FD_MGAUGE_SET( PACK, AVAILABLE_TRANSACTIONS, pack->pending_txn_cnt ); + FD_MGAUGE_SET( PACK, AVAILABLE_VOTE_TRANSACTIONS, treap_ele_cnt( pack->pending_votes ) ); + + fd_histf_sample( pack->txn_per_microblock, scheduled ); + fd_histf_sample( pack->vote_per_microblock, status1.txns_scheduled ); + return scheduled; } @@ -763,6 +787,14 @@ fd_pack_end_block( fd_pack_t * pack ) { acct_uses_clear( pack->writer_costs ); for( ulong i=0UL; ibank_tile_cnt; i++ ) pack->use_by_bank_cnt[i] = 0UL; + + /* If our stake is low and we don't become leader often, end_block + might get called on the order of O(1/hr), which feels too + infrequent to do anything related to metrics. However, we only + update the histograms when we are leader, so this is actually a + good place to copy them. */ + FD_MHIST_COPY( PACK, TOTAL_TRANSACTIONS_PER_MICROBLOCK_COUNT, pack->txn_per_microblock ); + FD_MHIST_COPY( PACK, VOTES_PER_MICROBLOCK_COUNT, pack->vote_per_microblock ); } static void diff --git a/src/ballet/pack/fd_pack.h b/src/ballet/pack/fd_pack.h index 40b78236f4..d14228102a 100644 --- a/src/ballet/pack/fd_pack.h +++ b/src/ballet/pack/fd_pack.h @@ -104,6 +104,65 @@ FD_FN_PURE ulong fd_pack_avail_txn_cnt( fd_pack_t * pack ); FD_PACK_MAX_BANK_TILES]. */ FD_FN_PURE ulong fd_pack_bank_tile_cnt( fd_pack_t * pack ); + +/* Return values for fd_pack_insert_txn_fini: Non-negative values + indicate the transaction was accepted and may be returned in a future + microblock. Negative values indicate that the transaction was + rejected and will never be returned in a future microblock. + Transactions can be rejected through no fault of their own, so it + doesn't necessarily imply bad behavior. + + The non-negative (success) codes are essentially a bitflag of two + bits: + * whether the transaction met the critera for a simple vote or not, + * whether this transaction replaced a previously accepted, low + priority transaction, rather than being accepted in addition to all + the previously accepted transactions. Since pack maintains a heap + with a fixed max size of pack_depth, replacing transaction is + necessary whenever the heap is full. + + The negative (failure) codes are a normal enumeration (not a + bitflag). + * PRIORITY: pack's heap was full and the transaction's priority was + lower than the worst currently accepted transaction. + * DUPLICATE: the transaction is a duplicate of a currently accepted + transaction. + * UNAFFORDABLE: the fee payer could not afford the transaction fee + (not yet implemented). + * TOO_LARGE: the transaction requested too many CUs and would never + be scheduled if it had been accepted. + * ESTIMATION_FAIL: estimation of the transaction's compute cost and + fee failed, typically because the transaction contained a + malformed ComputeBudgetProgram instruction. + * WRITES_SYSVAR: the transaction attempts to write-lock a sysvar. + Write-locking a sysvar can cause heavy contention. Solana Labs + solves this by downgrading these to read locks, but we instead + solve it by refusing to pack such transactions. + * FULL: in rare situations where the heap is full, pack may not be + able to accept a transaction regardless of its priority because a + transaction cannot be found to be replaced. This mostly can + happen if the whole heap is full of votes. + + NOTE: The corresponding enum in metrics.xml must be kept in sync + with any changes to these return values. */ +#define FD_PACK_INSERT_ACCEPT_VOTE_REPLACE ( 3) +#define FD_PACK_INSERT_ACCEPT_NONVOTE_REPLACE ( 2) +#define FD_PACK_INSERT_ACCEPT_VOTE_ADD ( 1) +#define FD_PACK_INSERT_ACCEPT_NONVOTE_ADD ( 0) +#define FD_PACK_INSERT_REJECT_PRIORITY (-1) +#define FD_PACK_INSERT_REJECT_DUPLICATE (-2) +#define FD_PACK_INSERT_REJECT_UNAFFORDABLE (-3) +#define FD_PACK_INSERT_REJECT_TOO_LARGE (-4) +#define FD_PACK_INSERT_REJECT_ESTIMATION_FAIL (-5) +#define FD_PACK_INSERT_REJECT_WRITES_SYSVAR (-6) +#define FD_PACK_INSERT_REJECT_FULL (-7) + +/* The FD_PACK_INSERT_{ACCEPT, REJECT}_* values defined above are in the + range [-FD_PACK_INSERT_RETVAL_OFF, + -FD_PACK_INSERT_RETVAL_OFF+FD_PACK_INSERT_RETVAL_CNT ) */ +#define FD_PACK_INSERT_RETVAL_OFF 7 +#define FD_PACK_INSERT_RETVAL_CNT 11 + /* fd_pack_insert_txn_{init,fini,cancel} execute the process of inserting a new transaction into the pool of available transactions that may be scheduled by the pack object. @@ -124,10 +183,13 @@ FD_FN_PURE ulong fd_pack_bank_tile_cnt( fd_pack_t * pack ); interest in the transaction after _fini or _cancel have been called. pack must be a local join of a pack object. From the caller's - perspective, these functions cannot fail. + perspective, these functions cannot fail, though pack may reject a + transaction for a variety of reasons. fd_pack_insert_txn_fini + returns one of the FD_PACK_INSERT_ACCEPT_* or FD_PACK_INSERT_REJECT_* + codes explained above. */ fd_txn_p_t * fd_pack_insert_txn_init ( fd_pack_t * pack ); -void fd_pack_insert_txn_fini ( fd_pack_t * pack, fd_txn_p_t * txn ); +int fd_pack_insert_txn_fini ( fd_pack_t * pack, fd_txn_p_t * txn ); void fd_pack_insert_txn_cancel( fd_pack_t * pack, fd_txn_p_t * txn ); diff --git a/src/ballet/pack/test_deduplication.c b/src/ballet/pack/test_deduplication.c new file mode 100644 index 0000000000..b9557a9313 --- /dev/null +++ b/src/ballet/pack/test_deduplication.c @@ -0,0 +1,281 @@ +#define FD_UNALIGNED_ACCESS_STYLE 0 +#include "../fd_ballet.h" +#include "fd_pack.h" +#include "fd_compute_budget_program.h" +#include "../txn/fd_txn.h" +#include "../../util/simd/fd_avx.h" + +FD_IMPORT_BINARY( sample_vote, "src/ballet/pack/sample_vote.bin" ); +FD_IMPORT_BINARY( transaction1, "src/ballet/txn/fixtures/transaction1.bin" ); +FD_IMPORT_BINARY( transaction2, "src/ballet/txn/fixtures/transaction2.bin" ); +FD_IMPORT_BINARY( transaction3, "src/ballet/txn/fixtures/transaction3.bin" ); + +#define SORT_NAME sort_pubkeys +#define SORT_KEY_T fd_acct_addr_t +#define SORT_BEFORE(_a,_b) (memcmp( (_a).b, (_b).b, FD_TXN_ACCT_ADDR_SZ )>0) +#include "../../util/tmpl/fd_sort.c" + +uchar _txn[ FD_TXN_MAX_SZ ]; + +fd_acct_addr_t scratch1[ FD_TXN_ACCT_ADDR_MAX ] __attribute__((aligned(32))); +fd_acct_addr_t scratch2[ FD_TXN_ACCT_ADDR_MAX ] __attribute__((aligned(32))); + +int +check_sort( uchar const * payload, + ulong sz ) { + FD_TEST( fd_txn_parse( payload, sz, _txn, NULL ) ); + fd_txn_t * txn = (fd_txn_t*)_txn; + ulong acct_cnt = fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_IMM ); + + memcpy( scratch1, fd_txn_get_acct_addrs( txn, payload ), acct_cnt*FD_TXN_ACCT_ADDR_SZ ); + + fd_acct_addr_t * sorted = sort_pubkeys_stable_fast( scratch1, acct_cnt, scratch2 ); + for( ulong i=1UL; i FD_IMPORT_BINARY( sample_vote, "src/ballet/pack/sample_vote.bin" ); @@ -17,6 +18,7 @@ ulong payload_sz[ MAX_TEST_TXNS ]; #define PACK_SCRATCH_SZ (128UL*1024UL*1024UL) uchar pack_scratch[ PACK_SCRATCH_SZ ] __attribute__((aligned(128))); +uchar metrics_scratch[ FD_METRICS_FOOTPRINT( 0, 0 ) ] __attribute__((aligned(FD_METRICS_ALIGN))); const char SIGNATURE_SUFFIX[ FD_TXN_SIGNATURE_SZ - sizeof(ulong) - sizeof(uint) ] = ": this is the fake signature of transaction number "; const char WORK_PROGRAM_ID[ FD_TXN_ACCT_ADDR_SZ ] = "Work Program Id Consumes 1< + + + + + + + + + + + + + + @@ -133,4 +147,33 @@ metric introduced. + + + Duration of scheduling one microblock + + + Duration of inserting one transaction into the pool of available transactions + + + Count of transactions in a scheduled microblock, including both votes and non-votes + + + Count of simple vote transactions in a scheduled microblock + + + + + + + + + + + + + + + + +