Skip to content

Commit efd6189

Browse files
authored
fix(storage): correct epochs and cache ABI args
1 parent 60551ef commit efd6189

11 files changed

Lines changed: 149 additions & 72 deletions

File tree

internal/adapters/pricing.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,14 @@ func perTiBGranularities(perMonth, epochsPerMonth *big.Int) storage.PricePerTiB
4242

4343
func buildServiceParameters(p *warmstorage.ServicePrice) storage.ServiceParameters {
4444
out := storage.ServiceParameters{
45-
EpochDuration: chain.EpochDurationSeconds,
46-
MinUploadSize: chain.MinUploadSize,
47-
MaxUploadSize: chain.MaxUploadSize,
45+
EpochsPerMonth: chain.EpochsPerMonth,
46+
EpochsPerDay: chain.EpochsPerDay,
47+
EpochDuration: chain.EpochDurationSeconds,
48+
MinUploadSize: chain.MinUploadSize,
49+
MaxUploadSize: chain.MaxUploadSize,
4850
}
49-
if p != nil && p.EpochsPerMonth != nil {
51+
if p != nil && p.EpochsPerMonth != nil && p.EpochsPerMonth.Sign() > 0 && p.EpochsPerMonth.IsInt64() {
5052
out.EpochsPerMonth = p.EpochsPerMonth.Int64()
51-
out.EpochsPerDay = out.EpochsPerMonth / daysPerMonth
5253
}
5354
return out
5455
}

internal/adapters/pricing_test.go

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,58 @@ import (
88
"github.com/strahe/synapse-go/warmstorage"
99
)
1010

11-
func TestBuildServiceParameters_IncludesUploadSizeBounds(t *testing.T) {
12-
got := buildServiceParameters(&warmstorage.ServicePrice{
13-
EpochsPerMonth: big.NewInt(2880),
14-
})
11+
func TestBuildServiceParameters_UsesChainGeometryDefaults(t *testing.T) {
12+
got := buildServiceParameters(nil)
1513

1614
if got.MinUploadSize != chain.MinUploadSize {
17-
t.Fatalf("MinUploadSize=%d want %d", got.MinUploadSize, chain.MinUploadSize)
15+
t.Errorf("MinUploadSize=%d want %d", got.MinUploadSize, chain.MinUploadSize)
1816
}
1917
if got.MaxUploadSize != chain.MaxUploadSize {
20-
t.Fatalf("MaxUploadSize=%d want %d", got.MaxUploadSize, chain.MaxUploadSize)
18+
t.Errorf("MaxUploadSize=%d want %d", got.MaxUploadSize, chain.MaxUploadSize)
19+
}
20+
if got.EpochsPerMonth != chain.EpochsPerMonth {
21+
t.Errorf("EpochsPerMonth=%d want %d", got.EpochsPerMonth, chain.EpochsPerMonth)
22+
}
23+
if got.EpochsPerDay != chain.EpochsPerDay {
24+
t.Errorf("EpochsPerDay=%d want %d", got.EpochsPerDay, chain.EpochsPerDay)
25+
}
26+
}
27+
28+
func TestBuildServiceParameters_UsesPositivePricingMonthWithoutDerivingDay(t *testing.T) {
29+
epochsPerMonth := chain.EpochsPerMonth + chain.EpochsPerDay
30+
got := buildServiceParameters(&warmstorage.ServicePrice{
31+
EpochsPerMonth: big.NewInt(epochsPerMonth),
32+
})
33+
34+
if got.EpochsPerMonth != epochsPerMonth {
35+
t.Errorf("EpochsPerMonth=%d want %d", got.EpochsPerMonth, epochsPerMonth)
36+
}
37+
if got.EpochsPerDay != chain.EpochsPerDay {
38+
t.Errorf("EpochsPerDay=%d want %d", got.EpochsPerDay, chain.EpochsPerDay)
39+
}
40+
}
41+
42+
func TestBuildServiceParameters_IgnoresInvalidPricingMonth(t *testing.T) {
43+
for _, tt := range []struct {
44+
name string
45+
month *big.Int
46+
}{
47+
{name: "nil"},
48+
{name: "zero", month: new(big.Int)},
49+
{name: "negative", month: big.NewInt(-1)},
50+
{name: "overflow", month: new(big.Int).Lsh(big.NewInt(1), 63)},
51+
} {
52+
t.Run(tt.name, func(t *testing.T) {
53+
got := buildServiceParameters(&warmstorage.ServicePrice{
54+
EpochsPerMonth: tt.month,
55+
})
56+
57+
if got.EpochsPerMonth != chain.EpochsPerMonth {
58+
t.Errorf("EpochsPerMonth=%d want %d", got.EpochsPerMonth, chain.EpochsPerMonth)
59+
}
60+
if got.EpochsPerDay != chain.EpochsPerDay {
61+
t.Errorf("EpochsPerDay=%d want %d", got.EpochsPerDay, chain.EpochsPerDay)
62+
}
63+
})
2164
}
2265
}

sessionkey/service.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,16 @@ func (s *Service) GetExpirations(ctx context.Context, rootAddr, sessionKeyAddr c
325325
// distinguish partial-failure from transport failure via errors.Is.
326326
var errBatchPartial = errors.New("sessionkey.GetExpirations: partial batch failure")
327327

328+
var uint256Args = mustABIArguments("uint256")
329+
330+
func mustABIArguments(typeName string) abi.Arguments {
331+
t, err := abi.NewType(typeName, "", nil)
332+
if err != nil {
333+
panic("sessionkey: failed to parse " + typeName + " ABI type: " + err.Error())
334+
}
335+
return abi.Arguments{{Type: t}}
336+
}
337+
328338
func (s *Service) getExpirationsBatch(ctx context.Context, rootAddr, sessionKeyAddr common.Address, permissions []Permission, result Expirations) (Expirations, error) {
329339
regABI, err := sessionkeyregistry.SessionKeyRegistryMetaData.GetAbi()
330340
if err != nil {
@@ -349,11 +359,6 @@ func (s *Service) getExpirationsBatch(ctx context.Context, rootAddr, sessionKeyA
349359
return nil, fmt.Errorf("sessionkey.GetExpirations: batch call: %w", err)
350360
}
351361

352-
uint256Type, err := abi.NewType("uint256", "", nil)
353-
if err != nil {
354-
return nil, fmt.Errorf("sessionkey.GetExpirations: build uint256 type: %w", err)
355-
}
356-
args := abi.Arguments{{Type: uint256Type}}
357362
var perCallErrs []error
358363
for i, r := range results {
359364
if !r.Success {
@@ -364,7 +369,7 @@ func (s *Service) getExpirationsBatch(ctx context.Context, rootAddr, sessionKeyA
364369
perCallErrs = append(perCallErrs, fmt.Errorf("permission %s: empty return data", permissions[i]))
365370
continue
366371
}
367-
vals, err := args.Unpack(r.ReturnData)
372+
vals, err := uint256Args.Unpack(r.ReturnData)
368373
if err != nil {
369374
perCallErrs = append(perCallErrs, fmt.Errorf("permission %s: unpack: %w", permissions[i], err))
370375
continue

storage/context.go

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,24 @@ import (
3131
)
3232

3333
var (
34-
contextAddressType, _ = abi.NewType("address", "", nil)
35-
contextUint256Type, _ = abi.NewType("uint256", "", nil)
36-
contextStringArrayType, _ = abi.NewType("string[]", "", nil)
37-
contextStringArray2DType, _ = abi.NewType("string[][]", "", nil)
38-
contextBytesType, _ = abi.NewType("bytes", "", nil)
34+
createDataSetArgs = mustABIArguments("address", "uint256", "string[]", "string[]", "bytes")
35+
addPiecesArgs = mustABIArguments("uint256", "string[][]", "string[][]", "bytes")
36+
createAndAddArgs = mustABIArguments("bytes", "bytes")
37+
bytesArgs = mustABIArguments("bytes")
3938
)
4039

40+
func mustABIArguments(typeNames ...string) abi.Arguments {
41+
args := make(abi.Arguments, len(typeNames))
42+
for i, typeName := range typeNames {
43+
t, err := abi.NewType(typeName, "", nil)
44+
if err != nil {
45+
panic("storage: failed to parse " + typeName + " ABI type: " + err.Error())
46+
}
47+
args[i] = abi.Argument{Type: t}
48+
}
49+
return args
50+
}
51+
4152
var randReader io.Reader = rand.Reader
4253

4354
const (
@@ -829,14 +840,7 @@ func encodeCreateDataSetExtraData(payer common.Address, clientDataSetID *big.Int
829840
keys = append(keys, m.Key)
830841
values = append(values, m.Value)
831842
}
832-
args := abi.Arguments{
833-
{Type: contextAddressType},
834-
{Type: contextUint256Type},
835-
{Type: contextStringArrayType},
836-
{Type: contextStringArrayType},
837-
{Type: contextBytesType},
838-
}
839-
out, err := args.Pack(payer, clientDataSetID, keys, values, signature)
843+
out, err := createDataSetArgs.Pack(payer, clientDataSetID, keys, values, signature)
840844
if err != nil {
841845
return nil, fmt.Errorf("storage: encode create dataset extraData: %w", err)
842846
}
@@ -854,22 +858,15 @@ func encodeAddPiecesExtraData(nonce *big.Int, metadata [][]ityped.MetadataEntry,
854858
values[i][j] = m.Value
855859
}
856860
}
857-
args := abi.Arguments{
858-
{Type: contextUint256Type},
859-
{Type: contextStringArray2DType},
860-
{Type: contextStringArray2DType},
861-
{Type: contextBytesType},
862-
}
863-
out, err := args.Pack(nonce, keys, values, signature)
861+
out, err := addPiecesArgs.Pack(nonce, keys, values, signature)
864862
if err != nil {
865863
return nil, fmt.Errorf("storage: encode add pieces extraData: %w", err)
866864
}
867865
return out, nil
868866
}
869867

870868
func encodeCreateAndAddExtraData(createPayload, addPayload []byte) ([]byte, error) {
871-
args := abi.Arguments{{Type: contextBytesType}, {Type: contextBytesType}}
872-
out, err := args.Pack(createPayload, addPayload)
869+
out, err := createAndAddArgs.Pack(createPayload, addPayload)
873870
if err != nil {
874871
return nil, fmt.Errorf("storage: encode create+add extraData: %w", err)
875872
}

storage/delete.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"fmt"
77
"math/big"
88

9-
"github.com/ethereum/go-ethereum/accounts/abi"
109
"github.com/ethereum/go-ethereum/common"
1110
"github.com/ipfs/go-cid"
1211

@@ -152,8 +151,7 @@ func (c *Context) schedulePieceDeletionByID(ctx context.Context, op string, targ
152151
// encodeSignatureExtraData wraps a raw 65-byte signature as
153152
// abi.encode(["bytes"], [sig]).
154153
func encodeSignatureExtraData(sig []byte) ([]byte, error) {
155-
args := abi.Arguments{{Type: contextBytesType}}
156-
out, err := args.Pack(sig)
154+
out, err := bytesArgs.Pack(sig)
157155
if err != nil {
158156
return nil, fmt.Errorf("encode schedule-removal extraData: %w", err)
159157
}

storage/doc.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,9 @@
5151
// boundaries without skipping PieceCID verification. Context.Download can use
5252
// a CDN-backed retriever first when the context has CDN enabled, then fall back
5353
// to provider PDP retrieval on ordinary CDN failures. By default the HTTP
54-
// download client refuses to dial loopback, link-local, or private (RFC1918 /
55-
// ULA) addresses to guard against SSRF, and it ignores environment-variable
54+
// download client refuses to dial local, private, multicast, unspecified, or
55+
// otherwise reserved address ranges to guard against SSRF, and it ignores
56+
// environment-variable
5657
// proxies for the same reason; set [Options.AllowPrivateNetworks] when
5758
// connecting to trusted private infrastructure, or provide [Options.HTTPClient]
5859
// if you need explicit proxy control. Bound the number of bytes accepted per

storage/safe_http.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
)
1010

1111
// newSafeHTTPClient returns an *http.Client whose transport refuses to dial
12-
// private/loopback/link-local/multicast/unspecified addresses. This is the
12+
// local, private, multicast, unspecified, or reserved addresses. This is the
1313
// default for Service.httpClient when neither a custom HTTPClient nor
1414
// AllowPrivateNetworks=true is supplied to prevent SSRF via Service.Download
1515
// URL-based calls. Environment-variable proxies are intentionally disabled:
@@ -36,10 +36,10 @@ func newSafeHTTPClient(timeout time.Duration, allowPrivate bool) *http.Client {
3636
}
3737

3838
// safeDialContext returns a DialContext that resolves the target host and,
39-
// when allowPrivate is false, rejects any IP that falls into loopback,
40-
// link-local, RFC1918 / ULA, multicast, or unspecified ranges. Resolution is
41-
// performed once and the resolved IP is dialed directly, eliminating the
42-
// DNS-rebinding window between check and connect.
39+
// when allowPrivate is false, rejects any IP in local, private, multicast,
40+
// unspecified, or reserved ranges. Resolution is performed once and the
41+
// resolved IP is dialed directly, eliminating the DNS-rebinding window between
42+
// check and connect.
4343
func safeDialContext(base *net.Dialer, allowPrivate bool) func(ctx context.Context, network, addr string) (net.Conn, error) {
4444
return func(ctx context.Context, network, addr string) (net.Conn, error) {
4545
host, port, err := net.SplitHostPort(addr)

storage/selector.go

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -528,15 +528,8 @@ func detailedCandidateProviders(dataSets []*warmstorage.EnhancedDataSetInfo, sel
528528
}
529529

530530
func selectMatchingDetailedDataSet(providerID types.BigInt, dataSets []*warmstorage.EnhancedDataSetInfo, requestedMetadata map[string]string) (*types.BigInt, *types.BigInt, map[string]string) {
531-
matching := matchingDetailedDataSets(providerID, dataSets, requestedMetadata)
532-
if len(matching) == 0 {
533-
return nil, nil, nil
534-
}
535-
return resolvedDetailedDataSet(matching[0])
536-
}
537-
538-
func matchingDetailedDataSets(providerID types.BigInt, dataSets []*warmstorage.EnhancedDataSetInfo, requestedMetadata map[string]string) []*warmstorage.EnhancedDataSetInfo {
539-
matching := make([]*warmstorage.EnhancedDataSetInfo, 0, len(dataSets))
531+
var best *warmstorage.EnhancedDataSetInfo
532+
var bestHasPieces bool
540533
for _, dataSet := range dataSets {
541534
if dataSet == nil || dataSet.DataSetInfo == nil {
542535
continue
@@ -550,17 +543,18 @@ func matchingDetailedDataSets(providerID types.BigInt, dataSets []*warmstorage.E
550543
if !metadataMatches(dataSet.Metadata, requestedMetadata) {
551544
continue
552545
}
553-
matching = append(matching, dataSet)
554-
}
555-
sort.Slice(matching, func(i, j int) bool {
556-
iHasPieces := matching[i].ActivePieceCount != nil && matching[i].ActivePieceCount.Sign() > 0
557-
jHasPieces := matching[j].ActivePieceCount != nil && matching[j].ActivePieceCount.Sign() > 0
558-
if iHasPieces != jHasPieces {
559-
return iHasPieces
546+
hasPieces := dataSet.ActivePieceCount != nil && dataSet.ActivePieceCount.Sign() > 0
547+
if best == nil ||
548+
(hasPieces && !bestHasPieces) ||
549+
(hasPieces == bestHasPieces && dataSet.DataSetID.Cmp(best.DataSetID) < 0) {
550+
best = dataSet
551+
bestHasPieces = hasPieces
560552
}
561-
return matching[i].DataSetID.Cmp(matching[j].DataSetID) < 0
562-
})
563-
return matching
553+
}
554+
if best == nil {
555+
return nil, nil, nil
556+
}
557+
return resolvedDetailedDataSet(best)
564558
}
565559

566560
func (r *ServiceResolver) dataSetAcceptsUpload(ctx context.Context, dataSetID types.BigInt) (bool, error) {

storage/selector_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,43 @@ func TestServiceResolverResolveWritableUploadContexts_AutoSelectTrustsDetailedSn
442442
}
443443
}
444444

445+
func TestSelectMatchingDetailedDataSet_PrefersActiveThenLowestID(t *testing.T) {
446+
providerID := testID(1)
447+
dataSetID, clientDataSetID, metadata := selectMatchingDetailedDataSet(providerID, []*warmstorage.EnhancedDataSetInfo{
448+
{
449+
DataSetInfo: &warmstorage.DataSetInfo{DataSetID: testID(1), ProviderID: providerID, ClientDataSetID: testID(101)},
450+
IsLive: true,
451+
IsManaged: true,
452+
ActivePieceCount: new(big.Int),
453+
Metadata: map[string]string{"source": "app"},
454+
},
455+
{
456+
DataSetInfo: &warmstorage.DataSetInfo{DataSetID: testID(3), ProviderID: providerID, ClientDataSetID: testID(103)},
457+
IsLive: true,
458+
IsManaged: true,
459+
ActivePieceCount: bigInt(2),
460+
Metadata: map[string]string{"source": "app"},
461+
},
462+
{
463+
DataSetInfo: &warmstorage.DataSetInfo{DataSetID: testID(2), ProviderID: providerID, ClientDataSetID: testID(102)},
464+
IsLive: true,
465+
IsManaged: true,
466+
ActivePieceCount: bigInt(1),
467+
Metadata: map[string]string{"source": "app"},
468+
},
469+
}, map[string]string{"source": "app"})
470+
471+
if dataSetID == nil || !dataSetID.Equal(testID(2)) {
472+
t.Fatalf("DataSetID=%v want 2", dataSetID)
473+
}
474+
if clientDataSetID == nil || !clientDataSetID.Equal(testID(102)) {
475+
t.Fatalf("ClientDataSetID=%v want 102", clientDataSetID)
476+
}
477+
if metadata["source"] != "app" {
478+
t.Fatalf("metadata=%v want source=app", metadata)
479+
}
480+
}
481+
445482
func TestServiceResolverResolveUploadContexts_AutoSelectRetriesRetryableDetailEnrichmentFailure(t *testing.T) {
446483
fixture := serviceResolverFixture{
447484
approvedProviderIDs: []types.BigInt{testID(1)},

storage/service.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,9 @@ type Options struct {
143143

144144
// AllowPrivateNetworks disables the default SSRF protection applied to
145145
// URL-based Service.Download calls. When false (the default), the
146-
// built-in HTTP client refuses to dial loopback / link-local / RFC1918 /
147-
// ULA / multicast / unspecified addresses and returns ErrPrivateNetwork.
146+
// built-in HTTP client refuses to dial local, private, multicast,
147+
// unspecified, or otherwise reserved address ranges and returns
148+
// ErrPrivateNetwork.
148149
// Set to true only when you knowingly need to download from a private
149150
// network (e.g. in-cluster storage). Ignored when HTTPClient is set.
150151
AllowPrivateNetworks bool

0 commit comments

Comments
 (0)