Skip to content
Open
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
21 changes: 21 additions & 0 deletions openapi/SwarmCommon.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,27 @@ components:
$ref: "#/components/schemas/BatchID"
utilization:
type: integer
description: >
Raw batch fullness indicator: the highest write count among the
`2^bucketDepth` collision buckets of the batch. This is **not** a
percentage; one unit corresponds to one chunk written into the
fullest bucket. Total batch capacity is `2^depth` chunks, while
the fullest bucket caps at `2^(depth - bucketDepth)` chunks, so
the fractional usage of the batch is
`utilization / 2^(depth - bucketDepth)` (also exposed directly as
`utilizedPercentage`). When the value reaches
`2^(depth - bucketDepth)` the batch is effectively full and any
further write to the fullest bucket would overflow it.
utilizedPercentage:
type: number
format: double
minimum: 0
maximum: 1
description: >
Fractional batch fullness in the range `[0, 1]`, computed as
`utilization / 2^(depth - bucketDepth)`. A value of `1` means the
fullest bucket has reached its capacity and the batch can no
longer accept writes that would land in that bucket.
usable:
description: Indicates whether the batch was discovered by the Bee node and has received sufficient on-chain confirmations
type: boolean
Expand Down
69 changes: 36 additions & 33 deletions pkg/api/postage.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,17 +139,18 @@ func (s *Service) postageCreateHandler(w http.ResponseWriter, r *http.Request) {
}

type postageStampResponse struct {
BatchID hexByte `json:"batchID"`
Utilization uint32 `json:"utilization"`
Usable bool `json:"usable"`
Label string `json:"label"`
Depth uint8 `json:"depth"`
Amount *bigint.BigInt `json:"amount"`
BucketDepth uint8 `json:"bucketDepth"`
BlockNumber uint64 `json:"blockNumber"`
ImmutableFlag bool `json:"immutableFlag"`
Exists bool `json:"exists"`
BatchTTL int64 `json:"batchTTL"`
BatchID hexByte `json:"batchID"`
Utilization uint32 `json:"utilization"`
UtilizedPercentage float64 `json:"utilizedPercentage"`
Usable bool `json:"usable"`
Label string `json:"label"`
Depth uint8 `json:"depth"`
Amount *bigint.BigInt `json:"amount"`
BucketDepth uint8 `json:"bucketDepth"`
BlockNumber uint64 `json:"blockNumber"`
ImmutableFlag bool `json:"immutableFlag"`
Exists bool `json:"exists"`
BatchTTL int64 `json:"batchTTL"`
}

type postageStampsResponse struct {
Expand Down Expand Up @@ -205,17 +206,18 @@ func (s *Service) postageGetStampsHandler(w http.ResponseWriter, r *http.Request
}

resp.Stamps = append(resp.Stamps, postageStampResponse{
BatchID: v.ID(),
Utilization: v.Utilization(),
Usable: s.post.IssuerUsable(v),
Label: v.Label(),
Depth: v.Depth(),
Amount: bigint.Wrap(v.Amount()),
BucketDepth: v.BucketDepth(),
BlockNumber: v.BlockNumber(),
ImmutableFlag: v.ImmutableFlag(),
Exists: true,
BatchTTL: batchTTL,
BatchID: v.ID(),
Utilization: v.Utilization(),
UtilizedPercentage: v.UtilizationPercentage(),
Usable: s.post.IssuerUsable(v),
Label: v.Label(),
Depth: v.Depth(),
Amount: bigint.Wrap(v.Amount()),
BucketDepth: v.BucketDepth(),
BlockNumber: v.BlockNumber(),
ImmutableFlag: v.ImmutableFlag(),
Exists: true,
BatchTTL: batchTTL,
})
}

Expand Down Expand Up @@ -395,17 +397,18 @@ func (s *Service) postageGetStampHandler(w http.ResponseWriter, r *http.Request)
}

jsonhttp.OK(w, &postageStampResponse{
BatchID: paths.BatchID,
Depth: issuer.Depth(),
BucketDepth: issuer.BucketDepth(),
ImmutableFlag: issuer.ImmutableFlag(),
Exists: true,
BatchTTL: batchTTL,
Utilization: issuer.Utilization(),
Usable: s.post.IssuerUsable(issuer),
Label: issuer.Label(),
Amount: bigint.Wrap(issuer.Amount()),
BlockNumber: issuer.BlockNumber(),
BatchID: paths.BatchID,
Depth: issuer.Depth(),
BucketDepth: issuer.BucketDepth(),
ImmutableFlag: issuer.ImmutableFlag(),
Exists: true,
BatchTTL: batchTTL,
Utilization: issuer.Utilization(),
UtilizedPercentage: issuer.UtilizationPercentage(),
Usable: s.post.IssuerUsable(issuer),
Label: issuer.Label(),
Amount: bigint.Wrap(issuer.Amount()),
BlockNumber: issuer.BlockNumber(),
})
}

