package frostfs // Tests for our FrostFS client code import ( "bytes" "context" "crypto/ecdsa" "crypto/sha256" "os" "sync" "testing" "time" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/wallet" ) // Initialize storage backend for tests. func openStorage(t *testing.T) *Storage { t.Helper() cid := os.Getenv("FROSTFS_CONTAINER") // sample: "348WWfBKbS79Wbmm38MRE3oBoEDM5Ga1XXbGKGNyisDM" endpoint := os.Getenv("FROSTFS_ENDPOINT") // sample: "grpc://localhost:8802" if cid == "" || endpoint == "" { t.Skipf("one or more environment variables not set: FROSTFS_ENDPOINT, FROSTFS_CONTAINER") } key, err := getKey( os.Getenv("FROSTFS_WALLET"), os.Getenv("FROSTFS_WALLET_ACCOUNT"), os.Getenv("FROSTFS_WALLET_PASSWORD"), ) if err != nil { t.Fatal(err) } storage, err := Open(endpoint, cid, key) if err != nil { t.Fatal(err) } return storage } // Save some bytes to FrostFS and clean up after. func TestObjectPutDelete(t *testing.T) { payload := []byte("Hello FrostFS!\n") storage := openStorage(t) ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) oid, err := storage.Save(ctx, payload, "FileName", "hello_from_sdk.txt", "Tag", "foobar") if err != nil { t.Fatal(err) } t.Logf("saved object: %s", oid) err = storage.Delete(ctx, oid) if err != nil { t.Fatal(err) } t.Logf("deleted object: %s", oid) } // Check that FrostFS is in fact a content addressable storage. func TestMulipleObjects(t *testing.T) { payload := []byte("Multiple objects with same content should get the same object ID") ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) var wg sync.WaitGroup objects := make(chan string) go func() { baseline := "" for { select { case <-ctx.Done(): return case obj, ok := <-objects: if !ok { return } if baseline == "" { baseline = obj continue } if obj != baseline { t.Errorf("non-identical object id: %s != %s", baseline, obj) return } } } }() const testCount = 5 storage := openStorage(t) for i := 0; i < testCount; i++ { //nolint:intrange wg.Add(1) go func() { defer wg.Done() oid, err := storage.Save(ctx, payload, "FileName", "CAS.txt", "Tag", "test") if err != nil { t.Error(err) return } err = storage.Delete(ctx, oid) if err != nil { t.Error(err) return } select { case objects <- oid: case <-ctx.Done(): return } t.Log(oid) }() } wg.Wait() } // Check opening wallet from file system. func TestLoadWallet(t *testing.T) { const ( walletPath = "client_test_wallet.json" walletAccount = "NWZnjbTKbzwtX6w1q5R3kbEKrnJ5bp1kn7" ) key, err := getKey(walletPath, "", "") if err != nil { t.Fatal(err) } if key2addr(key) != walletAccount { t.Fatalf("incorrect address for default account: want %s, got %s", walletAccount, key2addr(key)) } key, err = getKey(walletPath, walletAccount, "") if err != nil { t.Fatal(err) } if key2addr(key) != walletAccount { t.Fatalf("incorrect address for specific account: want %s, got %s", walletAccount, key2addr(key)) } } func key2addr(k *ecdsa.PrivateKey) string { var owner user.ID user.IDFromKey(&owner, k.PublicKey) return owner.String() } // Check that loaded wallet key generates valid signature. func TestLoadWalletAndSign(t *testing.T) { const ( walletPath = "client_test_wallet.json" walletAccount = "NWZnjbTKbzwtX6w1q5R3kbEKrnJ5bp1kn7" walletAccountPassword = "" walletAccountIndex = 0 dataString = "some data to sign in this test" ) w, err := wallet.NewWalletFromFile(walletPath) if err != nil { t.Fatalf("wallet from file: %v", err) } accountFromWallet := w.Accounts[walletAccountIndex] err = accountFromWallet.Decrypt(walletAccountPassword, w.Scrypt) if err != nil { t.Fatalf("wallet decrypt: %v", err) } key, err := getKey(walletPath, walletAccount, walletAccountPassword) if err != nil { t.Fatal(err) } wrappedKey := &keys.PrivateKey{PrivateKey: *key} accountFromKey := wallet.NewAccountFromPrivateKey(wrappedKey) if !accountFromKey.PublicKey().Equal(accountFromWallet.PublicKey()) { t.Fatalf("corrupted key: want %s, got %s", accountFromWallet.PublicKey().Address(), accountFromKey.PublicKey().Address()) } data := []byte(dataString) sig := accountFromKey.PrivateKey().Sign(data) hash := sha256.Sum256(data) hashSig := accountFromKey.PrivateKey().SignHash(hash) if !bytes.Equal(sig, hashSig) { t.Fatalf("different signatures for data (%x) and for hash (%x)", sig, hashSig) } ok := accountFromKey.PublicKey().Verify(sig, hash[:]) if !ok { t.Errorf("signature %x (%d bytes): check failed: signing key", sig, len(sig)) } ok = accountFromWallet.PublicKey().Verify(sig, hash[:]) if !ok { t.Errorf("signature %x (%d bytes): check failed: wallet key", sig, len(sig)) } } // Show epoch values for nearby times. // // This test is barely useful when run unattended: it will only validate "no // errors, no panic" result. // Human operator is required to check if epoch calculations actually make // sense for network configuration being used. func TestEpoch(t *testing.T) { storage := openStorage(t) now := time.Now() var i time.Duration for i = -10; i < 10; i++ { timestamp := now.Add(i * time.Minute) //nolint:durationcheck epoch, err := storage.Epoch(context.Background(), timestamp) if err != nil { t.Fatal(err) } t.Logf("%s: %d", timestamp, epoch) } }