diff --git a/book/api/metrics-generated.md b/book/api/metrics-generated.md index d4e1d597b8c..63b286b5cb0 100644 --- a/book/api/metrics-generated.md +++ b/book/api/metrics-generated.md @@ -509,6 +509,7 @@ | pack_​txn_​pending_​used
{pending_​txn_​type="vote"} | gauge | Pending transactions in pack's pool, by type (Deprecated, always 0 since Firedancer v26.11.) | | pack_​txn_​pending_​used
{pending_​txn_​type="conflicting"} | gauge | Pending transactions in pack's pool, by type (Transactions that write to a hotly-contended account) | | pack_​txn_​pending_​used
{pending_​txn_​type="bundle"} | gauge | Pending transactions in pack's pool, by type (Transactions that are part of a bundle) | +| pack_​txn_​pending_​used
{pending_​txn_​type="next_​auction"} | gauge | Pending transactions in pack's pool, by type (Transactions that are currently held waiting for the next auction) | | pack_​txn_​pending_​capacity | gauge | Maximum pending transactions pack can hold | | pack_​txn_​pending_​smallest_​cost | gauge | Lower bound on the cost of the cheapest pending transaction, 0 if none pending | | pack_​block_​microblock_​limit_​reached | counter | Times pack did not schedule a microblock because the block microblock limit was reached | @@ -530,6 +531,13 @@ | pack_​pre_​rotation_​bundle_​rejected | counter | Bundles that arrived while not leader that pack dropped because it was full of ones that declare at least as large a tip | | pack_​pre_​rotation_​bundle_​evicted | counter | Bundles that arrived while not leader that pack dropped for one that declares a larger tip | | pack_​pre_​rotation_​bundle_​scheduled | counter | Bundles that arrived while not leader that pack scheduled | +| pack_​auction_​started | counter | Batch auctions started | +| pack_​auction_​cancelled | counter | Batch auctions skipped because the block was already at their CU limit | +| pack_​auction_​ended
{pack_​auction_​end_​reason="limit"} | counter | Batch auctions ended, by reason (An auction ended because the block reached the auction's CU limit) | +| pack_​auction_​ended
{pack_​auction_​end_​reason="restarted"} | counter | Batch auctions ended, by reason (An auction ended because a new auction was started) | +| pack_​auction_​ended
{pack_​auction_​end_​reason="final"} | counter | Batch auctions ended, by reason (An auction ended because the final 20ms of our leader rotation began) | +| pack_​auction_​ended
{pack_​auction_​end_​reason="slot_​end"} | counter | Batch auctions ended, by reason (An auction ended because the leader slot ended) | +| pack_​auction_​duration_​nanos | histogram | Duration that batch auctions ran for | | pack_​block_​cost | gauge | Cost units used in the current block, 0 if not packing a block | | pack_​cost_​scheduled_​per_​block | histogram | Cost units scheduled for each block pack produced; can exceed the block limit because of rebates | | pack_​cu_​rebated_​per_​block | histogram | Compute units rebated for each block pack produced. Compute units are rebated when a transaction fails prior to execution or requests more compute units than it uses | diff --git a/src/app/firedancer/config/default.toml b/src/app/firedancer/config/default.toml index 8a4105fdd1a..df77389e57e 100644 --- a/src/app/firedancer/config/default.toml +++ b/src/app/firedancer/config/default.toml @@ -1721,7 +1721,7 @@ telemetry = true # Over time, as the network gets faster and compute unit limits # are raised, this trade-off will become less pronounced. # - # The Firedancer team supports two different strategies which + # The Firedancer team supports three different strategies which # are described below: # # "perf" @@ -1742,6 +1742,10 @@ telemetry = true # When using the balanced mode, there is no reason to increase # execle tile count beyond the default of 2. # + # "auction" + # Schedule transactions in 50ms batch auctions. Bundles are + # not included in the auctions. + # # In cases of network congestion, high volume, or if seeing poor # block packing performance for any reason, it is recommended to # revert to the "balanced" strategy. diff --git a/src/app/shared/fd_config.c b/src/app/shared/fd_config.c index 48c9ee93d3d..2bd3e2fb14a 100644 --- a/src/app/shared/fd_config.c +++ b/src/app/shared/fd_config.c @@ -399,6 +399,7 @@ fd_config_fill( fd_config_t * config, else if( FD_LIKELY( !strcmp( config->tiles.pack.schedule_strategy, "revenue" ) ) ) { FD_LOG_ERR(( "the revenue scheduler has been removed. Please update [tiles.pack.schedule_strategy]" )); } + else if( FD_LIKELY( !strcmp( config->tiles.pack.schedule_strategy, "auction" ) ) ) config->tiles.pack.schedule_strategy_enum = 2; else FD_LOG_ERR(( "[tiles.pack.schedule_strategy] %s not recognized", config->tiles.pack.schedule_strategy )); fd_config_fill_net( config ); diff --git a/src/disco/events/schema/events.proto b/src/disco/events/schema/events.proto index 228e7c62f7b..cc8db0fca4d 100644 --- a/src/disco/events/schema/events.proto +++ b/src/disco/events/schema/events.proto @@ -2286,6 +2286,56 @@ message MetricsResolvRegimeDurationNanos { uint64 backpressure_sleeping = 10; } +// Duration that batch auctions ran for +message MetricsPackAuctionDurationNanos { + // [0, 10000) nanos + uint64 b0 = 1; + // [10000, 19307) nanos + uint64 b1 = 2; + // [19307, 37276) nanos + uint64 b2 = 3; + // [37276, 71969) nanos + uint64 b3 = 4; + // [71969, 138950) nanos + uint64 b4 = 5; + // [138950, 268270) nanos + uint64 b5 = 6; + // [268270, 517948) nanos + uint64 b6 = 7; + // [517948, 1000001) nanos + uint64 b7 = 8; + // [1000001, 1930699) nanos + uint64 b8 = 9; + // [1930699, 3727596) nanos + uint64 b9 = 10; + // [3727596, 7196860) nanos + uint64 b10 = 11; + // [7196860, 13894960) nanos + uint64 b11 = 12; + // [13894960, 26826964) nanos + uint64 b12 = 13; + // [26826964, 51794753) nanos + uint64 b13 = 14; + // [51794753, 100000000) nanos + uint64 b14 = 15; + // [100000000, inf) nanos + uint64 b15 = 16; + // Sum of samples (nanos) + uint64 sum = 17; +} + +// Batch auctions ended, by reason +message MetricsPackAuctionEnded { + // An auction ended because the block reached the auction's CU limit + uint64 limit = 1; + // An auction ended because a new auction was started + uint64 restarted = 2; + // An auction ended because the final 20ms of our leader rotation began + uint64 final = 3; + // An auction ended because the leader slot ended + uint64 slot_end = 4; +} + // Percent of max_cost_per_block used by each block pack produced message MetricsPackCostPerBlockPct { // [0, 1) pct @@ -2480,6 +2530,8 @@ message MetricsPackTxnPendingUsed { uint64 conflicting = 3; // Transactions that are part of a bundle uint64 bundle = 4; + // Transactions that are currently held waiting for the next auction + uint64 next_auction = 5; } // Transactions dropped from pack after insertion, by reason @@ -9708,6 +9760,14 @@ message MetricsPack { uint64 pre_rotation_bundle_evicted = 51; // Bundles that arrived while not leader that pack scheduled uint64 pre_rotation_bundle_scheduled = 52; + // Batch auctions started + uint64 auction_started = 53; + // Batch auctions skipped because the block was already at their CU limit + uint64 auction_cancelled = 54; + // Batch auctions ended, by reason + MetricsPackAuctionEnded auction_ended = 55; + // Duration that batch auctions ran for + MetricsPackAuctionDurationNanos auction_duration_nanos = 56; } // Metrics of one execle tile instance, one row per sweep diff --git a/src/disco/events/schema/metrics_pack.json b/src/disco/events/schema/metrics_pack.json index 68bb27ac69d..e5890d521f3 100644 --- a/src/disco/events/schema/metrics_pack.json +++ b/src/disco/events/schema/metrics_pack.json @@ -304,10 +304,11 @@ "type": "Tuple", "description": "Pending transactions in pack's pool, by type", "fields": { - "regular": { "type": "UInt64", "compression": "T64, ZSTD", "description": "Transactions in the main treap" }, - "vote": { "type": "UInt64", "compression": "T64, ZSTD", "description": "Deprecated, always 0 since Firedancer v26.11." }, - "conflicting": { "type": "UInt64", "compression": "T64, ZSTD", "description": "Transactions that write to a hotly-contended account" }, - "bundle": { "type": "UInt64", "compression": "T64, ZSTD", "description": "Transactions that are part of a bundle" } + "regular": { "type": "UInt64", "compression": "T64, ZSTD", "description": "Transactions in the main treap" }, + "vote": { "type": "UInt64", "compression": "T64, ZSTD", "description": "Deprecated, always 0 since Firedancer v26.11." }, + "conflicting": { "type": "UInt64", "compression": "T64, ZSTD", "description": "Transactions that write to a hotly-contended account" }, + "bundle": { "type": "UInt64", "compression": "T64, ZSTD", "description": "Transactions that are part of a bundle" }, + "next_auction": { "type": "UInt64", "compression": "T64, ZSTD", "description": "Transactions that are currently held waiting for the next auction" } } }, "txn_pending_capacity": { "type": "UInt64", "compression": "T64, ZSTD", "description": "Maximum pending transactions pack can hold" }, @@ -441,6 +442,44 @@ "pre_rotation_bundle_inserted": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "Bundles that arrived while not leader that pack kept" }, "pre_rotation_bundle_rejected": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "Bundles that arrived while not leader that pack dropped because it was full of ones that declare at least as large a tip" }, "pre_rotation_bundle_evicted": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "Bundles that arrived while not leader that pack dropped for one that declares a larger tip" }, - "pre_rotation_bundle_scheduled": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "Bundles that arrived while not leader that pack scheduled" } + "pre_rotation_bundle_scheduled": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "Bundles that arrived while not leader that pack scheduled" }, + "auction_started": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "Batch auctions started" }, + "auction_cancelled": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "Batch auctions skipped because the block was already at their CU limit" }, + "auction_ended": { + "type": "Tuple", + "description": "Batch auctions ended, by reason", + "aliases": { + "all": ["limit", "restarted", "final", "slot_end"] + }, + "fields": { + "limit": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "An auction ended because the block reached the auction's CU limit" }, + "restarted": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "An auction ended because a new auction was started" }, + "final": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "An auction ended because the final 20ms of our leader rotation began" }, + "slot_end": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "An auction ended because the leader slot ended" } + } + }, + "auction_duration_nanos": { + "type": "Tuple", + "description": "Duration that batch auctions ran for", + "fields": { + "b0": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "[0, 10000) nanos" }, + "b1": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "[10000, 19307) nanos" }, + "b2": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "[19307, 37276) nanos" }, + "b3": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "[37276, 71969) nanos" }, + "b4": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "[71969, 138950) nanos" }, + "b5": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "[138950, 268270) nanos" }, + "b6": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "[268270, 517948) nanos" }, + "b7": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "[517948, 1000001) nanos" }, + "b8": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "[1000001, 1930699) nanos" }, + "b9": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "[1930699, 3727596) nanos" }, + "b10": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "[3727596, 7196860) nanos" }, + "b11": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "[7196860, 13894960) nanos" }, + "b12": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "[13894960, 26826964) nanos" }, + "b13": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "[26826964, 51794753) nanos" }, + "b14": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "[51794753, 100000000) nanos" }, + "b15": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "[100000000, inf) nanos" }, + "sum": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "Sum of samples (nanos)" } + } + } } } diff --git a/src/disco/gui/fd_gui_printf.c b/src/disco/gui/fd_gui_printf.c index 7cc8fa61e7e..cbb272c27a9 100644 --- a/src/disco/gui/fd_gui_printf.c +++ b/src/disco/gui/fd_gui_printf.c @@ -684,7 +684,7 @@ fd_gui_printf_schedule_strategy( fd_gui_t * gui ) { switch (gui->summary.schedule_strategy) { case 0: fd_cstr_ncpy( mode, "perf", sizeof(mode) ); break; case 1: fd_cstr_ncpy( mode, "balanced", sizeof(mode) ); break; - case 2: fd_cstr_ncpy( mode, "revenue", sizeof(mode) ); break; + case 2: fd_cstr_ncpy( mode, "auction", sizeof(mode) ); break; default: FD_LOG_ERR(("unexpected schedule_strategy %d", gui->summary.schedule_strategy)); } jsonp_string( gui->http, "value", mode ); diff --git a/src/disco/metrics/generated/fd_metrics_all.c b/src/disco/metrics/generated/fd_metrics_all.c index 431842bcea4..230940fa1a3 100644 --- a/src/disco/metrics/generated/fd_metrics_all.c +++ b/src/disco/metrics/generated/fd_metrics_all.c @@ -397,6 +397,7 @@ const fd_metrics_meta_t FD_METRICS_PACK[FD_METRICS_PACK_TOTAL] = { DECLARE_METRIC_ENUM( PACK_TXN_PENDING_USED, GAUGE, PENDING_TXN_TYPE, VOTE ), DECLARE_METRIC_ENUM( PACK_TXN_PENDING_USED, GAUGE, PENDING_TXN_TYPE, CONFLICTING ), DECLARE_METRIC_ENUM( PACK_TXN_PENDING_USED, GAUGE, PENDING_TXN_TYPE, BUNDLE ), + DECLARE_METRIC_ENUM( PACK_TXN_PENDING_USED, GAUGE, PENDING_TXN_TYPE, NEXT_AUCTION ), DECLARE_METRIC( PACK_TXN_PENDING_CAPACITY, GAUGE ), DECLARE_METRIC( PACK_TXN_PENDING_SMALLEST_COST, GAUGE ), DECLARE_METRIC( PACK_BLOCK_MICROBLOCK_LIMIT_REACHED, COUNTER ), @@ -418,6 +419,13 @@ const fd_metrics_meta_t FD_METRICS_PACK[FD_METRICS_PACK_TOTAL] = { DECLARE_METRIC( PACK_PRE_ROTATION_BUNDLE_REJECTED, COUNTER ), DECLARE_METRIC( PACK_PRE_ROTATION_BUNDLE_EVICTED, COUNTER ), DECLARE_METRIC( PACK_PRE_ROTATION_BUNDLE_SCHEDULED, COUNTER ), + DECLARE_METRIC( PACK_AUCTION_STARTED, COUNTER ), + DECLARE_METRIC( PACK_AUCTION_CANCELLED, COUNTER ), + DECLARE_METRIC_ENUM( PACK_AUCTION_ENDED, COUNTER, PACK_AUCTION_END_REASON, LIMIT ), + DECLARE_METRIC_ENUM( PACK_AUCTION_ENDED, COUNTER, PACK_AUCTION_END_REASON, RESTARTED ), + DECLARE_METRIC_ENUM( PACK_AUCTION_ENDED, COUNTER, PACK_AUCTION_END_REASON, FINAL ), + DECLARE_METRIC_ENUM( PACK_AUCTION_ENDED, COUNTER, PACK_AUCTION_END_REASON, SLOT_END ), + DECLARE_METRIC_HISTOGRAM_TICKS( PACK_AUCTION_DURATION_NANOS ), DECLARE_METRIC( PACK_BLOCK_COST, GAUGE ), DECLARE_METRIC_HISTOGRAM_NONE( PACK_COST_SCHEDULED_PER_BLOCK ), DECLARE_METRIC_HISTOGRAM_NONE( PACK_CU_REBATED_PER_BLOCK ), diff --git a/src/disco/metrics/generated/fd_metrics_enums.h b/src/disco/metrics/generated/fd_metrics_enums.h index a903d41ce93..9cbeb335d55 100644 --- a/src/disco/metrics/generated/fd_metrics_enums.h +++ b/src/disco/metrics/generated/fd_metrics_enums.h @@ -485,7 +485,7 @@ #define FD_METRICS_ENUM_PACK_SCHEDULE_REGIME_G_LEADER_NO_MICROBLOCK_MASK (0xf0UL) #define FD_METRICS_ENUM_PENDING_TXN_TYPE_NAME "pending_txn_type" -#define FD_METRICS_ENUM_PENDING_TXN_TYPE_CNT (4UL) +#define FD_METRICS_ENUM_PENDING_TXN_TYPE_CNT (5UL) #define FD_METRICS_ENUM_PENDING_TXN_TYPE_V_REGULAR_IDX 0 #define FD_METRICS_ENUM_PENDING_TXN_TYPE_V_REGULAR_NAME "regular" #define FD_METRICS_ENUM_PENDING_TXN_TYPE_V_VOTE_IDX 1 @@ -494,7 +494,9 @@ #define FD_METRICS_ENUM_PENDING_TXN_TYPE_V_CONFLICTING_NAME "conflicting" #define FD_METRICS_ENUM_PENDING_TXN_TYPE_V_BUNDLE_IDX 3 #define FD_METRICS_ENUM_PENDING_TXN_TYPE_V_BUNDLE_NAME "bundle" -#define FD_METRICS_ENUM_PENDING_TXN_TYPE_G_ALL_MASK (0xfUL) +#define FD_METRICS_ENUM_PENDING_TXN_TYPE_V_NEXT_AUCTION_IDX 4 +#define FD_METRICS_ENUM_PENDING_TXN_TYPE_V_NEXT_AUCTION_NAME "next_auction" +#define FD_METRICS_ENUM_PENDING_TXN_TYPE_G_ALL_MASK (0x1fUL) #define FD_METRICS_ENUM_BUNDLE_CRANK_RESULT_NAME "bundle_crank_result" #define FD_METRICS_ENUM_BUNDLE_CRANK_RESULT_CNT (4UL) @@ -508,6 +510,18 @@ #define FD_METRICS_ENUM_BUNDLE_CRANK_RESULT_V_INSERTION_FAILED_NAME "insertion_failed" #define FD_METRICS_ENUM_BUNDLE_CRANK_RESULT_G_ALL_MASK (0xfUL) +#define FD_METRICS_ENUM_PACK_AUCTION_END_REASON_NAME "pack_auction_end_reason" +#define FD_METRICS_ENUM_PACK_AUCTION_END_REASON_CNT (4UL) +#define FD_METRICS_ENUM_PACK_AUCTION_END_REASON_V_LIMIT_IDX 0 +#define FD_METRICS_ENUM_PACK_AUCTION_END_REASON_V_LIMIT_NAME "limit" +#define FD_METRICS_ENUM_PACK_AUCTION_END_REASON_V_RESTARTED_IDX 1 +#define FD_METRICS_ENUM_PACK_AUCTION_END_REASON_V_RESTARTED_NAME "restarted" +#define FD_METRICS_ENUM_PACK_AUCTION_END_REASON_V_FINAL_IDX 2 +#define FD_METRICS_ENUM_PACK_AUCTION_END_REASON_V_FINAL_NAME "final" +#define FD_METRICS_ENUM_PACK_AUCTION_END_REASON_V_SLOT_END_IDX 3 +#define FD_METRICS_ENUM_PACK_AUCTION_END_REASON_V_SLOT_END_NAME "slot_end" +#define FD_METRICS_ENUM_PACK_AUCTION_END_REASON_G_ALL_MASK (0xfUL) + #define FD_METRICS_ENUM_TXN_RESULT_NAME "txn_result" #define FD_METRICS_ENUM_TXN_RESULT_CNT (28UL) #define FD_METRICS_ENUM_TXN_RESULT_V_SUCCESS_IDX 0 diff --git a/src/disco/metrics/generated/fd_metrics_pack.h b/src/disco/metrics/generated/fd_metrics_pack.h index fc055fe5c41..a307aab8512 100644 --- a/src/disco/metrics/generated/fd_metrics_pack.h +++ b/src/disco/metrics/generated/fd_metrics_pack.h @@ -69,6 +69,7 @@ enum { FD_METRICS_GAUGE_PACK_TXN_PENDING_USED_VOTE_OFF, FD_METRICS_GAUGE_PACK_TXN_PENDING_USED_CONFLICTING_OFF, FD_METRICS_GAUGE_PACK_TXN_PENDING_USED_BUNDLE_OFF, + FD_METRICS_GAUGE_PACK_TXN_PENDING_USED_NEXT_AUCTION_OFF, FD_METRICS_GAUGE_PACK_TXN_PENDING_CAPACITY_OFF, FD_METRICS_GAUGE_PACK_TXN_PENDING_SMALLEST_COST_OFF, FD_METRICS_COUNTER_PACK_BLOCK_MICROBLOCK_LIMIT_REACHED_OFF, @@ -92,6 +93,15 @@ enum { FD_METRICS_COUNTER_PACK_PRE_ROTATION_BUNDLE_REJECTED_OFF, FD_METRICS_COUNTER_PACK_PRE_ROTATION_BUNDLE_EVICTED_OFF, FD_METRICS_COUNTER_PACK_PRE_ROTATION_BUNDLE_SCHEDULED_OFF, + FD_METRICS_COUNTER_PACK_AUCTION_STARTED_OFF, + FD_METRICS_COUNTER_PACK_AUCTION_CANCELLED_OFF, + FD_METRICS_COUNTER_PACK_AUCTION_ENDED_OFF, + FD_METRICS_COUNTER_PACK_AUCTION_ENDED_LIMIT_OFF = FD_METRICS_COUNTER_PACK_AUCTION_ENDED_OFF, + FD_METRICS_COUNTER_PACK_AUCTION_ENDED_RESTARTED_OFF, + FD_METRICS_COUNTER_PACK_AUCTION_ENDED_FINAL_OFF, + FD_METRICS_COUNTER_PACK_AUCTION_ENDED_SLOT_END_OFF, + FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF, + FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF_END = FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF + 16, FD_METRICS_GAUGE_PACK_BLOCK_COST_OFF, FD_METRICS_HISTOGRAM_PACK_COST_SCHEDULED_PER_BLOCK_OFF, FD_METRICS_HISTOGRAM_PACK_COST_SCHEDULED_PER_BLOCK_OFF_END = FD_METRICS_HISTOGRAM_PACK_COST_SCHEDULED_PER_BLOCK_OFF + 16, @@ -166,7 +176,7 @@ enum { #define FD_METRICS_GAUGE_PACK_TXN_PENDING_USED_TYPE (FD_METRICS_TYPE_GAUGE) #define FD_METRICS_GAUGE_PACK_TXN_PENDING_USED_DESC "Pending transactions in pack's pool, by type" #define FD_METRICS_GAUGE_PACK_TXN_PENDING_USED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_GAUGE_PACK_TXN_PENDING_USED_CNT (4UL) +#define FD_METRICS_GAUGE_PACK_TXN_PENDING_USED_CNT (5UL) #define FD_METRICS_GAUGE_PACK_TXN_PENDING_CAPACITY_NAME "pack_txn_pending_capacity" #define FD_METRICS_GAUGE_PACK_TXN_PENDING_CAPACITY_TYPE (FD_METRICS_TYPE_GAUGE) @@ -225,6 +235,28 @@ enum { #define FD_METRICS_COUNTER_PACK_PRE_ROTATION_BUNDLE_SCHEDULED_DESC "Bundles that arrived while not leader that pack scheduled" #define FD_METRICS_COUNTER_PACK_PRE_ROTATION_BUNDLE_SCHEDULED_CVT (FD_METRICS_CONVERTER_NONE) +#define FD_METRICS_COUNTER_PACK_AUCTION_STARTED_NAME "pack_auction_started" +#define FD_METRICS_COUNTER_PACK_AUCTION_STARTED_TYPE (FD_METRICS_TYPE_COUNTER) +#define FD_METRICS_COUNTER_PACK_AUCTION_STARTED_DESC "Batch auctions started" +#define FD_METRICS_COUNTER_PACK_AUCTION_STARTED_CVT (FD_METRICS_CONVERTER_NONE) + +#define FD_METRICS_COUNTER_PACK_AUCTION_CANCELLED_NAME "pack_auction_cancelled" +#define FD_METRICS_COUNTER_PACK_AUCTION_CANCELLED_TYPE (FD_METRICS_TYPE_COUNTER) +#define FD_METRICS_COUNTER_PACK_AUCTION_CANCELLED_DESC "Batch auctions skipped because the block was already at their CU limit" +#define FD_METRICS_COUNTER_PACK_AUCTION_CANCELLED_CVT (FD_METRICS_CONVERTER_NONE) + +#define FD_METRICS_COUNTER_PACK_AUCTION_ENDED_NAME "pack_auction_ended" +#define FD_METRICS_COUNTER_PACK_AUCTION_ENDED_TYPE (FD_METRICS_TYPE_COUNTER) +#define FD_METRICS_COUNTER_PACK_AUCTION_ENDED_DESC "Batch auctions ended, by reason" +#define FD_METRICS_COUNTER_PACK_AUCTION_ENDED_CVT (FD_METRICS_CONVERTER_NONE) +#define FD_METRICS_COUNTER_PACK_AUCTION_ENDED_CNT (4UL) + +#define FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_NAME "pack_auction_duration_nanos" +#define FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_TYPE (FD_METRICS_TYPE_HISTOGRAM) +#define FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_DESC "Duration that batch auctions ran for" +#define FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_CVT (FD_METRICS_CONVERTER_NANOSECONDS) +#define FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_EDGES_NS { 10000UL, 19307UL, 37276UL, 71969UL, 138950UL, 268270UL, 517948UL, 1000001UL, 1930699UL, 3727596UL, 7196860UL, 13894960UL, 26826964UL, 51794753UL, 100000000UL } + #define FD_METRICS_GAUGE_PACK_BLOCK_COST_NAME "pack_block_cost" #define FD_METRICS_GAUGE_PACK_BLOCK_COST_TYPE (FD_METRICS_TYPE_GAUGE) #define FD_METRICS_GAUGE_PACK_BLOCK_COST_DESC "Cost units used in the current block, 0 if not packing a block" @@ -258,7 +290,7 @@ enum { #define FD_METRICS_HISTOGRAM_PACK_COST_PER_BLOCK_PCT_MIN (0UL) #define FD_METRICS_HISTOGRAM_PACK_COST_PER_BLOCK_PCT_MAX (100UL) -#define FD_METRICS_PACK_TOTAL (76UL) +#define FD_METRICS_PACK_TOTAL (84UL) extern const fd_metrics_meta_t FD_METRICS_PACK[FD_METRICS_PACK_TOTAL]; #endif /* HEADER_fd_src_disco_metrics_generated_fd_metrics_pack_h */ diff --git a/src/disco/metrics/generated/fd_metrics_sample_gen.c b/src/disco/metrics/generated/fd_metrics_sample_gen.c index 62c2c235b87..ce26bd229ff 100644 --- a/src/disco/metrics/generated/fd_metrics_sample_gen.c +++ b/src/disco/metrics/generated/fd_metrics_sample_gen.c @@ -722,7 +722,7 @@ static fd_metrics_sample_field_t const fields_resolv[ 81 ] = { { FD_METRICS_COUNTER_RESOLV_ACCDB_COPIED_BYTES_OFF, 34U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, }; -static fd_metrics_sample_field_t const fields_pack[ 288 ] = { +static fd_metrics_sample_field_t const fields_pack[ 316 ] = { { FD_METRICS_GAUGE_TILE_PID_OFF, 8U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_GAUGE_TILE_TID_OFF, 9U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_GAUGE_TILE_LAST_CPU_INDEX_OFF, 10U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, @@ -908,6 +908,7 @@ static fd_metrics_sample_field_t const fields_pack[ 288 ] = { { FD_METRICS_GAUGE_PACK_TXN_PENDING_USED_VOTE_OFF, 2U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_GAUGE_PACK_TXN_PENDING_USED_CONFLICTING_OFF, 3U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_GAUGE_PACK_TXN_PENDING_USED_BUNDLE_OFF, 4U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_GAUGE_PACK_TXN_PENDING_USED_NEXT_AUCTION_OFF, 5U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { 0, 0U, FD_METRICS_SAMPLE_CLOSE, NULL, 0UL }, { FD_METRICS_GAUGE_PACK_TXN_PENDING_CAPACITY_OFF, 37U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_GAUGE_PACK_TXN_PENDING_SMALLEST_COST_OFF, 38U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, @@ -934,6 +935,33 @@ static fd_metrics_sample_field_t const fields_pack[ 288 ] = { { FD_METRICS_COUNTER_PACK_PRE_ROTATION_BUNDLE_REJECTED_OFF, 50U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_COUNTER_PACK_PRE_ROTATION_BUNDLE_EVICTED_OFF, 51U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_COUNTER_PACK_PRE_ROTATION_BUNDLE_SCHEDULED_OFF, 52U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_COUNTER_PACK_AUCTION_STARTED_OFF, 53U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_COUNTER_PACK_AUCTION_CANCELLED_OFF, 54U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_COUNTER_PACK_AUCTION_ENDED_OFF, 55U, FD_METRICS_SAMPLE_OPEN, NULL, 0UL }, + { FD_METRICS_COUNTER_PACK_AUCTION_ENDED_LIMIT_OFF, 1U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_COUNTER_PACK_AUCTION_ENDED_RESTARTED_OFF, 2U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_COUNTER_PACK_AUCTION_ENDED_FINAL_OFF, 3U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_COUNTER_PACK_AUCTION_ENDED_SLOT_END_OFF, 4U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { 0, 0U, FD_METRICS_SAMPLE_CLOSE, NULL, 0UL }, + { FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF, 56U, FD_METRICS_SAMPLE_OPEN, NULL, 0UL }, + { FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF+0, 1U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF+1, 2U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF+2, 3U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF+3, 4U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF+4, 5U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF+5, 6U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF+6, 7U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF+7, 8U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF+8, 9U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF+9, 10U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF+10, 11U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF+11, 12U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF+12, 13U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF+13, 14U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF+14, 15U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF+15, 16U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, + { FD_METRICS_HISTOGRAM_PACK_AUCTION_DURATION_NANOS_OFF+16, 17U, FD_METRICS_SAMPLE_NS, NULL, 0UL }, + { 0, 0U, FD_METRICS_SAMPLE_CLOSE, NULL, 0UL }, { FD_METRICS_GAUGE_PACK_BLOCK_COST_OFF, 43U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_HISTOGRAM_PACK_COST_SCHEDULED_PER_BLOCK_OFF, 44U, FD_METRICS_SAMPLE_OPEN, NULL, 0UL }, { FD_METRICS_HISTOGRAM_PACK_COST_SCHEDULED_PER_BLOCK_OFF+0, 1U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, @@ -4487,7 +4515,7 @@ fd_metrics_sample_schema_t const fd_metrics_sample_tiles[] = { { "verify", 27U, fields_verify, 45UL, 574UL }, { "dedup", 28U, fields_dedup, 43UL, 552UL }, { "resolv", 29U, fields_resolv, 81UL, 915UL }, - { "pack", 30U, fields_pack, 288UL, 3073UL }, + { "pack", 30U, fields_pack, 316UL, 3355UL }, { "execle", 31U, fields_execle, 247UL, 2599UL }, { "shred", 32U, fields_shred, 177UL, 1962UL }, { "accdb", 33U, fields_accdb, 132UL, 1361UL }, diff --git a/src/disco/metrics/metrics.xml b/src/disco/metrics/metrics.xml index 9ea010b2fee..c872dbc336a 100644 --- a/src/disco/metrics/metrics.xml +++ b/src/disco/metrics/metrics.xml @@ -613,6 +613,7 @@ deliberate exception with lint-allow="") + @@ -622,6 +623,13 @@ deliberate exception with lint-allow="") + + + + + + + Duration of scheduling one microblock @@ -664,6 +672,12 @@ deliberate exception with lint-allow="") + + + + + Duration that batch auctions ran for + diff --git a/src/disco/pack/fd_pack.c b/src/disco/pack/fd_pack.c index bea6322f2d6..212de10f5b6 100644 --- a/src/disco/pack/fd_pack.c +++ b/src/disco/pack/fd_pack.c @@ -129,6 +129,7 @@ FD_STATIC_ASSERT( offsetof( fd_pack_ord_txn_t, txn_e->txnp )==0UL, fd_pack_ord_ #define FD_ORD_TXN_ROOT_PENDING_BUNDLE 2 #define FD_ORD_TXN_ROOT_PENALTY( idx ) (3 | (idx)<<8) #define FD_ORD_TXN_ROOT_PRE_ROTATION_BUNDLE( idx ) (4 | (idx)<<8) +#define FD_ORD_TXN_ROOT_NEXT_AUCTION 5 /* if root & TAG_MASK == PENALTY, then PENALTY_ACCT_IDX(root) gives the index in the transaction's list of account addresses of which penalty treap the @@ -644,6 +645,13 @@ struct fd_pack_private { ulong pre_rotation_seq; long rotation_end_ns; + /* If an auction is running, new transactions are held in + next_auction - they are not eligible to be scheduled. When the + auction ends, these are flushed into either the pending or penalty + treaps. */ + treap_t next_auction[1]; + int auction_running; + /* penalty_treaps: an fd_map_dynamic mapping hotly contended account addresses to treaps of transactions that write to them. We try not to allow more than roughly PENALTY_TREAP_THRESHOLD transactions in @@ -942,6 +950,7 @@ fd_pack_new( void * mem, but they come from a pool of size pack_depth+extra_depth. */ treap_new( (void*)pack->pending, pack_depth+extra_depth ); treap_new( (void*)pack->pending_bundles, pack_depth+extra_depth ); + treap_new( (void*)pack->next_auction, pack_depth+extra_depth ); for( ulong i=0UL; ipre_rotation_bundles[ i ].treap, pack_depth+extra_depth ); } @@ -949,6 +958,7 @@ fd_pack_new( void * mem, pack->pre_rotation_bundle_cnt = 0UL; pack->pre_rotation_seq = 0UL; pack->rotation_end_ns = 0L; + pack->auction_running = 0; pack->pending_smallest->cus = ULONG_MAX; pack->pending_smallest->bytes = ULONG_MAX; @@ -1247,6 +1257,7 @@ delete_worst( fd_pack_t * pack, Treap with N transactions Scale Factor Pending 1.0 + Next auction same as pending Penalty treap 1.0 at <= 100 transactions, then sqrt(100/N) Pending bundles inf (since the rewards value is fudged) @@ -1284,6 +1295,11 @@ delete_worst( fd_pack_t * pack, multiplier = 1.0f; break; } + case FD_ORD_TXN_ROOT_NEXT_AUCTION: { + treap = pack->next_auction; + multiplier = 1.0f; + break; + } case FD_ORD_TXN_ROOT_PENDING_BUNDLE: { /* We don't have a way to tell how much these actually pay in rewards, so we just assume they are very high. */ @@ -1489,6 +1505,58 @@ populate_bitsets( fd_pack_t * pack, return cumulative_penalty; } +static void +insert_txn( fd_pack_t * pack, + fd_pack_ord_txn_t * ord ) { + /* If an auction is running, all incoming transactions should be + held in next_auction until the auction has ended. */ + if( pack->auction_running ) { + ord->root = FD_ORD_TXN_ROOT_NEXT_AUCTION; + treap_ele_insert( pack->next_auction, ord, pack->pool ); + return; + } + + fd_txn_t * txn = TXN( ord->txn ); + fd_acct_addr_t const * accts = fd_txn_get_acct_addrs( txn, ord->txn->payload ); + fd_acct_addr_t const * alt_adj = ord->txn_e->alt_accts - fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_IMM ); + + /* Since the pool uses ushorts, the size of the pool is < USHORT_MAX. + Each transaction can reference an account at most once, which means + that the total number of references for an account is < USHORT_MAX. + If these were ulongs, the array would be 512B, which is kind of a + lot to zero out.*/ + ushort penalties[ FD_TXN_ACCT_ADDR_MAX ] = {0}; + uchar penalty_idx[ FD_TXN_ACCT_ADDR_MAX ]; + ulong cumulative_penalty = populate_bitsets( pack, ord, penalties, penalty_idx ); + + treap_t * insert_into = pack->pending; + + if( FD_UNLIKELY( cumulative_penalty ) ) { /* Optimize for high parallelism case */ + /* Compute a weighted random choice */ + ulong roll = (ulong)fd_rng_uint_roll( pack->rng, (uint)cumulative_penalty ); /* cumulative_penalty < USHORT_MAX*64 < UINT_MAX */ + ulong i = 0UL; + /* Find the right one. This can be done in O(log N), but I imagine + N is normally so small that doesn't matter. */ + while( roll>=penalties[i] ) roll -= (ulong)penalties[i++]; + + fd_acct_addr_t penalty_acct = *ACCT_IDX_TO_PTR( penalty_idx[i] ); + fd_pack_penalty_treap_t * q = penalty_map_query( pack->penalty_treaps, penalty_acct, NULL ); + if( FD_UNLIKELY( q==NULL ) ) { + q = penalty_map_insert( pack->penalty_treaps, penalty_acct ); + treap_new( q->penalty_treap, trp_pool_max( pack->pool ) ); + } + insert_into = q->penalty_treap; + ord->root = FD_ORD_TXN_ROOT_PENALTY( penalty_idx[i] ); + } else { + ord->root = FD_ORD_TXN_ROOT_PENDING; + + pack->pending_smallest->cus = fd_ulong_min( pack->pending_smallest->cus, ord->compute_est ); + pack->pending_smallest->bytes = fd_ulong_min( pack->pending_smallest->bytes, ord->txn->payload_sz ); + } + + treap_ele_insert( insert_into, ord, pack->pool ); +} + int fd_pack_insert_txn_fini( fd_pack_t * pack, fd_txn_e_t * txne, @@ -1561,39 +1629,7 @@ fd_pack_insert_txn_fini( fd_pack_t * pack, /* At this point, we know we have space to insert the transaction and we've committed to insert it. */ - /* Since the pool uses ushorts, the size of the pool is < USHORT_MAX. - Each transaction can reference an account at most once, which means - that the total number of references for an account is < USHORT_MAX. - If these were ulongs, the array would be 512B, which is kind of a - lot to zero out.*/ - ushort penalties[ FD_TXN_ACCT_ADDR_MAX ] = {0}; - uchar penalty_idx[ FD_TXN_ACCT_ADDR_MAX ]; - ulong cumulative_penalty = populate_bitsets( pack, ord, penalties, penalty_idx ); - - treap_t * insert_into = pack->pending; - - if( FD_UNLIKELY( cumulative_penalty ) ) { /* Optimize for high parallelism case */ - /* Compute a weighted random choice */ - ulong roll = (ulong)fd_rng_uint_roll( pack->rng, (uint)cumulative_penalty ); /* cumulative_penalty < USHORT_MAX*64 < UINT_MAX */ - ulong i = 0UL; - /* Find the right one. This can be done in O(log N), but I imagine - N is normally so small that doesn't matter. */ - while( roll>=penalties[i] ) roll -= (ulong)penalties[i++]; - - fd_acct_addr_t penalty_acct = *ACCT_IDX_TO_PTR( penalty_idx[i] ); - fd_pack_penalty_treap_t * q = penalty_map_query( pack->penalty_treaps, penalty_acct, NULL ); - if( FD_UNLIKELY( q==NULL ) ) { - q = penalty_map_insert( pack->penalty_treaps, penalty_acct ); - treap_new( q->penalty_treap, trp_pool_max( pack->pool ) ); - } - insert_into = q->penalty_treap; - ord->root = FD_ORD_TXN_ROOT_PENALTY( penalty_idx[i] ); - } else { - ord->root = FD_ORD_TXN_ROOT_PENDING; - - pack->pending_smallest->cus = fd_ulong_min( pack->pending_smallest->cus, ord->compute_est ); - pack->pending_smallest->bytes = fd_ulong_min( pack->pending_smallest->bytes, txne->txnp->payload_sz ); - } + insert_txn( pack, ord ); pack->pending_txn_cnt++; @@ -1604,7 +1640,6 @@ fd_pack_insert_txn_fini( fd_pack_t * pack, fd_pack_expq_t temp[ 1 ] = {{ .expires_at = expires_at, .txn = ord }}; expq_insert( pack->expiration_q, temp ); - treap_ele_insert( insert_into, ord, pack->pool ); return (replaces<<1) | (is_durable_nonce<<2); } #undef REJECT @@ -2137,15 +2172,17 @@ fd_pack_metrics_write( fd_pack_t const * pack ) { pending_bundle += treap_ele_cnt( pack->pre_rotation_bundles[ i ].treap ); } ulong pending_pre_rotation = pack->pre_rotation_bundle_cnt; - ulong conflicting = pack->pending_txn_cnt - pending_regular - pending_bundle; + ulong next_auction = treap_ele_cnt( pack->next_auction ); + ulong conflicting = pack->pending_txn_cnt - pending_regular - pending_bundle - next_auction; ulong smallest_cost = fd_ulong_if( pack->pending_smallest->cus==ULONG_MAX, 0UL, pack->pending_smallest->cus ); - FD_MGAUGE_SET( PACK, TXN_PENDING_USED_REGULAR, pending_regular ); - FD_MGAUGE_SET( PACK, TXN_PENDING_USED_VOTE, 0UL ); - FD_MGAUGE_SET( PACK, TXN_PENDING_USED_CONFLICTING, conflicting ); - FD_MGAUGE_SET( PACK, TXN_PENDING_USED_BUNDLE, pending_bundle ); - FD_MGAUGE_SET( PACK, PRE_ROTATION_BUNDLE_PENDING, pending_pre_rotation ); - FD_MGAUGE_SET( PACK, TXN_PENDING_SMALLEST_COST, smallest_cost ); - FD_MGAUGE_SET( PACK, BLOCK_COST, pack->cumulative_block_cost ); + FD_MGAUGE_SET( PACK, TXN_PENDING_USED_REGULAR, pending_regular ); + FD_MGAUGE_SET( PACK, TXN_PENDING_USED_VOTE, 0UL ); + FD_MGAUGE_SET( PACK, TXN_PENDING_USED_CONFLICTING, conflicting ); + FD_MGAUGE_SET( PACK, TXN_PENDING_USED_BUNDLE, pending_bundle ); + FD_MGAUGE_SET( PACK, TXN_PENDING_USED_NEXT_AUCTION, next_auction ); + FD_MGAUGE_SET( PACK, PRE_ROTATION_BUNDLE_PENDING, pending_pre_rotation ); + FD_MGAUGE_SET( PACK, TXN_PENDING_SMALLEST_COST, smallest_cost ); + FD_MGAUGE_SET( PACK, BLOCK_COST, pack->cumulative_block_cost ); FD_MCNT_ENUM_COPY( PACK, TXN_SCHEDULE_ATTEMPTED, pack->sched_results ); } @@ -2994,8 +3031,52 @@ fd_pack_schedule_next_microblock( fd_pack_t * pack, return scheduled; } -ulong fd_pack_bank_tile_cnt ( fd_pack_t const * pack ) { return pack->bank_tile_cnt; } -ulong fd_pack_current_block_cost( fd_pack_t const * pack ) { return pack->cumulative_block_cost; } +ulong fd_pack_bank_tile_cnt ( fd_pack_t const * pack ) { return pack->bank_tile_cnt; } +ulong fd_pack_current_block_cost ( fd_pack_t const * pack ) { return pack->cumulative_block_cost; } +ulong fd_pack_next_auction_txn_cnt( fd_pack_t const * pack ) { return treap_ele_cnt( pack->next_auction ); } +int fd_pack_auction_running ( fd_pack_t const * pack ) { return pack->auction_running; } + +/* fd_pack_auction_begin doesn't actually move any transactions + between treaps. It just sets the auction_running flag, which means + that new transactions enter the next_auction treap instead of the + pending/penalty treaps. These are not eligible for scheduling until + the auction ends, at which point the transactions in the + next_auction treap are moved into the pending/penalty treaps. + + Transactions already in the pending/penalty treaps, as well as all + new and already received bundles, are still elegible for scheduling + throughout the auction period. */ +void +fd_pack_auction_begin( fd_pack_t * pack ) { + pack->auction_running = 1; +} + +/* fd_pack_auction_end ends an auction, if there was one running. It + moves every transaction that was held in next_auction to the + pending/penalty treaps, making them eligible for scheduling. */ +void +fd_pack_auction_end( fd_pack_t * pack ) { + pack->auction_running = 0; + + /* Flush the next_auction treap, as we now hold these transactions + in either the pending treap or the appropriate penalty treap. + Note that these won't be executed until the start of the next + auction. */ + fd_pack_ord_txn_t * pool = pack->pool; + treap_rev_iter_t cur = treap_rev_iter_init( pack->next_auction, pool ); + + /* Clear the next_auction treap first, as a transaction must not be in + a treap when it is inserted into another. We can still walk the + transactions, since each one's prev link is read before it is + moved. */ + treap_new( (void*)pack->next_auction, treap_ele_max( pack->next_auction ) ); + + while( !treap_rev_iter_done( cur ) ) { + treap_rev_iter_t next = treap_rev_iter_next( cur, pool ); + insert_txn( pack, treap_rev_iter_ele( cur, pool ) ); + cur = next; + } +} void @@ -3185,6 +3266,7 @@ fd_pack_clear_all( fd_pack_t * pack ) { release_tree( pack->pending, pack->signature_map, pack->noncemap, pack->pool ); release_tree( pack->pending_bundles, pack->signature_map, pack->noncemap, pack->pool ); + release_tree( pack->next_auction, pack->signature_map, pack->noncemap, pack->pool ); for( ulong i=0UL; ipre_rotation_bundles[ i ].treap, pack->signature_map, pack->noncemap, pack->pool ); } @@ -3259,6 +3341,7 @@ delete_transaction( fd_pack_t * pack, root = pre_rotation_bundle->treap; break; } + case FD_ORD_TXN_ROOT_NEXT_AUCTION: root = pack->next_auction; break; case FD_ORD_TXN_ROOT_PENALTY( 0 ): { fd_acct_addr_t penalty_acct = *ACCT_IDX_TO_PTR( FD_ORD_TXN_ROOT_PENALTY_ACCT_IDX( root_idx ) ); penalty_treap = penalty_map_query( pack->penalty_treaps, penalty_acct, NULL ); @@ -3339,13 +3422,18 @@ delete_transaction( fd_pack_t * pack, } } - for( fd_txn_acct_iter_t iter=fd_txn_acct_iter_init( txn, FD_TXN_ACCT_CAT_ALL ); - iter!=fd_txn_acct_iter_end(); iter=fd_txn_acct_iter_next( iter ) ) { - if( FD_UNLIKELY( fd_pack_unwritable_contains( ACCT_ITER_TO_PTR( iter ) ) ) ) continue; + /* Transactions which are being held in the next_auction treap never + acquired their bitset reference, so there is nothing to release + here. */ + if( FD_LIKELY( root!=pack->next_auction ) ) { + for( fd_txn_acct_iter_t iter=fd_txn_acct_iter_init( txn, FD_TXN_ACCT_CAT_ALL ); + iter!=fd_txn_acct_iter_end(); iter=fd_txn_acct_iter_next( iter ) ) { + if( FD_UNLIKELY( fd_pack_unwritable_contains( ACCT_ITER_TO_PTR( iter ) ) ) ) continue; - release_result_t ret = release_bit_reference( pack, ACCT_ITER_TO_PTR( iter ) ); - FD_PACK_BITSET_CLEARN( pack->bitset_rw_in_use, ret.clear_rw_bit ); - FD_PACK_BITSET_CLEARN( pack->bitset_w_in_use, ret.clear_w_bit ); + release_result_t ret = release_bit_reference( pack, ACCT_ITER_TO_PTR( iter ) ); + FD_PACK_BITSET_CLEARN( pack->bitset_rw_in_use, ret.clear_rw_bit ); + FD_PACK_BITSET_CLEARN( pack->bitset_w_in_use, ret.clear_w_bit ); + } } if( FD_UNLIKELY( containing->txn->flags & FD_TXN_P_FLAGS_DURABLE_NONCE ) ) { @@ -3466,7 +3554,8 @@ fd_pack_verify( fd_pack_t * pack, fd_pack_ord_txn_t * pool = pack->pool; - treap_t * treaps[ 2 ] = { pack->pending, pack->pending_bundles }; + treap_t * treaps [ 3 ] = { pack->pending, pack->pending_bundles, pack->next_auction }; + int root_tags[ 3 ] = { FD_ORD_TXN_ROOT_PENDING, FD_ORD_TXN_ROOT_PENDING_BUNDLE, FD_ORD_TXN_ROOT_NEXT_AUCTION }; ulong txn_cnt = 0UL; ulong const pre_rotation_bundle_max = FD_PACK_PRE_ROTATION_BUNDLE_CAPACITY; @@ -3478,17 +3567,19 @@ fd_pack_verify( fd_pack_t * pack, } VERIFY_TEST( pre_rotation_bundle_cnt==pack->pre_rotation_bundle_cnt, "pre_rotation_bundle_cnt %lu, found %lu bundles", pack->pre_rotation_bundle_cnt, pre_rotation_bundle_cnt ); - for( ulong k=0UL; k<2UL+pre_rotation_bundle_max+penalty_map_slot_cnt( pack->penalty_treaps ); k++ ) { + VERIFY_TEST( pack->auction_running || !treap_ele_cnt( pack->next_auction ), "next_auction not empty outside an auction" ); + + for( ulong k=0UL; k<3UL+pre_rotation_bundle_max+penalty_map_slot_cnt( pack->penalty_treaps ); k++ ) { treap_t * treap = NULL; - if( k<2UL ) treap = treaps[ k ]; - else if( k<2UL+pre_rotation_bundle_max ) { - treap = pack->pre_rotation_bundles[ k-2UL ].treap; + if( k<3UL ) treap = treaps[ k ]; + else if( k<3UL+pre_rotation_bundle_max ) { + treap = pack->pre_rotation_bundles[ k-3UL ].treap; if( !treap_ele_cnt( treap ) ) continue; } - else if( FD_LIKELY( penalty_map_key_inval( pack->penalty_treaps[ k-2UL-pre_rotation_bundle_max ].key ) ) ) continue; + else if( FD_LIKELY( penalty_map_key_inval( pack->penalty_treaps[ k-3UL-pre_rotation_bundle_max ].key ) ) ) continue; else { - treap = pack->penalty_treaps[ k-2UL-pre_rotation_bundle_max ].penalty_treap; + treap = pack->penalty_treaps[ k-3UL-pre_rotation_bundle_max ].penalty_treap; VERIFY_TEST( treap_ele_cnt( treap )>0UL, "empty penalty treap in map" ); } @@ -3503,14 +3594,14 @@ fd_pack_verify( fd_pack_t * pack, fd_pack_ord_txn_t const * in_tbl = sig2txn_ele_query_const( pack->signature_map, &cur->_txn_e, NULL, pool ); VERIFY_TEST( in_tbl, "signature missing from sig2txn" ); - ulong expected_root = (k<2UL) ? k+1UL : ((k<2UL+pre_rotation_bundle_max) ? (ulong)FD_ORD_TXN_ROOT_PRE_ROTATION_BUNDLE( 0 ) : (ulong)FD_ORD_TXN_ROOT_PENALTY( 0 )); + ulong expected_root = (k<3UL) ? (ulong)root_tags[ k ] : ((k<3UL+pre_rotation_bundle_max) ? (ulong)FD_ORD_TXN_ROOT_PRE_ROTATION_BUNDLE( 0 ) : (ulong)FD_ORD_TXN_ROOT_PENALTY( 0 )); VERIFY_TEST( (ulong)(cur->root & FD_ORD_TXN_ROOT_TAG_MASK)==expected_root, "treap element had bad root" ); if( FD_UNLIKELY( (cur->root & FD_ORD_TXN_ROOT_TAG_MASK)==FD_ORD_TXN_ROOT_PRE_ROTATION_BUNDLE( 0 ) ) ) { - VERIFY_TEST( (ulong)FD_ORD_TXN_ROOT_PRE_ROTATION_BUNDLE_IDX( cur->root )==k-2UL, "transaction in wrong pre-rotation bundle" ); + VERIFY_TEST( (ulong)FD_ORD_TXN_ROOT_PRE_ROTATION_BUNDLE_IDX( cur->root )==k-3UL, "transaction in wrong pre-rotation bundle" ); } if( FD_LIKELY( (cur->root & FD_ORD_TXN_ROOT_TAG_MASK)==FD_ORD_TXN_ROOT_PENALTY(0) ) ) { fd_acct_addr_t const * penalty_acct = ACCT_IDX_TO_PTR( FD_ORD_TXN_ROOT_PENALTY_ACCT_IDX( cur->root ) ); - VERIFY_TEST( !memcmp( penalty_acct, pack->penalty_treaps[ k-2UL-pre_rotation_bundle_max ].key.b, 32UL ), "transaction in wrong penalty treap" ); + VERIFY_TEST( !memcmp( penalty_acct, pack->penalty_treaps[ k-3UL-pre_rotation_bundle_max ].key.b, 32UL ), "transaction in wrong penalty treap" ); } VERIFY_TEST( cur->expires_at>=pack->expire_before, "treap element expired" ); @@ -3518,6 +3609,8 @@ fd_pack_verify( fd_pack_t * pack, VERIFY_TEST( eq->txn==cur, "expq inconsistent" ); VERIFY_TEST( eq->expires_at==cur->expires_at, "expq expires_at inconsistent" ); + if( FD_UNLIKELY( (cur->root & FD_ORD_TXN_ROOT_TAG_MASK)==FD_ORD_TXN_ROOT_NEXT_AUCTION ) ) continue; + FD_PACK_BITSET_DECLARE( complement ); FD_PACK_BITSET_COPY( complement, full ); for( fd_txn_acct_iter_t iter=fd_txn_acct_iter_init( txn, FD_TXN_ACCT_CAT_WRITABLE ); diff --git a/src/disco/pack/fd_pack.h b/src/disco/pack/fd_pack.h index 15f493ae4ce..5bea612f2d0 100644 --- a/src/disco/pack/fd_pack.h +++ b/src/disco/pack/fd_pack.h @@ -261,6 +261,32 @@ fd_pack_avail_txn_cnt( fd_pack_t const * pack ) { be a valid local join. */ FD_FN_PURE ulong fd_pack_current_block_cost( fd_pack_t const * pack ); +/* fd_pack_auction_begin starts an auction, which will last until + fd_pack_auction_end is called. + + During the auction period, newly arrived transactions are not + immediately scheduled, but instead held and can't be scheduled until + the auction ends. Newly arrived bundles are eligible for scheduling + immediately. Transactions that arrived before the auction started + are eligible for scheduling throughout the auction. + + pack must be a valid local join. */ +void fd_pack_auction_begin ( fd_pack_t * pack ); + +/* fd_pack_auction_end ends the current auction, if there is one. Any + transactions that were held back during the auction now become + eligible for scheduling. pack must be a valid local join. */ +void fd_pack_auction_end ( fd_pack_t * pack ); + +/* fd_pack_auction_running returns 1 if an auction is currently running + and 0 otherwise. pack must be a valid local join. */ +FD_FN_PURE int fd_pack_auction_running ( fd_pack_t const * pack ); + +/* fd_pack_next_auction_txn_cnt returns the number of transactions + currently being held back until the current auction ends. This will + be 0 if no auction is running. pack must be a valid local join. */ +FD_FN_PURE ulong fd_pack_next_auction_txn_cnt( fd_pack_t const * pack ); + /* fd_pack_bank_tile_cnt: returns the value of bank_tile_cnt provided in pack when the pack object was initialized with fd_pack_new. pack must be a valid local join. The result will be in [1, diff --git a/src/disco/pack/fd_pack_tile.c b/src/disco/pack/fd_pack_tile.c index 3fce63b5222..dae3496d27c 100644 --- a/src/disco/pack/fd_pack_tile.c +++ b/src/disco/pack/fd_pack_tile.c @@ -66,8 +66,31 @@ const ulong CUS_PER_MICROBLOCK = 1600000UL; /* Sync with src/app/shared/fd_config.c */ #define FD_PACK_STRATEGY_PERF 0 #define FD_PACK_STRATEGY_BALANCED 1 +#define FD_PACK_STRATEGY_AUCTION 2 -static char const * const schedule_strategy_strings[2] = { "PRF", "BAL" }; +static char const * const schedule_strategy_strings[3] = { "PRF", "BAL", "AUC" }; + +/* FD_PACK_AUCTION_BUNDLE_END_GAP_NS is the amount of time for which, + if receive no bundles, we consider the bundle burst to be over. We + start auctions after not observing any bundles for + FD_PACK_AUCTION_BUNDLE_END_GAP_NS. This synchronizes the start of + the auction with the end of the bundle burst. */ +#define FD_PACK_AUCTION_BUNDLE_END_GAP_NS ( 5000000L) + +/* FD_PACK_AUCTION_SPACING_NS is the minimum time gap between auctions. + 40ms was chosen so that a bundle burst with a short pause in the + middle doesn't start two auctions. */ +#define FD_PACK_AUCTION_SPACING_NS (40000000L) + +/* FD_PACK_AUCTION_INTERVAL_NS is the maximum time between auctions, + so that no transaction is held for longer than 50ms. */ +#define FD_PACK_AUCTION_INTERVAL_NS (50000000L) + +/* FD_PACK_AUCTION_FINAL_NS is the length of time before the end of the + last slot of the leader rotation that we do not hold incoming + transactions. This is so that all incoming transactions have a + chance of being scheduled. */ +#define FD_PACK_AUCTION_FINAL_NS (10000000L) typedef struct { @@ -168,6 +191,26 @@ typedef struct { for, if we are currently packing for a slot.*/ long slot_end_ns; + /* The batch auction bookkeeping state. + leader_slot_start_tick: the start tick of the leader slot + leader_slot_end_tick: the end tick of the leader slot + auction_start_tick: the tick when the current auction started + auction_last_attempt_tick: the tick when we last started or skipped an auction + auction_next_tick: the tick when the next auction is due to be attempted + auction_cu_limit: the total number of CUs within a block at which the auction must stop (for pacing) */ + long leader_slot_start_tick; + long leader_slot_end_tick; + long auction_start_tick; + long auction_last_attempt_tick; + long auction_next_tick; + long auction_final_start_tick; + ulong auction_cu_limit; + + /* The FD_PACK_AUCTION_*_NS constants, in ticks */ + long auction_bundle_end_gap_tick; + long auction_spacing_tick; + long auction_interval_tick; + /* The current dynamic upper bound on total microblocks for this slot. Monotonically decreasing over the slot lifetime. */ ulong slot_dynamic_max_microblocks; @@ -226,6 +269,11 @@ typedef struct { fd_histf_t insert_duration [ 1 ]; fd_histf_t complete_duration[ 1 ]; + ulong auction_started; + ulong auction_cancelled; + ulong auction_ended [ FD_METRICS_ENUM_PACK_AUCTION_END_REASON_CNT ]; + fd_histf_t auction_duration [ 1 ]; + struct { uint metric_state; long metric_state_begin; @@ -405,6 +453,10 @@ metrics_write( fd_pack_ctx_t * ctx ) { FD_MHIST_COPY( PACK, MICROBLOCK_SCHEDULE_EMPTY_DURATION_NANOS, ctx->no_sched_duration ); FD_MHIST_COPY( PACK, TXN_INSERT_DURATION_NANOS, ctx->insert_duration ); FD_MHIST_COPY( PACK, MICROBLOCK_COMPLETE_DURATION_NANOS, ctx->complete_duration ); + FD_MCNT_SET ( PACK, AUCTION_STARTED, ctx->auction_started ); + FD_MCNT_SET ( PACK, AUCTION_CANCELLED, ctx->auction_cancelled ); + FD_MCNT_ENUM_COPY( PACK, AUCTION_ENDED, ctx->auction_ended ); + FD_MHIST_COPY ( PACK, AUCTION_DURATION_NANOS, ctx->auction_duration ); fd_pack_metrics_write( ctx->pack ); } @@ -518,14 +570,47 @@ prevent_park( fd_pack_ctx_t * ctx ) { return ctx->skip_cnt>0L; } +static inline void +auction_end( fd_pack_ctx_t * ctx, + long now_tick, + ulong reason ) { + if( !fd_pack_auction_running( ctx->pack ) ) return; + ctx->auction_ended[ reason ]++; + fd_histf_sample( ctx->auction_duration, (ulong)fd_long_max( now_tick-ctx->auction_start_tick, 0L ) ); + fd_pack_auction_end( ctx->pack ); +} + +static inline void +auction_begin( fd_pack_ctx_t * ctx, + long now_tick ) { + ctx->auction_last_attempt_tick = now_tick; + ctx->auction_next_tick = now_tick+ctx->auction_interval_tick; + + long span = fd_long_max( ctx->leader_slot_end_tick-ctx->leader_slot_start_tick, 1L ); + long elapsed = fd_long_min( fd_long_max( now_tick+ctx->auction_interval_tick-ctx->leader_slot_start_tick, 0L ), span ); + ulong limit = (ulong)( (double)ctx->limits.slot_max_cost*(double)elapsed/(double)span ); + if( FD_UNLIKELY( fd_pack_current_block_cost( ctx->pack )>=limit ) ) { + ctx->auction_cancelled++; + return; + } + + ctx->auction_started++; + ctx->auction_start_tick = now_tick; + ctx->auction_cu_limit = limit; + fd_pack_auction_begin( ctx->pack ); +} + static inline long next_deadline( fd_pack_ctx_t * ctx ) { if( FD_LIKELY( ctx->leader_slot==ULONG_MAX ) ) return LONG_MAX; long now = fd_tickcount(); - long slot_end = fd_clock_tile_wallclock_to_tickcount( ctx->clock, ctx->slot_end_ns ); + long deadline = fd_clock_tile_wallclock_to_tickcount( ctx->clock, ctx->slot_end_ns ); + if( ctx->strategy==FD_PACK_STRATEGY_AUCTION && nowauction_final_start_tick ) { + deadline = fd_long_min( deadline, fd_long_min( ctx->auction_next_tick, ctx->auction_final_start_tick ) ); + } ulong enabled = fd_ulong_min( fd_pack_pacing_enabled_bank_cnt( ctx->pacer, now ), ctx->execle_cnt ); - if( FD_UNLIKELY( enabled>=ctx->execle_cnt ) ) return slot_end; - return fd_long_min( slot_end, fd_pack_pacing_next_enable( ctx->pacer, enabled ) ); + if( FD_UNLIKELY( enabled>=ctx->execle_cnt ) ) return deadline; + return fd_long_min( deadline, fd_pack_pacing_next_enable( ctx->pacer, enabled ) ); } static inline void @@ -604,6 +689,7 @@ after_credit( fd_pack_ctx_t * ctx, ctx->poh_out.chunk = fd_dcache_compact_next( ctx->poh_out.chunk, sizeof(fd_done_packing_t), ctx->poh_out.chunk0, ctx->poh_out.wmark ); ctx->pack_idx++; + auction_end( ctx, now, FD_METRICS_ENUM_PACK_AUCTION_END_REASON_V_SLOT_END_IDX ); ctx->drain_execle = 1; ctx->leader_slot = ULONG_MAX; ctx->slot_microblock_cnt = 0UL; @@ -635,6 +721,19 @@ after_credit( fd_pack_ctx_t * ctx, return; } + if( ctx->strategy==FD_PACK_STRATEGY_AUCTION ) { + if( now>=ctx->auction_final_start_tick ) { + auction_end( ctx, now, FD_METRICS_ENUM_PACK_AUCTION_END_REASON_V_FINAL_IDX ); + } else if( now>=ctx->auction_next_tick ) { + /* End the current auction, if one is running. This will flush + all transactions held in next_auction. */ + auction_end( ctx, now, FD_METRICS_ENUM_PACK_AUCTION_END_REASON_V_RESTARTED_IDX ); + /* Start the new auction, holding any new transactions in + next_auction. */ + auction_begin( ctx, now ); + } + } + if( FD_UNLIKELY( ctx->pending_reduce_mb_bound ) ) { ctx->pending_reduce_mb_bound = 0; ulong * dst = fd_chunk_to_laddr( ctx->poh_out.mem, ctx->poh_out.chunk ); @@ -743,6 +842,12 @@ after_credit( fd_pack_ctx_t * ctx, flags = fd_int_if( i==0, FD_PACK_SCHEDULE_BUNDLE, 0 ) | fd_int_if( ipack ) | (now>=ctx->auction_final_start_tick); + flags = FD_PACK_SCHEDULE_BUNDLE + | fd_int_if( allow & (iexecle_out[ i ]; @@ -752,6 +857,13 @@ after_credit( fd_pack_ctx_t * ctx, schedule_duration += fd_tickcount(); fd_histf_sample( (schedule_cnt>0UL) ? ctx->schedule_duration : ctx->no_sched_duration, (ulong)schedule_duration ); + if( fd_pack_auction_running( ctx->pack ) ) { + /* End the auction early if we have reached the CU limit. */ + if( fd_pack_current_block_cost( ctx->pack )>=ctx->auction_cu_limit ) { + auction_end( ctx, now, FD_METRICS_ENUM_PACK_AUCTION_END_REASON_V_LIMIT_IDX ); + } + } + if( FD_LIKELY( schedule_cnt ) ) { any_scheduled = 1; long now2 = fd_tickcount(); @@ -823,6 +935,7 @@ after_credit( fd_pack_ctx_t * ctx, ctx->poh_out.chunk = fd_dcache_compact_next( ctx->poh_out.chunk, sizeof(fd_done_packing_t), ctx->poh_out.chunk0, ctx->poh_out.wmark ); ctx->pack_idx++; + auction_end( ctx, now, FD_METRICS_ENUM_PACK_AUCTION_END_REASON_V_SLOT_END_IDX ); ctx->drain_execle = 1; ctx->leader_slot = ULONG_MAX; ctx->slot_microblock_cnt = 0UL; @@ -1007,6 +1120,7 @@ after_frag( fd_pack_ctx_t * ctx, ctx->pack_idx++; FD_LOG_WARNING(( "consensus reset while packing for slot %lu, ending block early", ctx->leader_slot )); + auction_end( ctx, now, FD_METRICS_ENUM_PACK_AUCTION_END_REASON_V_SLOT_END_IDX ); ctx->drain_execle = 1; ctx->leader_slot = ULONG_MAX; ctx->slot_microblock_cnt = 0UL; @@ -1102,7 +1216,16 @@ after_frag( fd_pack_ctx_t * ctx, update_metric_state( ctx, fd_tickcount(), FD_PACK_METRIC_STATE_LEADER, 1 ); - ctx->slot_end_ns = ctx->_became_leader->slot_end_ns; + /* If there is currently an auction running from a previous slot, end it. */ + auction_end( ctx, now_ticks, FD_METRICS_ENUM_PACK_AUCTION_END_REASON_V_SLOT_END_IDX ); + + ctx->slot_end_ns = ctx->_became_leader->slot_end_ns; + ctx->leader_slot_start_tick = now_ticks + (long)((double)( ctx->_became_leader->slot_start_ns - now_ns )*tick_per_ns); + ctx->leader_slot_end_tick = end_ticks; + ctx->auction_final_start_tick = fd_long_if( ctx->leader_slot%4UL==3UL, + end_ticks-(long)(tick_per_ns*(double)FD_PACK_AUCTION_FINAL_NS), LONG_MAX ); + ctx->auction_last_attempt_tick = now_ticks - ctx->auction_interval_tick; + ctx->auction_next_tick = now_ticks; ctx->slot_dynamic_max_microblocks = ctx->slot_max_microblocks; ctx->slot_mixin_per_tick = fd_ulong_if( ctx->_became_leader->hashcnt_per_tick>1UL, ctx->_became_leader->hashcnt_per_tick-1UL, 0UL ); /* 0: low power / alpenglow, no hash budget */ ctx->slot_tick_duration_ns = ctx->_became_leader->tick_duration_ns; @@ -1137,7 +1260,10 @@ after_frag( fd_pack_ctx_t * ctx, ulong deleted; long insert_duration = -fd_tickcount(); int result = fd_pack_insert_bundle_fini( ctx->pack, ctx->current_bundle->bundle, ctx->current_bundle->txn_cnt, ctx->current_bundle->min_blockhash_height, 0, ctx->blk_engine_cfg, &deleted ); - insert_duration += fd_tickcount(); + insert_duration += fd_tickcount(); + long burst = fd_long_max( now+ctx->auction_bundle_end_gap_tick, ctx->auction_last_attempt_tick+ctx->auction_spacing_tick ); + long fallback = ctx->auction_last_attempt_tick+ctx->auction_interval_tick; + ctx->auction_next_tick = fd_long_min( burst, fallback ); FD_MCNT_INC( PACK, TXN_EVICTED, deleted ); ctx->insert_result[ result + FD_PACK_INSERT_RETVAL_OFF ] += ctx->current_bundle->txn_received; fd_histf_sample( ctx->insert_duration, (ulong)insert_duration ); @@ -1253,7 +1379,7 @@ unprivileged_init( fd_topo_t const * topo, if( FD_UNLIKELY( tile->pack.execle_tile_count>FD_PACK_MAX_EXECLE_TILES ) ) FD_LOG_ERR(( "pack tile connects to too many execle tiles" )); - FD_TEST( (tile->pack.schedule_strategy>=0) & (tile->pack.schedule_strategy<=FD_PACK_STRATEGY_BALANCED) ); + FD_TEST( (tile->pack.schedule_strategy>=0) & (tile->pack.schedule_strategy<=FD_PACK_STRATEGY_AUCTION) ); ctx->crank->enabled = tile->pack.bundle.enabled; if( FD_UNLIKELY( tile->pack.bundle.enabled ) ) { @@ -1314,6 +1440,9 @@ unprivileged_init( fd_topo_t const * topo, ctx->max_pending_transactions = tile->pack.max_pending_transactions; ctx->leader_slot = ULONG_MAX; ctx->block_height = 0UL; + ctx->auction_last_attempt_tick = 0L; + ctx->auction_next_tick = LONG_MAX; + ctx->auction_final_start_tick = LONG_MAX; ctx->leader_bank_idx = ULONG_MAX; ctx->leader_bank_seq = ULONG_MAX; ctx->pack_idx = 0UL; @@ -1330,6 +1459,9 @@ unprivileged_init( fd_topo_t const * topo, ctx->last_successful_insert = 0L; ctx->highest_observed_block_height = 0UL; ctx->microblock_duration_ticks = (ulong)(tick_per_ns*(double)MICROBLOCK_DURATION_NS + 0.5); + ctx->auction_bundle_end_gap_tick = (long)(tick_per_ns*(double)FD_PACK_AUCTION_BUNDLE_END_GAP_NS); + ctx->auction_spacing_tick = (long)(tick_per_ns*(double)FD_PACK_AUCTION_SPACING_NS); + ctx->auction_interval_tick = (long)(tick_per_ns*(double)FD_PACK_AUCTION_INTERVAL_NS); ctx->use_consumed_cus = tile->pack.use_consumed_cus; ctx->bench_max_shreds_per_block = tile->pack.bench_max_shreds_per_block; ctx->crank->enabled = tile->pack.bundle.enabled; @@ -1386,6 +1518,10 @@ unprivileged_init( fd_topo_t const * topo, fd_histf_join( fd_metrics_histf_new_ns( ctx->no_sched_duration, FD_MHIST_EDGES_NS( PACK, MICROBLOCK_SCHEDULE_EMPTY_DURATION_NANOS ) ) ); fd_histf_join( fd_metrics_histf_new_ns( ctx->insert_duration, FD_MHIST_EDGES_NS( PACK, TXN_INSERT_DURATION_NANOS ) ) ); fd_histf_join( fd_metrics_histf_new_ns( ctx->complete_duration, FD_MHIST_EDGES_NS( PACK, MICROBLOCK_COMPLETE_DURATION_NANOS ) ) ); + ctx->auction_started = 0UL; + ctx->auction_cancelled = 0UL; + memset( ctx->auction_ended, 0, sizeof(ctx->auction_ended) ); + fd_histf_join( fd_metrics_histf_new_ns( ctx->auction_duration, FD_MHIST_EDGES_NS( PACK, AUCTION_DURATION_NANOS ) ) ); ctx->metric_state = 0; ctx->metric_state_begin = fd_tickcount(); memset( ctx->metric_timing, '\0', 16*sizeof(long) ); diff --git a/src/disco/pack/test_pack.c b/src/disco/pack/test_pack.c index 58ecbf004b0..50fa88fc2e2 100644 --- a/src/disco/pack/test_pack.c +++ b/src/disco/pack/test_pack.c @@ -2144,6 +2144,56 @@ test_pre_rotation_bundle_size_limit( void ) { FD_TEST( !fd_pack_verify( pack, pack_verify_scratch ) ); } +static void +test_next_auction( void ) { + FD_LOG_NOTICE(( "TEST NEXT AUCTION" )); + fd_pack_t * pack = init_all( 128UL, 1UL, 128UL, &outcome ); + ulong i = 0UL; + make_transaction( i, 500U, 500U, 11.0, "A", "B", NULL, NULL ); FD_TEST( insert( i++, pack )>=0 ); /* eligible */ + fd_pack_auction_begin( pack ); + make_transaction( i, 500U, 500U, 12.0, "C", "D", NULL, NULL ); FD_TEST( insert( i++, pack )>=0 ); /* held */ + FD_TEST( fd_pack_next_auction_txn_cnt( pack )==1UL ); + FD_TEST( fd_pack_avail_txn_cnt( pack )==2UL ); + FD_TEST( !fd_pack_verify( pack, pack_verify_scratch ) ); + + /* We expect only the transaction that arrived before the auction + started to be scheduled. */ + FD_TEST( 1UL==fd_pack_schedule_next_microblock( pack, 10000000UL, 0UL, ALL, outcome.results ) ); + fd_pack_microblock_complete( pack, 0UL ); + FD_TEST( 0UL==fd_pack_schedule_next_microblock( pack, 10000000UL, 0UL, ALL, outcome.results ) ); + fd_pack_microblock_complete( pack, 0UL ); + + /* When we end the auction, the transaction that is held in + next_auction can now be scheduled. */ + fd_pack_auction_end( pack ); + FD_TEST( fd_pack_next_auction_txn_cnt( pack )==0UL ); + FD_TEST( !fd_pack_verify( pack, pack_verify_scratch ) ); + FD_TEST( 1UL==fd_pack_schedule_next_microblock( pack, 10000000UL, 0UL, ALL, outcome.results ) ); + fd_pack_microblock_complete( pack, 0UL ); + FD_TEST( !fd_pack_verify( pack, pack_verify_scratch ) ); + + /* Test that deletion and expiry of transactions held in next_auction + work correctly. */ + fd_pack_auction_begin( pack ); + ulong i_g = i; + make_transaction( i, 500U, 500U, 11.0, "G", "H", NULL, NULL ); + FD_TEST( insert( i, pack )>=0 ); + FD_TEST( insert( i, pack )>=0 ); + i++; + make_transaction( i, 500U, 500U, 11.0, "I", "J", NULL, NULL ); FD_TEST( insert( i, pack )>=0 ); + FD_TEST( fd_pack_next_auction_txn_cnt( pack )==3UL ); + FD_TEST( 1UL==fd_pack_delete_transaction( pack, txnp_get_signatures( &txnp_scratch[ i ] ) ) ); + i++; + FD_TEST( fd_pack_next_auction_txn_cnt( pack )==2UL ); + FD_TEST( !fd_pack_verify( pack, pack_verify_scratch ) ); + FD_TEST( 2UL==fd_pack_expire_before( pack, i_g+1UL ) ); + FD_TEST( fd_pack_next_auction_txn_cnt( pack )==0UL ); + FD_TEST( fd_pack_avail_txn_cnt( pack )==0UL ); + FD_TEST( !fd_pack_verify( pack, pack_verify_scratch ) ); + fd_pack_auction_end( pack ); + fd_pack_delete( fd_pack_leave( pack ) ); +} + int main( int argc, char ** argv ) { @@ -2173,6 +2223,7 @@ main( int argc, test_pre_rotation_bundles(); test_pre_rotation_bundle_lifecycle(); test_pre_rotation_bundle_size_limit(); + test_next_auction(); if( extra_benchmark ) { performance_test( extra_benchmark ); performance_test2();