From 1760a0a9a00fe0078668abfb60602cc28de2bede Mon Sep 17 00:00:00 2001 From: Ryan Mello Date: Thu, 30 Jul 2026 15:07:45 +0100 Subject: [PATCH] feat(model): derive provider bandwidth from settled contract bytes transfer_escrow already records real bytes delivered per contract as a byproduct of billing. Deriving throughput from it costs nothing additional and cannot be gamed selectively -- a provider cannot inflate real user traffic without actually being fast for real users. Excludes companion (return-traffic) contracts: a client's return leg settles with the client as destination, which would otherwise misread ordinary users as fast providers. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QtgqtCmKJRXdsQ5ktiqwkg --- model/provider_bandwidth_model.go | 120 ++++++++++++++++ model/provider_bandwidth_model_test.go | 185 +++++++++++++++++++++++++ 2 files changed, 305 insertions(+) create mode 100644 model/provider_bandwidth_model.go create mode 100644 model/provider_bandwidth_model_test.go diff --git a/model/provider_bandwidth_model.go b/model/provider_bandwidth_model.go new file mode 100644 index 00000000..f2854c10 --- /dev/null +++ b/model/provider_bandwidth_model.go @@ -0,0 +1,120 @@ +package model + +import ( + "context" + "time" + + "github.com/urnetwork/server" +) + +// bandwidth sources. A stored figure is tagged with the source that produced +// it so consumers never have to know which one did. +const ( + // ProviderBandwidthSourcePassive is derived from already-settled contract + // bytes: zero additional cost, and it cannot be gamed selectively. + ProviderBandwidthSourcePassive = "passive" + // ProviderBandwidthSourceActive is a sampled download over the provider's + // tunnel, used only where passive history does not exist yet. + ProviderBandwidthSourceActive = "active" +) + +// ProviderBandwidth is one throughput figure for a provider. It is advisory: +// nothing may gate provider selection on it. +type ProviderBandwidth struct { + ClientId server.Id + BytesPerSecond float64 + Source string + SampleByteCount ByteCount + WindowStart time.Time + WindowEnd time.Time +} + +// ComputePassiveProviderBandwidth derives a provider's throughput from bytes it +// has already been paid to carry. `transfer_escrow`/`contract_close` record +// settled bytes per contract as a byproduct of billing, so reading them costs +// no additional bandwidth, and a provider cannot inflate the figure selectively +// -- it cannot move more real user traffic without actually being fast for real +// users. +// +// The rate is the total settled bytes in the window over the wall-clock span +// those contracts covered, so it is an average over the sampled traffic rather +// than a peak. Returns nil, nil when the provider settled no bytes in the +// window: no history, which is not the same as measured-zero throughput. +func ComputePassiveProviderBandwidth( + ctx context.Context, + clientId server.Id, + window time.Duration, +) (*ProviderBandwidth, error) { + windowStart := server.NowUtc().Add(-window) + + var contractCount int + var sampleByteCount ByteCount + // null whenever no contract matched + var minCreateTime *time.Time + var maxCloseTime *time.Time + + server.Db(ctx, func(conn server.PgConn) { + result, err := conn.Query( + ctx, + ` + SELECT + COUNT(*), + COALESCE(SUM(contract_close.used_transfer_byte_count), 0)::bigint, + MIN(transfer_contract.create_time), + MAX(contract_close.close_time) + + FROM contract_close + + INNER JOIN transfer_contract ON + transfer_contract.contract_id = contract_close.contract_id + + WHERE + transfer_contract.destination_id = $1 AND + contract_close.party = 'destination' AND + -- companion_contract_id IS NULL excludes return-traffic legs: a + -- client's return traffic settles as a contract where the CLIENT + -- is the destination, which would otherwise be misread as that + -- client acting as a fast provider. See + -- docs/superpowers/specs/2026-07-25-enforced-provider-geo-probing-design.md + -- "Threat model" -- confirmed empirically: on beta, every + -- non-Public-key "earner" turned out to be exactly this. + transfer_contract.companion_contract_id IS NULL AND + $2 <= contract_close.close_time + `, + clientId, + windowStart, + ) + server.WithPgResult(result, err, func() { + if result.Next() { + server.Raise(result.Scan( + &contractCount, + &sampleByteCount, + &minCreateTime, + &maxCloseTime, + )) + } + }) + }) + + if contractCount == 0 || sampleByteCount <= 0 || minCreateTime == nil || maxCloseTime == nil { + return nil, nil + } + + elapsed := maxCloseTime.Sub(*minCreateTime) + if elapsed <= 0 { + // no usable denominator (a single instantaneous close, or skew between + // the create and close writers). A rate is undefined here, and dividing + // by zero or a negative span would report an absurd one. + return nil, nil + } + + return &ProviderBandwidth{ + ClientId: clientId, + BytesPerSecond: float64(sampleByteCount) / elapsed.Seconds(), + Source: ProviderBandwidthSourcePassive, + SampleByteCount: sampleByteCount, + // the span actually measured, which is at most `window` wide + WindowStart: *minCreateTime, + WindowEnd: *maxCloseTime, + }, nil +} diff --git a/model/provider_bandwidth_model_test.go b/model/provider_bandwidth_model_test.go new file mode 100644 index 00000000..46077c66 --- /dev/null +++ b/model/provider_bandwidth_model_test.go @@ -0,0 +1,185 @@ +package model + +import ( + "context" + "testing" + "time" + + "github.com/urnetwork/connect" + + "github.com/urnetwork/server" +) + +func TestComputePassiveProviderBandwidthDerivesFromSettledBytes(t *testing.T) { + server.DefaultTestEnv().Run(t, func(t testing.TB) { + ctx := context.Background() + + sourceNetworkId := server.NewId() + sourceId := server.NewId() + destNetworkId := server.NewId() + destId := server.NewId() + Testing_CreateDevice(ctx, sourceNetworkId, server.NewId(), sourceId, "", "") + Testing_CreateDevice(ctx, destNetworkId, server.NewId(), destId, "", "") + + // a contract that settled 32 MiB over exactly 10 seconds of wall time + windowStart := server.NowUtc().Add(-1 * time.Hour) + contractId := Testing_CreateSettledContract(ctx, sourceId, destId, + windowStart, windowStart.Add(10*time.Second), 32*1024*1024) + + bw, err := ComputePassiveProviderBandwidth(ctx, destId, 2*time.Hour) + connect.AssertEqual(t, err, nil) + if bw == nil { + t.Fatal("expected a passive bandwidth result, got nil") + } + connect.AssertEqual(t, bw.Source, "passive") + // 32 MiB / 10s ~= 3355443 bytes/sec + if bw.BytesPerSecond < 3_000_000 || 3_700_000 < bw.BytesPerSecond { + t.Errorf("BytesPerSecond = %.0f, want ~3355443", bw.BytesPerSecond) + } + _ = contractId + }) +} + +func TestComputePassiveProviderBandwidthNilWhenNoHistory(t *testing.T) { + server.DefaultTestEnv().Run(t, func(t testing.TB) { + ctx := context.Background() + bw, err := ComputePassiveProviderBandwidth(ctx, server.NewId(), 2*time.Hour) + connect.AssertEqual(t, err, nil) + if bw != nil { + t.Errorf("expected nil for a provider with no settled bytes, got %+v", bw) + } + }) +} + +// TestComputePassiveProviderBandwidthExcludesCompanionContracts is the load +// bearing case: a client's return traffic settles as a companion contract where +// the CLIENT is the destination. Counting it would read an ordinary user as a +// very fast provider. Only the companion leg exists here, so the correct answer +// is nil (no provider history at all) -- a merely smaller number would prove +// only dilution, not exclusion. +func TestComputePassiveProviderBandwidthExcludesCompanionContracts(t *testing.T) { + server.DefaultTestEnv().Run(t, func(t testing.TB) { + ctx := context.Background() + + providerNetworkId := server.NewId() + providerId := server.NewId() + clientNetworkId := server.NewId() + clientId := server.NewId() + Testing_CreateDevice(ctx, providerNetworkId, server.NewId(), providerId, "", "") + Testing_CreateDevice(ctx, clientNetworkId, server.NewId(), clientId, "", "") + + // the client's own contract, which the companion leg pairs with + createTime := server.NowUtc().Add(-1 * time.Hour) + primaryContractId := Testing_CreateSettledContract(ctx, clientId, providerId, + createTime, createTime.Add(10*time.Second), 1024) + + // the return leg: provider -> client, with the client as destination + Testing_CreateSettledCompanionContract(ctx, providerId, clientId, + createTime, createTime.Add(1*time.Second), 64*1024*1024, primaryContractId) + + bw, err := ComputePassiveProviderBandwidth(ctx, clientId, 2*time.Hour) + connect.AssertEqual(t, err, nil) + if bw != nil { + t.Errorf( + "return traffic must not be read as provider egress: got %.0f bytes/sec for a client that never provided", + bw.BytesPerSecond, + ) + } + }) +} + +// Testing_CreateSettledContract inserts a closed contract and its +// destination-party close row, matching the shape real settlement writes +// (`CloseContract` in subscription_model.go). Returns the contract id. +func Testing_CreateSettledContract( + ctx context.Context, + sourceId server.Id, + destinationId server.Id, + createTime time.Time, + closeTime time.Time, + usedByteCount ByteCount, +) server.Id { + return testingCreateSettledContract( + ctx, sourceId, destinationId, createTime, closeTime, usedByteCount, nil, + ) +} + +// Testing_CreateSettledCompanionContract is Testing_CreateSettledContract for +// the return-traffic leg of `companionContractId`. +func Testing_CreateSettledCompanionContract( + ctx context.Context, + sourceId server.Id, + destinationId server.Id, + createTime time.Time, + closeTime time.Time, + usedByteCount ByteCount, + companionContractId server.Id, +) server.Id { + return testingCreateSettledContract( + ctx, sourceId, destinationId, createTime, closeTime, usedByteCount, &companionContractId, + ) +} + +func testingCreateSettledContract( + ctx context.Context, + sourceId server.Id, + destinationId server.Id, + createTime time.Time, + closeTime time.Time, + usedByteCount ByteCount, + companionContractId *server.Id, +) server.Id { + contractId := server.NewId() + + server.Tx(ctx, func(tx server.PgTx) { + server.RaisePgResult(tx.Exec( + ctx, + ` + INSERT INTO transfer_contract ( + contract_id, + source_network_id, + source_id, + destination_network_id, + destination_id, + transfer_byte_count, + create_time, + close_time, + outcome, + companion_contract_id + ) + VALUES ( + $1, + (SELECT network_id FROM network_client WHERE client_id = $2), + $2, + (SELECT network_id FROM network_client WHERE client_id = $3), + $3, + $4, + $5, + $6, + 'success', + $7 + ) + `, + contractId, + sourceId, + destinationId, + usedByteCount, + createTime.UTC(), + closeTime.UTC(), + companionContractId, + )) + + server.RaisePgResult(tx.Exec( + ctx, + ` + INSERT INTO contract_close (contract_id, close_time, party, used_transfer_byte_count) + VALUES ($1, $2, 'destination', $3) + `, + contractId, + closeTime.UTC(), + usedByteCount, + )) + }) + + return contractId +}