Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pkg/exchange/auto.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ type ExchangeOutcome struct {
ExchangeID string
UtilizationPct float64
Error string
// OutcomeUnknown is true when the accept response was lost, so the exchange
// may have executed. It must be reconciled before any retry. The outcome
// stays in AutoExchangeResult.Failed (readers of Failed still see it) and
// halts the run (no further exchanges are attempted).
OutcomeUnknown bool
// Simulated is true when RunAutoExchangeParams.DryRun was set. The outcome
// was analyzed and quoted but no record was saved and no exchange was executed.
Simulated bool
Expand Down Expand Up @@ -573,6 +578,7 @@ func processAutoExchange(ctx context.Context, params RunAutoExchangeParams, rec
logging.Errorf("exchange execution failed for %s: %v", rec.SourceRIID, execErr)
outcome.Error = execErr.Error()
if errors.Is(execErr, common.ErrOutcomeUnknown) {
outcome.OutcomeUnknown = true
// The exchange may have happened: keep the record processing (still
// counted against the cap) and stop, instead of marking it failed.
return outcome, true
Expand Down
30 changes: 30 additions & 0 deletions pkg/exchange/outcome_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package exchange
import (
"context"
"errors"
"fmt"
"math/big"
"net"
"net/http"
Expand Down Expand Up @@ -111,3 +112,32 @@ func TestRunAutoExchange_UnknownAcceptLeavesRecordProcessingAndHalts(t *testing.
require.NotEmpty(t, store.savedRecords)
assert.NotEqual(t, "failed", store.savedRecords[0].Status)
}

func TestRunAutoExchange_OutcomeUnknownFlag(t *testing.T) {
t.Parallel()
cases := []struct {
name string
execErr error
wantUnknown bool
wantFailed int
}{
{"wrapped unknown sets the flag", fmt.Errorf("x: %w", common.ErrOutcomeUnknown), true, 0},
{"plain error leaves it false", errors.New("boom"), false, 1},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
store := &mockExchangeStore{dailySpend: "0"}
client := &mockExchangeClient{quoteResult: defaultQuote(), executeErr: tc.execErr}
params := defaultParams(store, client)
params.Config.Mode = "auto"

result, err := RunAutoExchange(context.Background(), params)
require.NoError(t, err)
assert.Equal(t, 1, client.executeCalls)
assert.Equal(t, tc.wantFailed, store.failCalls)
require.Len(t, result.Failed, 1)
assert.Equal(t, tc.wantUnknown, result.Failed[0].OutcomeUnknown)
})
}
}
Loading