Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8e9dde4
feat: pullsync fix tests
0xBECEDA Jul 29, 2026
0c96d97
feat(check): add socconvergence check for cluster-wide SWIP-101 valid…
martinconic Jul 29, 2026
f96d918
build(deps): remove local bee replace directive from go.mod
martinconic Jul 29, 2026
3a58006
refactor(socconvergence): replace storage.DivergentChunkWins with loc…
martinconic Jul 29, 2026
d5b8657
fix: scenarios description
0xBECEDA Jul 29, 2026
4efa202
fix: scenarios description
sbackend123 Jul 29, 2026
ffa3aeb
Merge remote-tracking branch 'origin/feat/pullsync-chuns-convergence'…
0xBECEDA Jul 29, 2026
1391990
Merge remote-tracking branch 'origin/feat/pullsync-chuns-convergence'…
sbackend123 Jul 29, 2026
ffe745a
Merge remote-tracking branch 'origin/feat/pullsync-chuns-convergence'…
0xBECEDA Aug 3, 2026
f5dfe3b
Merge remote-tracking branch 'origin/feat/pullsync-chuns-convergence'…
sbackend123 Aug 3, 2026
990f8bc
Merge remote-tracking branch 'origin/feat/pullsync-chuns-convergence'…
sbackend123 Aug 3, 2026
e2a0952
feat(chunk-convergence): replace scenario 5 with CAC equal-timestamp …
sbackend123 Aug 3, 2026
2536fd0
test(socmatrix): add socmatrix check and multi-stamp precedence scena…
martinconic Aug 4, 2026
74de186
test(chunkconvergence): add 503 retry handling in uploadSOC
martinconic Aug 4, 2026
78843fe
test(bee): add 503 retry handling in HasChunk and DownloadChunk
martinconic Aug 4, 2026
9d10bcc
fix(config): guard IsNil calls on value structs during config merge
martinconic Aug 7, 2026
da3ed9f
fix(config): simplify selector on embedded Inherit struct for staticc…
martinconic Aug 7, 2026
03506ef
Merge remote-tracking branch 'origin/feat/pullsync-chuns-convergence'…
0xBECEDA Aug 7, 2026
6a59db8
fix: refactor scenarios
sbackend123 Aug 9, 2026
250069c
chore: update go.sum
sbackend123 Aug 9, 2026
de644dc
fix: remove old checks
sbackend123 Aug 9, 2026
0de48a6
fix: remove dead code
sbackend123 Aug 9, 2026
22888a8
fix: clean up PR
sbackend123 Aug 9, 2026
79a86fc
fix: update deps
sbackend123 Aug 9, 2026
5a7a926
fix(check/socmatrix): use precomputed equal-timestamp stamps for scen…
martinconic Aug 12, 2026
f7121ed
Merge branch 'master' into feat/pullsync-chuns-convergence
martinconic Aug 12, 2026
a78e729
Merge branch 'master' into feat/pullsync-chuns-convergence
martinconic Aug 14, 2026
c2423a7
fix(check/socmatrix): create batches on issuer for equal-timestamp en…
martinconic Aug 14, 2026
312c790
fix(check/socmatrix): map node upload batches and issuer batches dist…
martinconic Aug 14, 2026
7ed4366
Merge remote-tracking branch 'origin/master' into feat/pullsync-chuns…
martinconic Oct 27, 2026
6763166
fix(orchestration): use the standard library sha3 for the key mac
martinconic Oct 27, 2026
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
11 changes: 11 additions & 0 deletions config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,17 @@ checks:
request-timeout: 5m
timeout: 5m
type: soc
socmatrix:
options:
postage-ttl: 24h
postage-depth: 21
postage-label: socmatrix-label
sync-wait: 45s
sync-retry-interval: 2s
request-timeout: 15m
password: beekeeper
timeout: 20m
type: socmatrix
stake:
type: stake
timeout: 5m
Expand Down
11 changes: 11 additions & 0 deletions config/local.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,17 @@ checks:
request-timeout: 5m
timeout: 5m
type: soc
ci-socmatrix:
options:
postage-ttl: 24h
postage-depth: 21
postage-label: socmatrix-label
sync-wait: 45s # wait after upload for push/pull sync before neighborhood assert (precision 11–16)
sync-retry-interval: 2s
request-timeout: 45m
password: beekeeper
timeout: 60m
type: socmatrix
ci-content-availability:
type: content-availability
timeout: 5m
Expand Down
3 changes: 3 additions & 0 deletions pkg/bee/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const (
apiVersion = "v1"
contentType = "application/json, text/plain, */*; charset=utf-8"
postageStampBatchHeader = "Swarm-Postage-Batch-Id"
postageStampHeader = "Swarm-Postage-Stamp"
deferredUploadHeader = "Swarm-Deferred-Upload"
swarmAct = "Swarm-Act"
swarmActHistoryAddress = "Swarm-Act-History-Address"
Expand Down Expand Up @@ -46,6 +47,7 @@ type Client struct {
Bytes *BytesService
Chunks *ChunksService
Dirs *DirsService
Envelope *EnvelopeService
Feed *FeedService
Files *FilesService
Node *NodeService
Expand Down Expand Up @@ -96,6 +98,7 @@ func newClient(apiURL *url.URL, httpClient *http.Client) (c *Client) {
c.Bytes = (*BytesService)(&c.service)
c.Chunks = (*ChunksService)(&c.service)
c.Dirs = (*DirsService)(&c.service)
c.Envelope = (*EnvelopeService)(&c.service)
c.Feed = (*FeedService)(&c.service)
c.Files = (*FilesService)(&c.service)
c.Node = (*NodeService)(&c.service)
Expand Down
6 changes: 5 additions & 1 deletion pkg/bee/api/chunks.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ func (c *ChunksService) Upload(ctx context.Context, data []byte, o UploadOptions
if o.Direct {
h.Add(deferredUploadHeader, "false")
}
h.Add(postageStampBatchHeader, o.BatchID)
if o.Stamp != "" {
h.Add(postageStampHeader, o.Stamp)
} else {
h.Add(postageStampBatchHeader, o.BatchID)
}
err := c.client.requestWithHeader(ctx, http.MethodPost, "/"+apiVersion+"/chunks", h, bytes.NewReader(data), &resp)
return resp, err
}
53 changes: 53 additions & 0 deletions pkg/bee/api/envelope.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package api

import (
"context"
"encoding/hex"
"fmt"
"net/http"

"github.com/ethersphere/bee/v2/pkg/postage"
"github.com/ethersphere/bee/v2/pkg/swarm"
)

// EnvelopeService represents Bee's postage envelope service.
type EnvelopeService service

// EnvelopeResponse is the JSON body returned by POST /envelope/{address}.
type EnvelopeResponse struct {
Issuer string `json:"issuer"`
Index string `json:"index"`
Timestamp string `json:"timestamp"`
Signature string `json:"signature"`
}

// Create requests a postage stamp for the given chunk address from the node's
// stamper for batchID. The stamp is returned as a postage.Stamp ready for upload.
func (e *EnvelopeService) Create(ctx context.Context, addr swarm.Address, batchID string) (*postage.Stamp, error) {
h := http.Header{}
h.Add(postageStampBatchHeader, batchID)

var resp EnvelopeResponse
err := e.client.requestWithHeader(ctx, http.MethodPost, "/"+apiVersion+"/envelope/"+addr.String(), h, nil, &resp)
if err != nil {
return nil, err
}

batchBytes, err := hex.DecodeString(batchID)
if err != nil {
return nil, fmt.Errorf("decode batch id: %w", err)
}
index, err := hex.DecodeString(resp.Index)
if err != nil {
return nil, fmt.Errorf("decode stamp index: %w", err)
}
ts, err := hex.DecodeString(resp.Timestamp)
if err != nil {
return nil, fmt.Errorf("decode stamp timestamp: %w", err)
}
sig, err := hex.DecodeString(resp.Signature)
if err != nil {
return nil, fmt.Errorf("decode stamp signature: %w", err)
}
return postage.NewStamp(batchBytes, index, ts, sig), nil
}
26 changes: 14 additions & 12 deletions pkg/bee/api/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,21 @@ func (n *NodeService) Balances(ctx context.Context) (resp Balances, err error) {

// HasChunk returns true/false if node has a chunk
func (n *NodeService) HasChunk(ctx context.Context, a swarm.Address) (bool, error) {
resp := struct {
Message string `json:"message,omitempty"`
Code int `json:"code,omitempty"`
}{}

err := n.client.requestJSON(ctx, http.MethodGet, "/chunks/"+a.String(), nil, &resp)
if IsHTTPStatusErrorCode(err, http.StatusNotFound) {
return false, nil
} else if err != nil {
return false, err
for attempt := 0; attempt < 5; attempt++ {
err := n.client.requestJSON(ctx, http.MethodHead, "/chunks/"+a.String(), nil, nil)
if IsHTTPStatusErrorCode(err, http.StatusNotFound) {
return false, nil
}
if IsHTTPStatusErrorCode(err, http.StatusServiceUnavailable) {
time.Sleep(1 * time.Second)
continue
}
if err != nil {
return false, err
}
return true, nil
}

return true, nil
return false, fmt.Errorf("has chunk %s: exceeded retries on 503", a.String())
}

// Health represents node's health
Expand Down
1 change: 1 addition & 0 deletions pkg/bee/api/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type UploadOptions struct {
Pin bool
Tag uint64
BatchID string
Stamp string // hex-encoded postage stamp; when set, BatchID is ignored
Direct bool
ActHistoryAddress swarm.Address
RLevel *redundancy.Level
Expand Down
21 changes: 18 additions & 3 deletions pkg/bee/api/soc.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,32 @@ import (
"github.com/ethersphere/bee/v2/pkg/swarm"
)

// PSSService represents Bee's PSS service
// SOCService represents Bee's SOC service.
type SOCService service

// SocResponse is the JSON body returned by POST /soc.
type SocResponse struct {
Reference swarm.Address
Reference swarm.Address `json:"reference"`
}

// Sends a PSS message to a recipienct with a specific topic
// UploadSOC uploads a single owner chunk using a postage batch ID.
func (p *SOCService) UploadSOC(ctx context.Context, owner, ID, signature string, data io.Reader, batchID string) (*SocResponse, error) {
h := http.Header{}
h.Add(postageStampBatchHeader, batchID)
h.Add(deferredUploadHeader, "false")
url := fmt.Sprintf("/%s/soc/%s/%s?sig=%s", apiVersion, owner, ID, signature)

resp := SocResponse{}
return &resp, p.client.requestWithHeader(ctx, http.MethodPost, url, h, data, &resp)
}

// UploadSOCWithStamp uploads a single owner chunk using a precomputed postage stamp
// (hex-encoded Swarm-Postage-Stamp header). Direct upload is forced so the chunk
// enters the reserve immediately for pullsync.
func (p *SOCService) UploadSOCWithStamp(ctx context.Context, owner, ID, signature string, data io.Reader, stampHex string) (*SocResponse, error) {
h := http.Header{}
h.Add(postageStampHeader, stampHex)
h.Add(deferredUploadHeader, "false")
url := fmt.Sprintf("/%s/soc/%s/%s?sig=%s", apiVersion, owner, ID, signature)

resp := SocResponse{}
Expand Down
38 changes: 31 additions & 7 deletions pkg/bee/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package bee
import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
Expand All @@ -12,11 +13,13 @@ import (
"net/http"
"net/url"
"slices"
"strings"
"sync"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethersphere/bee/v2/pkg/crypto"
"github.com/ethersphere/bee/v2/pkg/postage"
"github.com/ethersphere/bee/v2/pkg/swarm"
"github.com/ethersphere/beekeeper/pkg/bee/api"
"github.com/ethersphere/beekeeper/pkg/logging"
Expand Down Expand Up @@ -213,13 +216,19 @@ func (c *Client) DownloadBytes(ctx context.Context, a swarm.Address, opts *api.D

// DownloadChunk downloads chunk from the node
func (c *Client) DownloadChunk(ctx context.Context, a swarm.Address, targets string, opts *api.DownloadOptions) (data []byte, err error) {
r, err := c.api.Chunks.Download(ctx, a, targets, opts)
if err != nil {
return nil, fmt.Errorf("download chunk %s: %w", a, err)
for attempt := 0; attempt < 5; attempt++ {
r, err := c.api.Chunks.Download(ctx, a, targets, opts)
if err != nil {
if api.IsHTTPStatusErrorCode(err, 503) || strings.Contains(err.Error(), "503") || strings.Contains(err.Error(), "Service Unavailable") {
time.Sleep(1 * time.Second)
continue
}
return nil, fmt.Errorf("download chunk %s: %w", a, err)
}
defer r.Close()
return io.ReadAll(r)
}
defer r.Close()

return io.ReadAll(r)
return nil, fmt.Errorf("download chunk %s: exceeded retries on 503", a)
}

// DownloadFileBytes downloads a flie from the node and returns the data.
Expand Down Expand Up @@ -642,7 +651,7 @@ func (c *Client) SendPSSMessage(ctx context.Context, nodeAddress swarm.Address,
return c.api.PSS.SendMessage(ctx, nodeAddress, publicKey, topic, prefix, bytes.NewReader(data), batchID)
}

// UploadSOC uploads a single owner chunk to a node with a E
// UploadSOC uploads a single owner chunk to a node with a batch ID.
func (c *Client) UploadSOC(ctx context.Context, owner, ID, signature string, data []byte, batchID string) (swarm.Address, error) {
resp, err := c.api.SOC.UploadSOC(ctx, owner, ID, signature, bytes.NewReader(data), batchID)
if err != nil {
Expand All @@ -652,6 +661,21 @@ func (c *Client) UploadSOC(ctx context.Context, owner, ID, signature string, dat
return resp.Reference, nil
}

// UploadSOCWithStamp uploads a single owner chunk using a precomputed postage stamp.
func (c *Client) UploadSOCWithStamp(ctx context.Context, owner, ID, signature string, data []byte, stamp []byte) (swarm.Address, error) {
resp, err := c.api.SOC.UploadSOCWithStamp(ctx, owner, ID, signature, bytes.NewReader(data), hex.EncodeToString(stamp))
if err != nil {
return swarm.ZeroAddress, err
}

return resp.Reference, nil
}

// CreateEnvelope requests a postage stamp for addr from the node's stamper.
func (c *Client) CreateEnvelope(ctx context.Context, addr swarm.Address, batchID string) (*postage.Stamp, error) {
return c.api.Envelope.Create(ctx, addr, batchID)
}

// Settlements represents Settlements's response
type Settlements struct {
Settlements []Settlement
Expand Down
Loading
Loading