From daa68474120d840ea2fc0f05c5d198730b4ed46a Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Tue, 9 Jun 2026 12:16:45 +0200 Subject: [PATCH 1/3] feat(pricing): add -unitScale flag, deprecate -pixelsPerUnit `pixelsPerUnit` is really just a pricing scale factor: price is quoted per N work units (`price_per_work_unit = pricePerUnit / pixelsPerUnit`). The "pixels" name is transcoding-era legacy and is confusing for non-video / BYOC runners, where there are no pixels. Add `-unitScale` as the clearer primary flag and keep `-pixelsPerUnit` as a deprecated alias writing to the same config (following the datadir/dataDir pattern). Update the pricePerUnit/maxPricePerUnit help text to reference unitScale. No wire or config-format changes: the proto field and the config struct are untouched, so existing clients, discovery JSON, and configs keep working. Only the user-facing flag is renamed, with the old name still accepted. Co-Authored-By: Claude Opus 4.8 (1M context) --- cmd/livepeer/starter/flags.go | 9 +++++---- cmd/livepeer/starter/starter_test.go | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/cmd/livepeer/starter/flags.go b/cmd/livepeer/starter/flags.go index 8a8c724f2e..5a7df4464e 100644 --- a/cmd/livepeer/starter/flags.go +++ b/cmd/livepeer/starter/flags.go @@ -31,7 +31,7 @@ func NewLivepeerConfig(fs *flag.FlagSet) LivepeerConfig { cfg.SelectPriceExpFactor = fs.Float64("selectPriceExpFactor", *cfg.SelectPriceExpFactor, "Expresses how significant a small change of price is for the selection algorithm; default 100") cfg.OrchPerfStatsURL = fs.String("orchPerfStatsUrl", *cfg.OrchPerfStatsURL, "URL of Orchestrator Performance Stream Tester") cfg.Region = fs.String("region", *cfg.Region, "Region in which a gateway is deployed; used to select the region while using the orchestrator's performance stats") - cfg.MaxPricePerUnit = fs.String("maxPricePerUnit", *cfg.MaxPricePerUnit, "The maximum transcoding price per 'pixelsPerUnit' a gateway is willing to accept. If not set explicitly, broadcaster is willing to accept ANY price. Can be specified in wei or a custom currency in the format (e.g. 0.50USD). When using a custom currency, a corresponding price feed must be configured with -priceFeedAddr") + cfg.MaxPricePerUnit = fs.String("maxPricePerUnit", *cfg.MaxPricePerUnit, "The maximum price per 'unitScale' a gateway is willing to accept. If not set explicitly, broadcaster is willing to accept ANY price. Can be specified in wei or a custom currency in the format (e.g. 0.50USD). When using a custom currency, a corresponding price feed must be configured with -priceFeedAddr") cfg.MaxPricePerCapability = fs.String("maxPricePerCapability", *cfg.MaxPricePerCapability, `json list of prices per capability/model or path to json config file. Use "model_id": "default" to price all models in a pipeline the same. Example: {"capabilities_prices": [{"pipeline": "text-to-image", "model_id": "stabilityai/sd-turbo", "price_per_unit": 1000, "pixels_per_unit": 1}, {"pipeline": "upscale", "model_id": "default", price_per_unit": 1200, "pixels_per_unit": 1}]}`) cfg.IgnoreMaxPriceIfNeeded = fs.Bool("ignoreMaxPriceIfNeeded", *cfg.IgnoreMaxPriceIfNeeded, "Set to true to allow exceeding max price condition if there is no O that meets this requirement") cfg.MinPerfScore = fs.Float64("minPerfScore", *cfg.MinPerfScore, "The minimum orchestrator's performance score a gateway is willing to accept") @@ -102,9 +102,10 @@ func NewLivepeerConfig(fs *flag.FlagSet) LivepeerConfig { // Broadcaster deposit multiplier to determine max acceptable ticket faceValue cfg.DepositMultiplier = fs.Int("depositMultiplier", *cfg.DepositMultiplier, "The deposit multiplier used to determine max acceptable faceValue for PM tickets") // Orchestrator base pricing info - cfg.PricePerUnit = fs.String("pricePerUnit", "0", "The price per 'pixelsPerUnit' amount pixels. Can be specified in wei or a custom currency in the format (e.g. 0.50USD). When using a custom currency, a corresponding price feed must be configured with -priceFeedAddr") - // Unit of pixels for both O's pricePerUnit and B's maxPricePerUnit - cfg.PixelsPerUnit = fs.String("pixelsPerUnit", *cfg.PixelsPerUnit, "Amount of pixels per unit. Set to '> 1' to have smaller price granularity than 1 wei / pixel") + cfg.PricePerUnit = fs.String("pricePerUnit", "0", "The price per 'unitScale' amount of work units. Can be specified in wei or a custom currency in the format (e.g. 0.50USD). When using a custom currency, a corresponding price feed must be configured with -priceFeedAddr") + // Shared by O's pricePerUnit and B's maxPricePerUnit. + fs.StringVar(cfg.PixelsPerUnit, "unitScale", *cfg.PixelsPerUnit, "Scale factor for pricing: price is quoted per this many work units. Set > 1 for finer price granularity than 1 wei per unit.") + fs.StringVar(cfg.PixelsPerUnit, "pixelsPerUnit", *cfg.PixelsPerUnit, "[Deprecated] Use -unitScale. Amount of pixels per unit; set > 1 for smaller price granularity than 1 wei / pixel.") cfg.PriceFeedAddr = fs.String("priceFeedAddr", *cfg.PriceFeedAddr, "ETH address of the Chainlink price feed contract. Used for custom currencies conversion on -pricePerUnit or -maxPricePerUnit") cfg.AutoAdjustPrice = fs.Bool("autoAdjustPrice", *cfg.AutoAdjustPrice, "Enable/disable automatic price adjustments based on the overhead for redeeming tickets") cfg.PricePerGateway = fs.String("pricePerGateway", *cfg.PricePerGateway, `json list of price per gateway or path to json config file. Example: {"gateways":[{"ethaddress":"address1","priceperunit":0.5,"currency":"USD","pixelsperunit":1000000000000},{"ethaddress":"address2","priceperunit":0.3,"currency":"USD","pixelsperunit":1000000000000}]}`) diff --git a/cmd/livepeer/starter/starter_test.go b/cmd/livepeer/starter/starter_test.go index 705426eb32..fc6803c818 100644 --- a/cmd/livepeer/starter/starter_test.go +++ b/cmd/livepeer/starter/starter_test.go @@ -400,6 +400,28 @@ func TestNewLivepeerConfig_RemoteSignerWebhookFlags(t *testing.T) { require.Equal("Authorization:Bearer abc,X-API-Key:secret", *cfg.RemoteSignerWebhookHeaders) } +func TestNewLivepeerConfig_UnitScaleFlag(t *testing.T) { + require := require.New(t) + + // Default when neither flag is set. + fs := flag.NewFlagSet("livepeer-test", flag.ContinueOnError) + cfg := NewLivepeerConfig(fs) + require.NoError(fs.Parse([]string{})) + require.Equal("1", *cfg.PixelsPerUnit) + + // -unitScale sets PixelsPerUnit. + fs = flag.NewFlagSet("livepeer-test", flag.ContinueOnError) + cfg = NewLivepeerConfig(fs) + require.NoError(fs.Parse([]string{"-unitScale", "1000000000000"})) + require.Equal("1000000000000", *cfg.PixelsPerUnit) + + // -pixelsPerUnit is a deprecated alias for the same value. + fs = flag.NewFlagSet("livepeer-test", flag.ContinueOnError) + cfg = NewLivepeerConfig(fs) + require.NoError(fs.Parse([]string{"-pixelsPerUnit", "42"})) + require.Equal("42", *cfg.PixelsPerUnit) +} + // Helper struct to capture output for testing type testWriter struct { buf *[]byte From 91b0432b1a11d962b0a658adac27d6db6a80767b Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Mon, 15 Jun 2026 13:35:14 +0200 Subject: [PATCH 2/3] refactor(pricing): rename -unitScale to -pricingUnitSize pricingUnitSize reads as the size of one pricing unit (in work units), which parallels pricePerUnit and makes the direction unambiguous (larger = coarser granularity). The "pricing" qualifier keeps the flag self-documenting in isolation (config files, run commands) where the neighboring price flags aren't visible, and avoids the "unit of price" misread of priceUnitSize. Co-Authored-By: Claude Opus 4.8 (1M context) --- cmd/livepeer/starter/flags.go | 8 ++++---- cmd/livepeer/starter/starter_test.go | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/livepeer/starter/flags.go b/cmd/livepeer/starter/flags.go index 5a7df4464e..3bc374d006 100644 --- a/cmd/livepeer/starter/flags.go +++ b/cmd/livepeer/starter/flags.go @@ -31,7 +31,7 @@ func NewLivepeerConfig(fs *flag.FlagSet) LivepeerConfig { cfg.SelectPriceExpFactor = fs.Float64("selectPriceExpFactor", *cfg.SelectPriceExpFactor, "Expresses how significant a small change of price is for the selection algorithm; default 100") cfg.OrchPerfStatsURL = fs.String("orchPerfStatsUrl", *cfg.OrchPerfStatsURL, "URL of Orchestrator Performance Stream Tester") cfg.Region = fs.String("region", *cfg.Region, "Region in which a gateway is deployed; used to select the region while using the orchestrator's performance stats") - cfg.MaxPricePerUnit = fs.String("maxPricePerUnit", *cfg.MaxPricePerUnit, "The maximum price per 'unitScale' a gateway is willing to accept. If not set explicitly, broadcaster is willing to accept ANY price. Can be specified in wei or a custom currency in the format (e.g. 0.50USD). When using a custom currency, a corresponding price feed must be configured with -priceFeedAddr") + cfg.MaxPricePerUnit = fs.String("maxPricePerUnit", *cfg.MaxPricePerUnit, "The maximum price per 'pricingUnitSize' a gateway is willing to accept. If not set explicitly, broadcaster is willing to accept ANY price. Can be specified in wei or a custom currency in the format (e.g. 0.50USD). When using a custom currency, a corresponding price feed must be configured with -priceFeedAddr") cfg.MaxPricePerCapability = fs.String("maxPricePerCapability", *cfg.MaxPricePerCapability, `json list of prices per capability/model or path to json config file. Use "model_id": "default" to price all models in a pipeline the same. Example: {"capabilities_prices": [{"pipeline": "text-to-image", "model_id": "stabilityai/sd-turbo", "price_per_unit": 1000, "pixels_per_unit": 1}, {"pipeline": "upscale", "model_id": "default", price_per_unit": 1200, "pixels_per_unit": 1}]}`) cfg.IgnoreMaxPriceIfNeeded = fs.Bool("ignoreMaxPriceIfNeeded", *cfg.IgnoreMaxPriceIfNeeded, "Set to true to allow exceeding max price condition if there is no O that meets this requirement") cfg.MinPerfScore = fs.Float64("minPerfScore", *cfg.MinPerfScore, "The minimum orchestrator's performance score a gateway is willing to accept") @@ -102,10 +102,10 @@ func NewLivepeerConfig(fs *flag.FlagSet) LivepeerConfig { // Broadcaster deposit multiplier to determine max acceptable ticket faceValue cfg.DepositMultiplier = fs.Int("depositMultiplier", *cfg.DepositMultiplier, "The deposit multiplier used to determine max acceptable faceValue for PM tickets") // Orchestrator base pricing info - cfg.PricePerUnit = fs.String("pricePerUnit", "0", "The price per 'unitScale' amount of work units. Can be specified in wei or a custom currency in the format (e.g. 0.50USD). When using a custom currency, a corresponding price feed must be configured with -priceFeedAddr") + cfg.PricePerUnit = fs.String("pricePerUnit", "0", "The price per 'pricingUnitSize' amount of work units. Can be specified in wei or a custom currency in the format (e.g. 0.50USD). When using a custom currency, a corresponding price feed must be configured with -priceFeedAddr") // Shared by O's pricePerUnit and B's maxPricePerUnit. - fs.StringVar(cfg.PixelsPerUnit, "unitScale", *cfg.PixelsPerUnit, "Scale factor for pricing: price is quoted per this many work units. Set > 1 for finer price granularity than 1 wei per unit.") - fs.StringVar(cfg.PixelsPerUnit, "pixelsPerUnit", *cfg.PixelsPerUnit, "[Deprecated] Use -unitScale. Amount of pixels per unit; set > 1 for smaller price granularity than 1 wei / pixel.") + fs.StringVar(cfg.PixelsPerUnit, "pricingUnitSize", *cfg.PixelsPerUnit, "Number of work units in one pricing unit; pricePerUnit is the price for this many work units. Set > 1 for finer price granularity than 1 wei per work unit.") + fs.StringVar(cfg.PixelsPerUnit, "pixelsPerUnit", *cfg.PixelsPerUnit, "[Deprecated] Use -pricingUnitSize. Amount of pixels per unit; set > 1 for smaller price granularity than 1 wei / pixel.") cfg.PriceFeedAddr = fs.String("priceFeedAddr", *cfg.PriceFeedAddr, "ETH address of the Chainlink price feed contract. Used for custom currencies conversion on -pricePerUnit or -maxPricePerUnit") cfg.AutoAdjustPrice = fs.Bool("autoAdjustPrice", *cfg.AutoAdjustPrice, "Enable/disable automatic price adjustments based on the overhead for redeeming tickets") cfg.PricePerGateway = fs.String("pricePerGateway", *cfg.PricePerGateway, `json list of price per gateway or path to json config file. Example: {"gateways":[{"ethaddress":"address1","priceperunit":0.5,"currency":"USD","pixelsperunit":1000000000000},{"ethaddress":"address2","priceperunit":0.3,"currency":"USD","pixelsperunit":1000000000000}]}`) diff --git a/cmd/livepeer/starter/starter_test.go b/cmd/livepeer/starter/starter_test.go index fc6803c818..75e058ba40 100644 --- a/cmd/livepeer/starter/starter_test.go +++ b/cmd/livepeer/starter/starter_test.go @@ -400,7 +400,7 @@ func TestNewLivepeerConfig_RemoteSignerWebhookFlags(t *testing.T) { require.Equal("Authorization:Bearer abc,X-API-Key:secret", *cfg.RemoteSignerWebhookHeaders) } -func TestNewLivepeerConfig_UnitScaleFlag(t *testing.T) { +func TestNewLivepeerConfig_UnitSizeFlag(t *testing.T) { require := require.New(t) // Default when neither flag is set. @@ -409,10 +409,10 @@ func TestNewLivepeerConfig_UnitScaleFlag(t *testing.T) { require.NoError(fs.Parse([]string{})) require.Equal("1", *cfg.PixelsPerUnit) - // -unitScale sets PixelsPerUnit. + // -pricingUnitSize sets PixelsPerUnit. fs = flag.NewFlagSet("livepeer-test", flag.ContinueOnError) cfg = NewLivepeerConfig(fs) - require.NoError(fs.Parse([]string{"-unitScale", "1000000000000"})) + require.NoError(fs.Parse([]string{"-pricingUnitSize", "1000000000000"})) require.Equal("1000000000000", *cfg.PixelsPerUnit) // -pixelsPerUnit is a deprecated alias for the same value. From 95a1d2ec3fcafcdc49613e8a543c5b68a2f17b56 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Wed, 17 Jun 2026 15:48:09 +0200 Subject: [PATCH 3/3] docs: add changelog entry for -pricingUnitSize flag Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG_PENDING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 3e3ac497c9..23d8ead028 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -12,6 +12,8 @@ #### Orchestrator +* [#3942](https://github.com/livepeer/go-livepeer/pull/3942) Add `-pricingUnitSize` flag and deprecate `-pixelsPerUnit` (@rickstaa) + #### Transcoder ### Bug Fixes 🐞