Expand Down
46 changes: 24 additions & 22 deletions pkg/api/postage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,17 +222,18 @@ func TestPostageGetStamps(t *testing.T) {
jsonhttptest.WithExpectedJSONResponse(&api.PostageStampsResponse{
Stamps: []api.PostageStampResponse{
{
BatchID: b.ID,
Utilization: si.Utilization(),
Usable: true,
Label: si.Label(),
Depth: si.Depth(),
Amount: bigint.Wrap(si.Amount()),
BucketDepth: si.BucketDepth(),
BlockNumber: si.BlockNumber(),
ImmutableFlag: si.ImmutableFlag(),
Exists: true,
BatchTTL: 15, // ((value-totalAmount)/pricePerBlock)*blockTime=((20-5)/2)*2.
BatchID: b.ID,
Utilization: si.Utilization(),
UtilizedPercentage: si.UtilizationPercentage(),
Usable: true,
Label: si.Label(),
Depth: si.Depth(),
Amount: bigint.Wrap(si.Amount()),
BucketDepth: si.BucketDepth(),
BlockNumber: si.BlockNumber(),
ImmutableFlag: si.ImmutableFlag(),
Exists: true,
BatchTTL: 15, // ((value-totalAmount)/pricePerBlock)*blockTime=((20-5)/2)*2.
},
},
}),
Expand Down Expand Up @@ -373,17 +374,18 @@ func TestPostageGetStamp(t *testing.T) {

jsonhttptest.Request(t, ts, http.MethodGet, "/stamps/"+hex.EncodeToString(b.ID), http.StatusOK,
jsonhttptest.WithExpectedJSONResponse(&api.PostageStampResponse{
BatchID: b.ID,
Utilization: si.Utilization(),
Usable: true,
Label: si.Label(),
Depth: si.Depth(),
Amount: bigint.Wrap(si.Amount()),
BucketDepth: si.BucketDepth(),
BlockNumber: si.BlockNumber(),
ImmutableFlag: si.ImmutableFlag(),
Exists: true,
BatchTTL: 15, // ((value-totalAmount)/pricePerBlock)*blockTime=((20-5)/2)*2.
BatchID: b.ID,
Utilization: si.Utilization(),
UtilizedPercentage: si.UtilizationPercentage(),
Usable: true,
Label: si.Label(),
Depth: si.Depth(),
Amount: bigint.Wrap(si.Amount()),
BucketDepth: si.BucketDepth(),
BlockNumber: si.BlockNumber(),
ImmutableFlag: si.ImmutableFlag(),
Exists: true,
BatchTTL: 15, // ((value-totalAmount)/pricePerBlock)*blockTime=((20-5)/2)*2.
}),
)
})
Expand Down
8 changes: 8 additions & 0 deletions pkg/postage/stampissuer.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,14 @@ func (si *StampIssuer) Utilization() uint32 {
return si.data.MaxBucketCount
}

// UtilizationPercentage returns the batch fullness as a fraction in the
// range [0, 1], computed as Utilization / 2^(BatchDepth - BucketDepth).
// A value of 1 means the most-filled bucket is full and any further write
// to that bucket would overflow the batch.
func (si *StampIssuer) UtilizationPercentage() float64 {
return float64(si.data.MaxBucketCount) / float64(uint64(1)<<(si.data.BatchDepth-si.data.BucketDepth))
}

// ID returns the BatchID for this batch.
func (si *StampIssuer) ID() []byte {
id := make([]byte, len(si.data.BatchID))
Expand Down
33 changes: 33 additions & 0 deletions pkg/postage/stampissuer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,39 @@ func TestUtilization(t *testing.T) {
}
}

func TestUtilizationPercentage(t *testing.T) {
t.Parallel()

// depth=17, bucketDepth=16 => fullest bucket caps at 2^(17-16)=2 chunks.
const depth, bucketDepth uint8 = 17, 16

sti := postage.NewStampIssuer("label", "keyID", make([]byte, 32), big.NewInt(3), depth, bucketDepth, 0, true)

if got := sti.UtilizationPercentage(); got != 0 {
t.Fatalf("empty issuer: want 0, got %v", got)
}

// Fill the issuer until the fullest bucket is full, then check the value
// matches the formula utilization / 2^(depth - bucketDepth).
for {
_, _, err := sti.Increment(swarm.RandAddress(t))
if errors.Is(err, postage.ErrBucketFull) {
break
}
if err != nil {
t.Fatal(err)
}
}

want := float64(sti.Utilization()) / math.Pow(2, float64(depth-bucketDepth))
if got := sti.UtilizationPercentage(); got != want {
t.Fatalf("filled issuer: want %v, got %v", want, got)
}
if got := sti.UtilizationPercentage(); got != 1 {
t.Fatalf("filled issuer should report 1, got %v", got)
}
}

func bytesToIndex(buf []byte) (bucket, index uint32) {
index64 := binary.BigEndian.Uint64(buf)
bucket = uint32(index64 >> 32)
Expand Down
Loading