diff --git a/go/testutil/base.go b/go/testutil/base.go index 917b7f4f..d1d5c3be 100644 --- a/go/testutil/base.go +++ b/go/testutil/base.go @@ -2,8 +2,16 @@ package testutil import ( "fmt" - "math/rand" "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/tendermint/tendermint/libs/rand" + + dtypes "github.com/akash-network/akash-api/go/node/deployment/v1beta3" + types "github.com/akash-network/akash-api/go/node/types/v1beta3" + + // ensure sdkutil.init() to seal SDK config for the tests + _ "github.com/akash-network/akash-api/go/sdkutil" ) // CoinDenom provides ability to create coins in test functions and @@ -22,3 +30,91 @@ func Name(_ testing.TB, prefix string) string { func Hostname(t testing.TB) string { return Name(t, "hostname") + ".test.com" } + +func ProviderHostname(t testing.TB) string { + return "https://" + Hostname(t) +} + +// Attribute generates a random sdk.Attribute +func Attribute(t testing.TB) types.Attribute { + t.Helper() + return types.NewStringAttribute(Name(t, "attr-key"), Name(t, "attr-value")) +} + +// Attributes generates a set of sdk.Attribute +func Attributes(t testing.TB) []types.Attribute { + t.Helper() + count := rand.Intn(10) + 1 + + vals := make([]types.Attribute, 0, count) + for i := 0; i < count; i++ { + vals = append(vals, Attribute(t)) + } + return vals +} + +// PlacementRequirements generates placement requirements +func PlacementRequirements(t testing.TB) types.PlacementRequirements { + return types.PlacementRequirements{ + Attributes: Attributes(t), + } +} + +func RandCPUUnits() uint { + return RandRangeUint( + dtypes.GetValidationConfig().Unit.Min.CPU, + dtypes.GetValidationConfig().Unit.Max.CPU) +} + +func RandGPUUnits() uint { + return RandRangeUint( + dtypes.GetValidationConfig().Unit.Min.GPU, + dtypes.GetValidationConfig().Unit.Max.GPU) +} + +func RandMemoryQuantity() uint64 { + return RandRangeUint64( + dtypes.GetValidationConfig().Unit.Min.Memory, + dtypes.GetValidationConfig().Unit.Max.Memory) +} + +func RandStorageQuantity() uint64 { + return RandRangeUint64( + dtypes.GetValidationConfig().Unit.Min.Storage, + dtypes.GetValidationConfig().Unit.Max.Storage) +} + +// Resources produces an attribute list for populating a Group's +// 'Resources' fields. +func Resources(t testing.TB) []dtypes.ResourceUnit { + t.Helper() + count := rand.Intn(10) + 1 + + vals := make(dtypes.ResourceUnits, 0, count) + for i := 0; i < count; i++ { + coin := sdk.NewDecCoin(CoinDenom, sdk.NewInt(rand.Int63n(9999)+1)) + res := dtypes.ResourceUnit{ + Resources: types.Resources{ + ID: uint32(i) + 1, + CPU: &types.CPU{ + Units: types.NewResourceValue(uint64(dtypes.GetValidationConfig().Unit.Min.CPU)), + }, + GPU: &types.GPU{ + Units: types.NewResourceValue(uint64(dtypes.GetValidationConfig().Unit.Min.GPU)), + }, + Memory: &types.Memory{ + Quantity: types.NewResourceValue(dtypes.GetValidationConfig().Unit.Min.Memory), + }, + Storage: types.Volumes{ + types.Storage{ + Quantity: types.NewResourceValue(dtypes.GetValidationConfig().Unit.Min.Storage), + }, + }, + }, + Count: 1, + Price: coin, + } + vals = append(vals, res) + } + return vals +} diff --git a/go/testutil/cert.go b/go/testutil/cert.go new file mode 100644 index 00000000..b767fb02 --- /dev/null +++ b/go/testutil/cert.go @@ -0,0 +1,211 @@ +package testutil + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "net" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + types "github.com/akash-network/akash-api/go/node/cert/v1beta3" + certutils "github.com/akash-network/akash-api/go/node/cert/v1beta3/utils" + clientmocks "github.com/akash-network/akash-api/go/node/client/v1beta2/mocks" +) + +type TestCertificate struct { + Cert []tls.Certificate + Serial big.Int + PEM struct { + Cert []byte + Priv []byte + Pub []byte + } +} + +type certificateOption struct { + domains []string + nbf time.Time + naf time.Time + qclient *clientmocks.QueryClient +} + +type CertificateOption func(*certificateOption) + +func CertificateOptionDomains(domains []string) CertificateOption { + return func(opt *certificateOption) { + opt.domains = domains + } +} + +func CertificateOptionNotBefore(tm time.Time) CertificateOption { + return func(opt *certificateOption) { + opt.nbf = tm + } +} + +func CertificateOptionNotAfter(tm time.Time) CertificateOption { + return func(opt *certificateOption) { + opt.naf = tm + } +} + +func CertificateOptionMocks(val *clientmocks.QueryClient) CertificateOption { + return func(opt *certificateOption) { + opt.qclient = val + } +} + +func Certificate(t testing.TB, addr sdk.Address, opts ...CertificateOption) TestCertificate { + t.Helper() + + opt := &certificateOption{} + + for _, o := range opts { + o(opt) + } + + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + + if opt.nbf.IsZero() { + opt.nbf = time.Now() + } + + if opt.naf.IsZero() { + opt.naf = opt.nbf.Add(time.Hour * 24 * 365) + } + + extKeyUsage := []x509.ExtKeyUsage{ + x509.ExtKeyUsageClientAuth, + } + + if len(opt.domains) != 0 { + extKeyUsage = append(extKeyUsage, x509.ExtKeyUsageServerAuth) + } + + template := x509.Certificate{ + SerialNumber: new(big.Int).SetInt64(time.Now().UTC().UnixNano()), + Subject: pkix.Name{ + CommonName: addr.String(), + ExtraNames: []pkix.AttributeTypeAndValue{ + { + Type: certutils.AuthVersionOID, + Value: "v0.0.1", + }, + }, + }, + Issuer: pkix.Name{ + CommonName: addr.String(), + }, + NotBefore: opt.nbf, + NotAfter: opt.naf, + KeyUsage: x509.KeyUsageDataEncipherment | x509.KeyUsageKeyEncipherment, + ExtKeyUsage: extKeyUsage, + BasicConstraintsValid: true, + } + + var ips []net.IP + + for i := len(opt.domains) - 1; i >= 0; i-- { + if ip := net.ParseIP(opt.domains[i]); ip != nil { + ips = append(ips, ip) + opt.domains = append(opt.domains[:i], opt.domains[i+1:]...) + } + } + + if len(opt.domains) != 0 || len(ips) != 0 { + template.PermittedDNSDomainsCritical = true + template.PermittedDNSDomains = opt.domains + template.DNSNames = opt.domains + template.IPAddresses = ips + } + + var certDer []byte + if certDer, err = x509.CreateCertificate(rand.Reader, &template, &template, priv.Public(), priv); err != nil { + t.Fatal(err) + } + + var keyDer []byte + if keyDer, err = x509.MarshalPKCS8PrivateKey(priv); err != nil { + t.Fatal(err) + } + + var pubKeyDer []byte + if pubKeyDer, err = x509.MarshalPKIXPublicKey(priv.Public()); err != nil { + t.Fatal(err) + } + + res := TestCertificate{ + Serial: *template.SerialNumber, + PEM: struct { + Cert []byte + Priv []byte + Pub []byte + }{ + Cert: pem.EncodeToMemory(&pem.Block{ + Type: types.PemBlkTypeCertificate, + Bytes: certDer, + }), + Priv: pem.EncodeToMemory(&pem.Block{ + Type: types.PemBlkTypeECPrivateKey, + Bytes: keyDer, + }), + Pub: pem.EncodeToMemory(&pem.Block{ + Type: types.PemBlkTypeECPublicKey, + Bytes: pubKeyDer, + }), + }, + } + + cert, err := tls.X509KeyPair(res.PEM.Cert, res.PEM.Priv) + if err != nil { + t.Fatal(err) + } + + res.Cert = append(res.Cert, cert) + + if opt.qclient != nil { + opt.qclient.On("Certificates", + mock.Anything, + &types.QueryCertificatesRequest{ + Filter: types.CertificateFilter{ + Owner: addr.String(), + Serial: res.Serial.String(), + State: "valid", + }, + }). + Return(&types.QueryCertificatesResponse{ + Certificates: types.CertificatesResponse{ + types.CertificateResponse{ + Certificate: types.Certificate{ + State: types.CertificateValid, + Cert: res.PEM.Cert, + Pubkey: res.PEM.Pub, + }, + Serial: res.Serial.String(), + }, + }, + }, nil) + } + return res +} + +func CertificateRequireEqualResponse(t *testing.T, cert TestCertificate, resp types.CertificateResponse, state types.Certificate_State) { + t.Helper() + + require.Equal(t, state, resp.Certificate.State) + require.Equal(t, cert.PEM.Cert, resp.Certificate.Cert) + require.Equal(t, cert.PEM.Pub, resp.Certificate.Pubkey) +} diff --git a/go/testutil/deployment.go b/go/testutil/deployment.go new file mode 100644 index 00000000..9e12036a --- /dev/null +++ b/go/testutil/deployment.go @@ -0,0 +1,61 @@ +package testutil + +import ( + "crypto/sha256" + "math/rand" + "testing" + + dtypes "github.com/akash-network/akash-api/go/node/deployment/v1beta3" +) + +// sum256Seed provides a consistent sha256 value for initial Deployment.Version +const sum256Seed = "hihi" + +// DefaultDeploymentVersion provides consistent sha256 sum for initial Deployment.Version +var DefaultDeploymentVersion = sha256.Sum256([]byte(sum256Seed)) + +// Deployment generates a dtype.Deployment in state `DeploymentActive` +func Deployment(t testing.TB) dtypes.Deployment { + t.Helper() + return dtypes.Deployment{ + DeploymentID: DeploymentID(t), + State: dtypes.DeploymentActive, + Version: DefaultDeploymentVersion[:], + } +} + +// DeploymentGroup generates a dtype.DepDeploymentGroup in state `GroupOpen` +// with a set of random required attributes +func DeploymentGroup(t testing.TB, did dtypes.DeploymentID, gseq uint32) dtypes.Group { + t.Helper() + return dtypes.Group{ + GroupID: dtypes.MakeGroupID(did, gseq), + State: dtypes.GroupOpen, + GroupSpec: dtypes.GroupSpec{ + Name: Name(t, "dgroup"), + Requirements: PlacementRequirements(t), + Resources: Resources(t), + }, + } +} + +// GroupSpec generator +func GroupSpec(t testing.TB) dtypes.GroupSpec { + t.Helper() + return dtypes.GroupSpec{ + Name: Name(t, "dgroup"), + Requirements: PlacementRequirements(t), + Resources: Resources(t), + } +} + +// DeploymentGroups returns a set of deployment groups generated by DeploymentGroup +func DeploymentGroups(t testing.TB, did dtypes.DeploymentID, gseq uint32) []dtypes.Group { + t.Helper() + count := rand.Intn(5) + 5 // nolint:gosec + vals := make([]dtypes.Group, 0, count) + for i := 0; i < count; i++ { + vals = append(vals, DeploymentGroup(t, did, gseq+uint32(i))) + } + return vals +} diff --git a/go/testutil/ids.go b/go/testutil/ids.go new file mode 100644 index 00000000..6ed7fa10 --- /dev/null +++ b/go/testutil/ids.go @@ -0,0 +1,102 @@ +package testutil + +import ( + cryptorand "crypto/rand" + "crypto/sha256" + "math/rand" + "testing" + + "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/tendermint/tendermint/crypto/ed25519" + + dtypes "github.com/akash-network/akash-api/go/node/deployment/v1beta3" + mtypes "github.com/akash-network/akash-api/go/node/market/v1beta4" +) + +func Keyring(t testing.TB) keyring.Keyring { + t.Helper() + obj := keyring.NewInMemory() + return obj +} + +// AccAddress provides an Account's Address bytes from a ed25519 generated +// private key. +func AccAddress(t testing.TB) sdk.AccAddress { + t.Helper() + privKey := ed25519.GenPrivKey() + return sdk.AccAddress(privKey.PubKey().Address()) +} + +func Key(t testing.TB) ed25519.PrivKey { + t.Helper() + return ed25519.GenPrivKey() +} + +func DeploymentID(t testing.TB) dtypes.DeploymentID { + t.Helper() + return dtypes.DeploymentID{ + Owner: AccAddress(t).String(), + DSeq: uint64(rand.Uint32()), // nolint: gosec + } +} + +func DeploymentIDForAccount(t testing.TB, addr sdk.Address) dtypes.DeploymentID { + t.Helper() + return dtypes.DeploymentID{ + Owner: addr.String(), + DSeq: uint64(rand.Uint32()), // nolint: gosec + } +} + +// DeploymentVersion provides a random sha256 sum for simulating Deployments. +func DeploymentVersion(t testing.TB) []byte { + t.Helper() + src := make([]byte, 128) + _, err := cryptorand.Read(src) + if err != nil { + t.Fatal(err) + } + sum := sha256.Sum256(src) + return sum[:] +} + +func GroupID(t testing.TB) dtypes.GroupID { + t.Helper() + return dtypes.MakeGroupID(DeploymentID(t), rand.Uint32()) // nolint: gosec +} + +func GroupIDForAccount(t testing.TB, addr sdk.Address) dtypes.GroupID { + t.Helper() + return dtypes.MakeGroupID(DeploymentIDForAccount(t, addr), rand.Uint32()) // nolint: gosec +} + +func OrderID(t testing.TB) mtypes.OrderID { + t.Helper() + return mtypes.MakeOrderID(GroupID(t), rand.Uint32()) // nolint: gosec +} + +func OrderIDForAccount(t testing.TB, addr sdk.Address) mtypes.OrderID { + t.Helper() + return mtypes.MakeOrderID(GroupIDForAccount(t, addr), rand.Uint32()) // nolint: gosec +} + +func BidID(t testing.TB) mtypes.BidID { + t.Helper() + return mtypes.MakeBidID(OrderID(t), AccAddress(t)) +} + +func BidIDForAccount(t testing.TB, owner, provider sdk.Address) mtypes.BidID { + t.Helper() + return mtypes.MakeBidID(OrderIDForAccount(t, owner), provider.Bytes()) +} + +func LeaseID(t testing.TB) mtypes.LeaseID { + t.Helper() + return mtypes.MakeLeaseID(BidID(t)) +} + +func LeaseIDForAccount(t testing.TB, owner, provider sdk.Address) mtypes.LeaseID { + t.Helper() + return mtypes.MakeLeaseID(BidIDForAccount(t, owner, provider)) +}