Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: extend manifest integration test #443

Merged
merged 13 commits into from
Jan 30, 2025
2 changes: 1 addition & 1 deletion config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ checks:
max-pathname-length: 64
postage-amount: 1000
postage-depth: 17
timeout: 5m
timeout: 30m
type: manifest
networkavailability:
options:
Expand Down
2 changes: 1 addition & 1 deletion config/local.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ checks:
max-pathname-length: 64
postage-amount: 1000
postage-depth: 17
timeout: 5m
timeout: 30m
type: manifest
ci-pingpong:
options:
Expand Down
2 changes: 1 addition & 1 deletion config/public-testnet.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ checks:
max-pathname-length: 64
postage-amount: 140000000
postage-depth: 17
timeout: 5m
timeout: 30m
type: manifest
pt-pss:
options:
Expand Down
6 changes: 6 additions & 0 deletions pkg/bee/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const (
swarmSocSignatureHeader = "Swarm-Soc-Signature"
swarmFeedIndexHeader = "Swarm-Feed-Index"
swarmFeedIndexNextHeader = "Swarm-Feed-Index-Next"
swarmIndexDocumentHeader = "Swarm-Index-Document"
swarmErrorDocumentHeader = "Swarm-Error-Document"
)

var userAgent = "beekeeper/" + beekeeper.Version
Expand Down Expand Up @@ -340,6 +342,10 @@ type UploadOptions struct {
BatchID string
Direct bool
ActHistoryAddress swarm.Address

// Dirs
IndexDocument string
ErrorDocument string
}

