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();