From 0bd37d0148a7f6d530a921b133bb1c58e142d705 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Sat, 25 Jul 2026 12:54:50 +0200 Subject: [PATCH] pm: add diagnostics to insufficient sender reserve errors The same errInsufficientSenderReserve sentinel was returned from two unrelated checks in faceValue(): faceValue below the orchestrator's ticketEV (sender reserve genuinely too low) and faceValue unable to cover the ticket redemption tx cost (usually a too low -maxFaceValue). Operators debugging the second case were misled into inspecting the sender's reserve when the fix was raising -maxFaceValue. Wrap the sentinel with the values involved (faceValue, ticketEV, max float, redemption cost at current and avg gas price) and name the knob responsible per site. errors.Is compatibility is preserved via %w. Co-Authored-By: Claude Fable 5 --- CHANGELOG_PENDING.md | 2 ++ pm/recipient.go | 5 +++-- pm/recipient_test.go | 10 ++++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index f9afe08498..0fde89de6c 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -12,6 +12,8 @@ #### Orchestrator +- The "insufficient sender reserve" error now reports which check failed and the values involved (faceValue vs ticketEV vs sender max float, or faceValue vs redemption tx cost) so operators can tell whether the sender's reserve, `-ticketEV`, or `-maxFaceValue` needs adjusting + #### Transcoder ### Bug Fixes 🐞 diff --git a/pm/recipient.go b/pm/recipient.go index 2199842489..6eef5cbcc3 100644 --- a/pm/recipient.go +++ b/pm/recipient.go @@ -4,6 +4,7 @@ import ( "crypto/hmac" "crypto/rand" "crypto/sha256" + "fmt" "math/big" "sync" @@ -293,7 +294,7 @@ func (r *recipient) faceValue(sender ethcommon.Address) (*big.Int, error) { monitor.MaxFloat(sender.Hex(), maxFloat) } if faceValue.Cmp(r.cfg.EV) < 0 { - return nil, errInsufficientSenderReserve + return nil, fmt.Errorf("%w: faceValue %v is less than ticketEV %v (sender max float: %v); the sender's reserve is too low for this orchestrator's -ticketEV", errInsufficientSenderReserve, faceValue, r.cfg.EV, maxFloat) } // faceValue must be >= txCostWithGasPrice(current gasPrice) OR >= txCostWithGasPrice(avg gasPrice) @@ -309,7 +310,7 @@ func (r *recipient) faceValue(sender ethcommon.Address) (*big.Int, error) { // and needs to be redeemed. // For now, avgGasPrice is hardcoded. See the comment for avgGasPrice for TODO information. if faceValue.Cmp(txCost) < 0 && faceValue.Cmp(r.txCostWithGasPrice(avgGasPrice)) < 0 { - return nil, errInsufficientSenderReserve + return nil, fmt.Errorf("%w: faceValue %v cannot cover the ticket redemption tx cost (at current gas price: %v, at avg gas price: %v); increase -maxFaceValue or the sender's reserve", errInsufficientSenderReserve, faceValue, txCost, r.txCostWithGasPrice(avgGasPrice)) } return faceValue, nil diff --git a/pm/recipient_test.go b/pm/recipient_test.go index d84f5fab39..4cb0edc4f7 100644 --- a/pm/recipient_test.go +++ b/pm/recipient_test.go @@ -528,7 +528,8 @@ func TestTicketParams(t *testing.T) { // Test insufficient sender reserve error due to maxFloat < EV sm.maxFloat = new(big.Int).Sub(cfg.EV, big.NewInt(1)) _, err = r.TicketParams(sender, big.NewRat(1, 1)) - assert.EqualError(err, errInsufficientSenderReserve.Error()) + assert.ErrorIs(err, errInsufficientSenderReserve) + assert.ErrorContains(err, "less than ticketEV") // Test faceValue < txCostWithGasPrice(current gasPrice) and faceValue > txCostWithGasPrice(avg gasPrice) // Set current gasPrice higher than avg gasPrice @@ -553,7 +554,8 @@ func TestTicketParams(t *testing.T) { require.True(sm.maxFloat.Cmp(txCost) < 0) require.True(sm.maxFloat.Cmp(txCostAvgGasPrice) < 0) _, err = r.TicketParams(sender, big.NewRat(1, 1)) - assert.EqualError(err, errInsufficientSenderReserve.Error()) + assert.ErrorIs(err, errInsufficientSenderReserve) + assert.ErrorContains(err, "cannot cover the ticket redemption tx cost") // Test lazy evaluation when faceValue > txCostWithGasPrice(current gasPrice) // Set current gasPrice lower than avg gasPrice @@ -587,7 +589,7 @@ func TestTicketParams(t *testing.T) { sm.maxFloat = big.NewInt(0) // Set maxFloat to some value less than EV _, err = r.TicketParams(sender, big.NewRat(1, 1)) - assert.EqualError(err, errInsufficientSenderReserve.Error()) + assert.ErrorIs(err, errInsufficientSenderReserve) } func TestTxCostMultiplier_UsingFaceValue_ReturnsDefaultMultiplier(t *testing.T) { @@ -638,7 +640,7 @@ func TestTxCostMultiplier_InsufficientReserve_ReturnsError(t *testing.T) { mul, err := r.TxCostMultiplier(sender) assert.Nil(t, mul) - assert.EqualError(t, err, errInsufficientSenderReserve.Error()) + assert.ErrorIs(t, err, errInsufficientSenderReserve) } func TestTxCostMultiplier_ZeroTxCost_Returns_Zero(t *testing.T) {