type DownloadOptions struct {
Expand Down
7 changes: 7 additions & 0 deletions pkg/bee/api/dirs.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ func (s *DirsService) Upload(ctx context.Context, data io.Reader, size int64, o
header.Set("swarm-collection", "True")
header.Set(postageStampBatchHeader, o.BatchID)

if o.IndexDocument != "" {
header.Set(swarmIndexDocumentHeader, o.IndexDocument)
}
if o.ErrorDocument != "" {
header.Set(swarmErrorDocumentHeader, o.ErrorDocument)
}

err = s.client.requestWithHeader(ctx, http.MethodPost, "/"+apiVersion+"/bzz", header, data, &resp)

return
Expand Down
191 changes: 163 additions & 28 deletions pkg/check/manifest/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"math/rand"
"time"

"github.com/ethersphere/bee/v2/pkg/crypto"
"github.com/ethersphere/bee/v2/pkg/swarm"
"github.com/ethersphere/beekeeper/pkg/bee"
"github.com/ethersphere/beekeeper/pkg/bee/api"
"github.com/ethersphere/beekeeper/pkg/beekeeper"
Expand Down Expand Up @@ -67,14 +69,34 @@ func (c *Check) Run(ctx context.Context, cluster orchestration.Cluster, opts int
}

rnd := random.PseudoGenerator(o.Seed)
names := cluster.FullNodeNames()
perm := rnd.Perm(len(names))

c.logger.Infof("Seed: %d", o.Seed)
if len(names) < 2 {
return fmt.Errorf("not enough nodes to run feed check")
acha-bill marked this conversation as resolved.
Show resolved Hide resolved
}

overlays, err := cluster.FlattenOverlays(ctx)
clients, err := cluster.NodesClients(ctx)
if err != nil {
return err
}
upClient := clients[names[perm[0]]]
downClient := clients[names[perm[1]]]
acha-bill marked this conversation as resolved.
Show resolved Hide resolved

err = c.checkWithoutSubDirs(ctx, rnd, o, upClient, downClient)
if err != nil {
acha-bill marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("check without subdirs: %w", err)
}

err = c.checkWithSubDirs(ctx, rnd, o, upClient, downClient)
if err != nil {
return fmt.Errorf("check with subdirs: %w", err)
}

return nil
}

func (c *Check) checkWithoutSubDirs(ctx context.Context, rnd *rand.Rand, o Options, upClient *bee.Client, downClient *bee.Client) error {
files, err := generateFiles(rnd, o.FilesInCollection, o.MaxPathnameLength)
if err != nil {
return err
Expand All @@ -86,57 +108,170 @@ func (c *Check) Run(ctx context.Context, cluster orchestration.Cluster, opts int
}

tarFile := bee.NewBufferFile("", tarReader)
clients, err := cluster.NodesClients(ctx)
batchID, err := upClient.GetOrCreateMutableBatch(ctx, o.PostageAmount, o.PostageDepth, o.PostageLabel)
if err != nil {
return fmt.Errorf("node %s: batch id %w", upClient.Name(), err)
}
c.logger.Infof("node %s: batch id %s", upClient.Name(), batchID)

if err := upClient.UploadCollection(ctx, &tarFile, api.UploadOptions{BatchID: batchID}); err != nil {
return fmt.Errorf("node %d: %w", 0, err)
}

for _, file := range files {
if err := c.download(downClient, tarFile.Address(), &file, bee.File{}); err != nil {
return err
}
}
return nil
}

func (c *Check) checkWithSubDirs(ctx context.Context, rnd *rand.Rand, o Options, upClient *bee.Client, downClient *bee.Client) error {
privKey, err := crypto.GenerateSecp256k1Key()
if err != nil {
return err
}

sortedNodes := cluster.FullNodeNames()
node := sortedNodes[0]
signer := crypto.NewDefaultSigner(privKey)
topic, err := crypto.LegacyKeccak256([]byte("my-website"))
if err != nil {
return err
}

client := clients[node]
batchID, err := upClient.GetOrCreateMutableBatch(ctx, o.PostageAmount, o.PostageDepth, o.PostageLabel)
if err != nil {
return fmt.Errorf("node %s: batch id %w", upClient.Name(), err)
}
c.logger.Infof("node %s: batch id %s", upClient.Name(), batchID)

batchID, err := client.GetOrCreateMutableBatch(ctx, o.PostageAmount, o.PostageDepth, o.PostageLabel)
rootFeedRef, err := upClient.CreateRootFeedManifest(ctx, signer, topic, api.UploadOptions{BatchID: batchID})
if err != nil {
return fmt.Errorf("node %s: batch id %w", node, err)
return err
}
c.logger.Infof("node %s: batch id %s", node, batchID)
c.logger.Infof("root feed reference: %s", rootFeedRef.Reference)
time.Sleep(3 * time.Second)

if err := client.UploadCollection(ctx, &tarFile, api.UploadOptions{BatchID: batchID}); err != nil {
return fmt.Errorf("node %d: %w", 0, err)
paths := []string{"index.html", "assets/styles/styles.css", "assets/styles/images/image.png", "error.html"}
files, err := generateFilesWithPaths(rnd, paths, int(o.MaxPathnameLength))
if err != nil {
return err
}

lastNode := sortedNodes[len(sortedNodes)-1]
try := 0
tarReader, err := tarFiles(files)
if err != nil {
return err
}
tarFile := bee.NewBufferFile("", tarReader)
if err := upClient.UploadCollection(ctx, &tarFile, api.UploadOptions{BatchID: batchID, IndexDocument: "index.html"}); err != nil {
return err
}
c.logger.Infof("collection uploaded: %s", tarFile.Address())
time.Sleep(3 * time.Second)

DOWNLOAD:
time.Sleep(5 * time.Second)
try++
if try > 5 {
return errors.New("failed getting manifest files after too many retries")
// push first version of website to the feed
ref, err := upClient.UpdateFeedWithReference(ctx, signer, topic, 0, tarFile.Address(), api.UploadOptions{BatchID: batchID})
if err != nil {
return err
}
c.logger.Infof("feed updated: %s", ref.Reference)

for i, file := range files {
node := clients[lastNode]
// download root (index.html) from the feed
err = c.download(downClient, rootFeedRef.Reference, nil, files[0])
if err != nil {
return err
}

// update website files
files, err = generateFilesWithPaths(rnd, paths, int(o.MaxPathnameLength))
if err != nil {
return err
}

tarReader, err = tarFiles(files)
if err != nil {
return err
}
tarFile = bee.NewBufferFile("", tarReader)
if err := upClient.UploadCollection(ctx, &tarFile, api.UploadOptions{BatchID: batchID, IndexDocument: "index.html"}); err != nil {
return err
}
time.Sleep(3 * time.Second)

// push 2nd version of website to the feed
ref, err = upClient.UpdateFeedWithReference(ctx, signer, topic, 1, tarFile.Address(), api.UploadOptions{BatchID: batchID})
if err != nil {
return err
}
c.logger.Infof("feed updated: %s", ref.Reference)

// download updated index.html from the feed
err = c.download(downClient, rootFeedRef.Reference, nil, files[0])
if err != nil {
return err
}

size, hash, err := node.DownloadManifestFile(ctx, tarFile.Address(), file.Name())
// download other paths and compare
for i := 0; i < len(files); i++ {
err = c.download(downClient, tarFile.Address(), &files[i], files[0])
if err != nil {
c.logger.Infof("Node %s. Error retrieving file: %v", lastNode, err)
goto DOWNLOAD
return err
}
}
return nil
}

// download retrieves a file from the given address using the specified client.
// If the file parameter is nil, it downloads the index file in the collection.
func (c *Check) download(client *bee.Client, address swarm.Address, file *bee.File, indexFile bee.File) error {
acha-bill marked this conversation as resolved.
Show resolved Hide resolved
fName := ""
if file != nil {
fName = file.Name()
}
c.logger.Infof("downloading file: %s/%s", address, fName)

try := 0
DOWNLOAD:
time.Sleep(5 * time.Second)
try++
if try > 5 {
return fmt.Errorf("failed getting manifest files after too many retries")
}
_, hash, err := client.DownloadManifestFile(context.Background(), address, fName)
if err != nil {
c.logger.Infof("node %s. Error retrieving file: %s", client.Name(), err.Error())
goto DOWNLOAD
}
acha-bill marked this conversation as resolved.
Show resolved Hide resolved

if file != nil {
if !bytes.Equal(file.Hash(), hash) {
c.logger.Infof("Node %s. File %d not retrieved successfully. Uploaded size: %d Downloaded size: %d Node: %s File: %s/%s", lastNode, i, file.Size(), size, overlays[lastNode].String(), tarFile.Address().String(), file.Name())
c.logger.Infof("node %s. File hash does not match", client.Name())
return errManifest
}
} else {
if !bytes.Equal(indexFile.Hash(), hash) {
c.logger.Infof("node %s. Index hash does not match", client.Name())
return errManifest
}
acha-bill marked this conversation as resolved.
Show resolved Hide resolved

c.logger.Infof("Node %s. File %d retrieved successfully. Node: %s File: %s/%s", lastNode, i, overlays[lastNode].String(), tarFile.Address().String(), file.Name())
try = 0 // reset the retry counter for the next file
}

c.logger.Infof("node %s. File retrieved successfully", client.Name())
return nil
}

func generateFilesWithPaths(r *rand.Rand, paths []string, maxSize int) ([]bee.File, error) {
files := make([]bee.File, len(paths))
for i := 0; i < len(paths); i++ {
acha-bill marked this conversation as resolved.
Show resolved Hide resolved
path := paths[i]
size := int64(r.Intn(maxSize)) + 1
file := bee.NewRandomFile(r, path, size)
err := file.CalculateHash()
if err != nil {
return nil, err
}
files[i] = file
}
return files, nil
}

func generateFiles(r *rand.Rand, filesCount int, maxPathnameLength int32) ([]bee.File, error) {
files := make([]bee.File, filesCount)

Expand Down
Loading