From 3c6020005daeb60b817732f5d73408bdf4a829c4 Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Mon, 17 Feb 2025 15:30:21 +0200 Subject: [PATCH 01/27] add node client with account and nodes functions signatures --- node-registrar/client/account.go | 31 ++++++++ node-registrar/client/client.go | 50 +++++++++++++ node-registrar/client/node.go | 101 +++++++++++++++++++++++++ node-registrar/client/zos_version.go | 108 +++++++++++++++++++++++++++ 4 files changed, 290 insertions(+) create mode 100644 node-registrar/client/account.go create mode 100644 node-registrar/client/client.go create mode 100644 node-registrar/client/node.go create mode 100644 node-registrar/client/zos_version.go diff --git a/node-registrar/client/account.go b/node-registrar/client/account.go new file mode 100644 index 0000000..349bbac --- /dev/null +++ b/node-registrar/client/account.go @@ -0,0 +1,31 @@ +package client + +import ( + "fmt" +) + +var ErrorAccountNotFround = fmt.Errorf("failed to get requested account from node regiatrar") + +func (c RegistrarClient) CreateAccount(relays []string, rmbEncKey string) (account Account, err error) { + return c.createTwin(relays, rmbEncKey) +} + +func (c RegistrarClient) UpdateAccount(relays []string, rmbEncKey string) (err error) { + return +} + +func (c RegistrarClient) EnsureAccount(pk []byte) (account Account, err error) { + return +} + +func (c RegistrarClient) GetAccount(id uint64) (account Account, err error) { + return +} + +func (c RegistrarClient) GetAccountByPK(pk []byte) (account Account, err error) { + return +} + +func (c *RegistrarClient) createTwin(relays []string, rmbEncKey string) (result Account, err error) { + return +} diff --git a/node-registrar/client/client.go b/node-registrar/client/client.go new file mode 100644 index 0000000..48a2f79 --- /dev/null +++ b/node-registrar/client/client.go @@ -0,0 +1,50 @@ +package client + +import ( + "crypto/ed25519" + "net/http" + + "github.com/pkg/errors" +) + +type RegistrarClient struct { + httpClient http.Client + privateKey ed25519.PrivateKey + nodeID uint64 + twinID uint64 + baseURL string +} + +func NewRegistrarClient(baseURL string, privateKey []byte) (cli RegistrarClient, err error) { + client := http.DefaultClient + + sk := ed25519.NewKeyFromSeed(privateKey) + pk, ok := sk.Public().(ed25519.PublicKey) + if !ok { + return cli, errors.Wrap(err, "failed to get public key of provided private key") + } + + cli = RegistrarClient{ + httpClient: *client, + privateKey: privateKey, + baseURL: baseURL, + } + + account, err := cli.GetAccountByPK(pk) + if errors.Is(err, ErrorAccountNotFround) { + return cli, nil + } else if err != nil { + return cli, errors.Wrap(err, "failed to get account with public key") + } + + cli.twinID = account.TwinID + node, err := cli.GetNodeByTwinID(account.TwinID) + if errors.Is(err, ErrorNodeNotFround) { + return cli, nil + } else if err != nil { + return cli, errors.Wrapf(err, "failed to get node with twin id %d", account.TwinID) + } + + cli.nodeID = node.NodeID + return +} diff --git a/node-registrar/client/node.go b/node-registrar/client/node.go new file mode 100644 index 0000000..32e1a99 --- /dev/null +++ b/node-registrar/client/node.go @@ -0,0 +1,101 @@ +package client + +import "fmt" + +var ErrorNodeNotFround = fmt.Errorf("failed to get requested node from node regiatrar") + +func (c RegistrarClient) RegisterNode( + farmID uint64, + twinID uint64, + interfaces Interface, + location Location, + resources Resources, + serialNumber string, + secureBoot, + virtualized bool, +) (node Node, err error) { + return +} + +func (c RegistrarClient) UpdateNode( + nodeID uint64, + farmID uint64, + interfaces Interface, + location Location, + resources Resources, + serialNumber string, + secureBoot, + virtualized bool, +) (err error) { + return +} + +func (c RegistrarClient) ReportUptime(id uint64, report UptimeReport) (err error) { + return +} + +func (c RegistrarClient) GetNode(id uint64) (node Node, err error) { + return +} + +func (c RegistrarClient) GetNodeByTwinID(id uint64) (node Node, err error) { + return +} + +func (c RegistrarClient) ListNodes(opts ...NodeOpts) (nodes []Node, err error) { + return +} + +type nodeCfg struct { + nodeID uint64 + farmID uint64 + twinID uint64 + status string + healthy bool + page uint32 + size uint32 +} + +type NodeOpts func(*nodeCfg) + +func NodeWithNodeID(id uint64) NodeOpts { + return func(n *nodeCfg) { + n.nodeID = id + } +} + +func NodeWithFarmID(id uint64) NodeOpts { + return func(n *nodeCfg) { + n.farmID = id + } +} + +func NodeWithStatus(status string) NodeOpts { + return func(n *nodeCfg) { + n.status = status + } +} + +func NodeHealthy() NodeOpts { + return func(n *nodeCfg) { + n.healthy = true + } +} + +func NodeWithTwinID(id uint64) NodeOpts { + return func(n *nodeCfg) { + n.twinID = id + } +} + +func NodeWithPage(page uint32) NodeOpts { + return func(n *nodeCfg) { + n.page = page + } +} + +func NodeWithSize(size uint32) NodeOpts { + return func(n *nodeCfg) { + n.size = size + } +} diff --git a/node-registrar/client/zos_version.go b/node-registrar/client/zos_version.go new file mode 100644 index 0000000..18339db --- /dev/null +++ b/node-registrar/client/zos_version.go @@ -0,0 +1,108 @@ +package client + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "net/http" + "net/url" + "strings" + + "github.com/pkg/errors" +) + +func (c RegistrarClient) GetZosVersion() (version ZosVersion, err error) { + return c.getZosVersion() +} + +func (c RegistrarClient) SetZosVersion(v string, safeToUpgrade bool) (err error) { + return c.setZosVersion(v, safeToUpgrade) +} + +func (c RegistrarClient) getZosVersion() (version ZosVersion, err error) { + url, err := url.JoinPath(c.baseURL, "zos", "version") + if err != nil { + return version, errors.Wrap(err, "failed to construct registrar url") + } + + resp, err := c.httpClient.Get(url) + if err != nil { + return version, err + } + + if resp.StatusCode != http.StatusOK { + err = parseResponseError(resp.Body) + return version, errors.Wrapf(err, "failed to get zos version with status code %s", resp.Status) + } + + defer resp.Body.Close() + + var versionString string + err = json.NewDecoder(resp.Body).Decode(&versionString) + if err != nil { + return version, err + } + + versionBytes, err := base64.StdEncoding.DecodeString(versionString) + if err != nil { + return version, err + } + + correctedJSON := strings.ReplaceAll(string(versionBytes), "'", "\"") + + err = json.NewDecoder(strings.NewReader(correctedJSON)).Decode(&version) + if err != nil { + return version, err + } + + return +} + +func (c RegistrarClient) setZosVersion(v string, safeToUpgrade bool) (err error) { + url, err := url.JoinPath(c.baseURL, "zos", "version") + if err != nil { + return errors.Wrap(err, "failed to construct registrar url") + } + + version := ZosVersion{ + Version: v, + SafeToUpgrade: safeToUpgrade, + } + + jsonData, err := json.Marshal(version) + if err != nil { + return errors.Wrap(err, "failed to marshal zos version") + } + + encodedVersion := struct { + Version string `json:"version"` + }{ + Version: base64.StdEncoding.EncodeToString(jsonData), + } + + jsonData, err = json.Marshal(encodedVersion) + if err != nil { + return errors.Wrap(err, "failed to marshal zos version in hex format") + } + + req, err := http.NewRequest("PUT", url, bytes.NewReader(jsonData)) + if err != nil { + return errors.Wrap(err, "failed to construct http request to the registrar") + } + + req.Header.Set("X-Auth", c.signRequest()) + req.Header.Set("Content-Type", "application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + return errors.Wrap(err, "failed to send request to get zos version from the registrar") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return parseResponseError(resp.Body) + } + + return +} From 12c538058dd9e2e022005349b12693803ac29709 Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Mon, 17 Feb 2025 15:30:41 +0200 Subject: [PATCH 02/27] add sign request and parse response helpers --- node-registrar/client/utils.go | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 node-registrar/client/utils.go diff --git a/node-registrar/client/utils.go b/node-registrar/client/utils.go new file mode 100644 index 0000000..910d1ec --- /dev/null +++ b/node-registrar/client/utils.go @@ -0,0 +1,39 @@ +package client + +import ( + "crypto/ed25519" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "time" + + "github.com/pkg/errors" +) + +func (c RegistrarClient) signRequest() (authHeader string) { + timestamp := time.Now().Unix() + challenge := []byte(fmt.Sprintf("%d:%v", timestamp, c.twinID)) + + signature := ed25519.Sign(c.privateKey, challenge) + + authHeader = fmt.Sprintf( + "%s:%s", + base64.StdEncoding.EncodeToString(challenge), + base64.StdEncoding.EncodeToString(signature), + ) + return +} + +func parseResponseError(body io.Reader) (err error) { + errResp := struct { + Error string `json:"error"` + }{} + + err = json.NewDecoder(body).Decode(&errResp) + if err != nil { + return errors.Wrap(err, "failed to parse response error") + } + + return errors.New(errResp.Error) +} From 4dd5b29ce990d2530f79bd1ee8812f01965b5cd5 Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Mon, 17 Feb 2025 15:41:17 +0200 Subject: [PATCH 03/27] add signature for farm functions --- node-registrar/client/farm.go | 61 +++++++++++++++++++++++++++++++ node-registrar/client/types.go | 66 ++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 node-registrar/client/farm.go create mode 100644 node-registrar/client/types.go diff --git a/node-registrar/client/farm.go b/node-registrar/client/farm.go new file mode 100644 index 0000000..6c1be81 --- /dev/null +++ b/node-registrar/client/farm.go @@ -0,0 +1,61 @@ +package client + +import "fmt" + +var ErrorFarmNotFround = fmt.Errorf("failed to get requested farm from node regiatrar") + +func (c RegistrarClient) CreateFarm(farmName string, twinID uint64, dedicated bool) (farm Farm, err error) { + return +} + +func (c RegistrarClient) UpdateFarm(farmID uint64, farmName string) (err error) { + return +} + +func (c RegistrarClient) GetFarm(id uint64) (farm Farm, err error) { + return +} + +func (c RegistrarClient) ListFarms(opts ...FarmOpts) (farms []Farm, err error) { + return +} + +type farmCfg struct { + farmName string + farmID uint64 + twinID uint64 + page uint32 + size uint32 +} + +type FarmOpts func(*farmCfg) + +func FarmWithName(name string) FarmOpts { + return func(n *farmCfg) { + n.farmName = name + } +} + +func FarmWithFarmID(id uint64) FarmOpts { + return func(n *farmCfg) { + n.farmID = id + } +} + +func FarmWithTwinID(id uint64) FarmOpts { + return func(n *farmCfg) { + n.twinID = id + } +} + +func FarmWithPage(page uint32) FarmOpts { + return func(n *farmCfg) { + n.page = page + } +} + +func FarmWithSize(size uint32) FarmOpts { + return func(n *farmCfg) { + n.size = size + } +} diff --git a/node-registrar/client/types.go b/node-registrar/client/types.go new file mode 100644 index 0000000..8c8d6d0 --- /dev/null +++ b/node-registrar/client/types.go @@ -0,0 +1,66 @@ +package client + +import ( + "time" +) + +type Account struct { + TwinID uint64 `json:"twin_id"` + Relays []string `json:"relays"` // Optional list of relay domains + RMBEncKey string `json:"rmb_enc_key"` // Optional base64 encoded public key for rmb communication + PublicKey string `json:"public_key"` +} + +type Farm struct { + FarmID uint64 `json:"farm_id"` + FarmName string `json:"farm_name"` + TwinID uint64 `json:"twin_id"` + Dedicated bool `json:"dedicated"` +} + +type Node struct { + NodeID uint64 `json:"node_id"` + FarmID uint64 `json:"farm_id"` + TwinID uint64 `json:"twin_id"` + Location Location `json:"location"` + Resources Resources `json:"resources"` + Interfaces []Interface `json:"interface"` + SecureBoot bool `json:"secure_boot"` + Virtualized bool `json:"virtualized"` + SerialNumber string `json:"serial_number"` + UptimeReports []UptimeReport `json:"uptime"` + Approved bool +} + +type UptimeReport struct { + ID uint64 + NodeID uint64 `json:"node_id"` + Duration time.Duration `json:"duration"` + Timestamp time.Time `json:"timestamp"` + WasRestart bool `json:"was_restart"` +} + +type ZosVersion struct { + Version string `json:"version"` + SafeToUpgrade bool `json:"safe_to_upgrade"` +} + +type Interface struct { + Name string `json:"name"` + Mac string `json:"mac"` + IPs string `json:"ips"` +} + +type Resources struct { + HRU uint64 `json:"hru"` + SRU uint64 `json:"sru"` + CRU uint64 `json:"cru"` + MRU uint64 `json:"mru"` +} + +type Location struct { + Country string `json:"country"` + City string `json:"city"` + Longitude string `json:"longitude"` + Latitude string `json:"latitude"` +} From 4fe920656965e56def642c3f2e6e132ca291fc42 Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Mon, 17 Feb 2025 17:05:06 +0200 Subject: [PATCH 04/27] add account methods implementation --- node-registrar/client/account.go | 196 ++++++++++++++++++++++++++- node-registrar/client/client.go | 13 +- node-registrar/client/utils.go | 6 +- node-registrar/client/zos_version.go | 3 +- 4 files changed, 203 insertions(+), 15 deletions(-) diff --git a/node-registrar/client/account.go b/node-registrar/client/account.go index 349bbac..e1d87c9 100644 --- a/node-registrar/client/account.go +++ b/node-registrar/client/account.go @@ -1,31 +1,215 @@ package client import ( + "bytes" + "encoding/base64" + "encoding/json" "fmt" + "net/http" + "net/url" + "time" + + "github.com/pkg/errors" ) var ErrorAccountNotFround = fmt.Errorf("failed to get requested account from node regiatrar") func (c RegistrarClient) CreateAccount(relays []string, rmbEncKey string) (account Account, err error) { - return c.createTwin(relays, rmbEncKey) + return c.createAccount(relays, rmbEncKey) +} + +func (c RegistrarClient) GetAccount(id uint64) (account Account, err error) { + return c.getAccount(id) +} + +func (c RegistrarClient) GetAccountByPK(pk []byte) (account Account, err error) { + return c.getAccountByPK(pk) } func (c RegistrarClient) UpdateAccount(relays []string, rmbEncKey string) (err error) { - return + return c.updateAccount(relays, rmbEncKey) +} + +func (c RegistrarClient) EnsureAccount(pk []byte, relays []string, rmbEncKey string) (account Account, err error) { + return c.ensureAccount(pk, relays, rmbEncKey) } -func (c RegistrarClient) EnsureAccount(pk []byte) (account Account, err error) { +func (c *RegistrarClient) createAccount(relays []string, rmbEncKey string) (result Account, err error) { + url, err := url.JoinPath(c.baseURL, "accounts") + if err != nil { + return result, errors.Wrap(err, "failed to construct registrar url") + } + + publicKeyBase64 := base64.StdEncoding.EncodeToString(c.keyPair.publicKey) + + timestamp := time.Now().Unix() + signature := c.signRequest(timestamp) + + account := map[string]any{ + "public_key": publicKeyBase64, + "signature": signature, + "timestamp": timestamp, + "rmb_enc_key": rmbEncKey, + "relays": relays, + } + + var body bytes.Buffer + err = json.NewEncoder(&body).Encode(account) + if err != nil { + return result, errors.Wrap(err, "failed to parse request body") + } + + resp, err := c.httpClient.Post(url, "application/json", &body) + if err != nil { + return result, errors.Wrap(err, "failed to send request to the registrar") + } + + if resp.StatusCode != http.StatusCreated { + err = parseResponseError(resp.Body) + return result, errors.Wrapf(err, "failed to create account with status %s", resp.Status) + } + defer resp.Body.Close() + + err = json.NewDecoder(resp.Body).Decode(&result) + + c.twinID = result.TwinID return } -func (c RegistrarClient) GetAccount(id uint64) (account Account, err error) { +func (c RegistrarClient) getAccount(id uint64) (account Account, err error) { + url, err := url.JoinPath(c.baseURL, "accounts") + if err != nil { + return account, errors.Wrap(err, "failed to construct registrar url") + } + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return + } + + q := req.URL.Query() + q.Add("twin_id", fmt.Sprint(id)) + req.URL.RawQuery = q.Encode() + + resp, err := c.httpClient.Do(req) + if err != nil { + return + } + + if resp == nil { + return account, errors.New("failed to get account, no response received") + } + + if resp.StatusCode == http.StatusNotFound { + return account, ErrorAccountNotFround + } + + if resp.StatusCode != http.StatusOK { + err = parseResponseError(resp.Body) + return account, errors.Wrapf(err, "failed to get account by twin id with status code %s", resp.Status) + } + defer resp.Body.Close() + + err = json.NewDecoder(resp.Body).Decode(&account) return } -func (c RegistrarClient) GetAccountByPK(pk []byte) (account Account, err error) { +func (c RegistrarClient) updateAccount(relays []string, rmbEncKey string) (err error) { + url, err := url.JoinPath(c.baseURL, "accounts", fmt.Sprint(c.twinID)) + if err != nil { + return errors.Wrap(err, "failed to construct registrar url") + } + + acc := map[string]any{} + + if len(relays) != 0 { + acc["relays"] = relays + } + + if len(rmbEncKey) != 0 { + acc["rmb_enc_key"] = rmbEncKey + } + + var body bytes.Buffer + err = json.NewEncoder(&body).Encode(acc) + if err != nil { + return errors.Wrap(err, "failed to parse request body") + } + + req, err := http.NewRequest("PATCH", url, &body) + if err != nil { + return + } + + req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) + req.Header.Set("Content-Type", "application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + fmt.Println("Error sending request:", err) + return + } + + if resp == nil { + return errors.New("failed to update account, no response received") + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return parseResponseError(resp.Body) + } + return } -func (c *RegistrarClient) createTwin(relays []string, rmbEncKey string) (result Account, err error) { +func (c RegistrarClient) getAccountByPK(pk []byte) (account Account, err error) { + url, err := url.JoinPath(c.baseURL, "accounts", fmt.Sprint(c.twinID)) + if err != nil { + return account, errors.Wrap(err, "failed to construct registrar url") + } + + publicKeyBase64 := base64.StdEncoding.EncodeToString(pk) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return account, err + } + + q := req.URL.Query() + q.Add("public_key", publicKeyBase64) + req.URL.RawQuery = q.Encode() + + resp, err := c.httpClient.Do(req) + if err != nil { + return account, err + } + + if resp == nil { + return account, errors.New("no response received") + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + return account, ErrorAccountNotFround + } + + if resp.StatusCode != http.StatusOK { + err = parseResponseError(resp.Body) + return account, errors.Wrapf(err, "failed to get account by public_key with status code %s", resp.Status) + } + + err = json.NewDecoder(resp.Body).Decode(&account) + + return account, err +} + +func (c RegistrarClient) ensureAccount(pk []byte, relays []string, rmbEncKey string) (account Account, err error) { + account, err = c.GetAccountByPK(pk) + if errors.Is(err, ErrorAccountNotFround) { + return c.CreateAccount(relays, rmbEncKey) + } else if err != nil { + return account, errors.Wrap(err, "failed to get account from the registrar") + } + return } diff --git a/node-registrar/client/client.go b/node-registrar/client/client.go index 48a2f79..3be0d11 100644 --- a/node-registrar/client/client.go +++ b/node-registrar/client/client.go @@ -7,9 +7,14 @@ import ( "github.com/pkg/errors" ) +type keyPair struct { + privateKey ed25519.PrivateKey + publicKey ed25519.PublicKey +} + type RegistrarClient struct { httpClient http.Client - privateKey ed25519.PrivateKey + keyPair keyPair nodeID uint64 twinID uint64 baseURL string @@ -19,18 +24,18 @@ func NewRegistrarClient(baseURL string, privateKey []byte) (cli RegistrarClient, client := http.DefaultClient sk := ed25519.NewKeyFromSeed(privateKey) - pk, ok := sk.Public().(ed25519.PublicKey) + publicKey, ok := sk.Public().(ed25519.PublicKey) if !ok { return cli, errors.Wrap(err, "failed to get public key of provided private key") } cli = RegistrarClient{ httpClient: *client, - privateKey: privateKey, + keyPair: keyPair{privateKey, publicKey}, baseURL: baseURL, } - account, err := cli.GetAccountByPK(pk) + account, err := cli.GetAccountByPK(publicKey) if errors.Is(err, ErrorAccountNotFround) { return cli, nil } else if err != nil { diff --git a/node-registrar/client/utils.go b/node-registrar/client/utils.go index 910d1ec..a56657f 100644 --- a/node-registrar/client/utils.go +++ b/node-registrar/client/utils.go @@ -6,16 +6,14 @@ import ( "encoding/json" "fmt" "io" - "time" "github.com/pkg/errors" ) -func (c RegistrarClient) signRequest() (authHeader string) { - timestamp := time.Now().Unix() +func (c RegistrarClient) signRequest(timestamp int64) (authHeader string) { challenge := []byte(fmt.Sprintf("%d:%v", timestamp, c.twinID)) - signature := ed25519.Sign(c.privateKey, challenge) + signature := ed25519.Sign(c.keyPair.privateKey, challenge) authHeader = fmt.Sprintf( "%s:%s", diff --git a/node-registrar/client/zos_version.go b/node-registrar/client/zos_version.go index 18339db..af7e66c 100644 --- a/node-registrar/client/zos_version.go +++ b/node-registrar/client/zos_version.go @@ -7,6 +7,7 @@ import ( "net/http" "net/url" "strings" + "time" "github.com/pkg/errors" ) @@ -90,7 +91,7 @@ func (c RegistrarClient) setZosVersion(v string, safeToUpgrade bool) (err error) return errors.Wrap(err, "failed to construct http request to the registrar") } - req.Header.Set("X-Auth", c.signRequest()) + req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) From b4d2cf522017c9118089c0cb6af89572f241c8e9 Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Wed, 19 Feb 2025 10:26:03 +0200 Subject: [PATCH 05/27] add farms implementation --- node-registrar/client/farm.go | 283 +++++++++++++++++++++++++++++++--- 1 file changed, 264 insertions(+), 19 deletions(-) diff --git a/node-registrar/client/farm.go b/node-registrar/client/farm.go index 6c1be81..7ab5ac2 100644 --- a/node-registrar/client/farm.go +++ b/node-registrar/client/farm.go @@ -1,61 +1,306 @@ package client -import "fmt" +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/pkg/errors" +) var ErrorFarmNotFround = fmt.Errorf("failed to get requested farm from node regiatrar") -func (c RegistrarClient) CreateFarm(farmName string, twinID uint64, dedicated bool) (farm Farm, err error) { - return +func (c RegistrarClient) CreateFarm(farmName string, twinID uint64, dedicated bool) (farmID uint64, err error) { + return c.createFarm(farmName, twinID, dedicated) } -func (c RegistrarClient) UpdateFarm(farmID uint64, farmName string) (err error) { - return +func (c RegistrarClient) UpdateFarm(farmID uint64, opts ...UpdateFarmOpts) (err error) { + return c.updateFarm(farmID, opts) } func (c RegistrarClient) GetFarm(id uint64) (farm Farm, err error) { - return + return c.getFarm(id) } -func (c RegistrarClient) ListFarms(opts ...FarmOpts) (farms []Farm, err error) { - return +func (c RegistrarClient) ListFarms(opts ...ListFarmOpts) (farms []Farm, err error) { + return c.listFarms(opts...) } type farmCfg struct { - farmName string - farmID uint64 - twinID uint64 - page uint32 - size uint32 + farmName string + farmID uint64 + twinID uint64 + dedicated bool + page uint32 + size uint32 } -type FarmOpts func(*farmCfg) +type ( + ListFarmOpts func(*farmCfg) + UpdateFarmOpts func(*farmCfg) +) -func FarmWithName(name string) FarmOpts { +func ListFarmWithName(name string) ListFarmOpts { return func(n *farmCfg) { n.farmName = name } } -func FarmWithFarmID(id uint64) FarmOpts { +func ListFarmWithFarmID(id uint64) ListFarmOpts { return func(n *farmCfg) { n.farmID = id } } -func FarmWithTwinID(id uint64) FarmOpts { +func ListFarmWithTwinID(id uint64) ListFarmOpts { return func(n *farmCfg) { n.twinID = id } } -func FarmWithPage(page uint32) FarmOpts { +func ListFarmWithDedicated() ListFarmOpts { + return func(n *farmCfg) { + n.dedicated = true + } +} + +func ListFarmWithPage(page uint32) ListFarmOpts { return func(n *farmCfg) { n.page = page } } -func FarmWithSize(size uint32) FarmOpts { +func ListFarmWithSize(size uint32) ListFarmOpts { return func(n *farmCfg) { n.size = size } } + +func UpdateFarmWithName(name string) UpdateFarmOpts { + return func(n *farmCfg) { + n.farmName = name + } +} + +func UpdateFarmWithDedicated() UpdateFarmOpts { + return func(n *farmCfg) { + n.dedicated = true + } +} + +func (c RegistrarClient) createFarm(farmName string, twinID uint64, dedicated bool) (farmID uint64, err error) { + url, err := url.JoinPath(c.baseURL, "farms") + if err != nil { + return farmID, errors.Wrap(err, "failed to construct registrar url") + } + + data := map[string]any{ + "farm_name": farmName, + "twin_id": twinID, + "dedicated": dedicated, + } + + var body bytes.Buffer + err = json.NewEncoder(&body).Encode(data) + if err != nil { + return farmID, errors.Wrap(err, "failed to encode request body") + } + + req, err := http.NewRequest("POST", url, &body) + if err != nil { + return farmID, errors.Wrap(err, "failed to construct http request to the registrar") + } + + req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) + req.Header.Set("Content-Type", "application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + return farmID, errors.Wrap(err, "failed to send request to create farm") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusCreated { + err = parseResponseError(resp.Body) + return farmID, fmt.Errorf("failed to create farm with status code %s", resp.Status) + } + + var result uint64 + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return farmID, errors.Wrap(err, "failed to decode response body") + } + + return result, nil +} + +func (c RegistrarClient) updateFarm(farmID uint64, opts []UpdateFarmOpts) (err error) { + url, err := url.JoinPath(c.baseURL, "farms", fmt.Sprint(farmID)) + if err != nil { + return errors.Wrap(err, "failed to construct registrar url") + } + + var body bytes.Buffer + data := parseUpdateFarmOpts(opts...) + + err = json.NewEncoder(&body).Encode(data) + if err != nil { + return errors.Wrap(err, "failed to encode request body") + } + + req, err := http.NewRequest("PATCH", url, &body) + if err != nil { + return errors.Wrap(err, "failed to construct http request to the registrar") + } + + req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) + req.Header.Set("Content-Type", "application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + return errors.Wrap(err, "failed to send request to update farm") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + err = parseResponseError(resp.Body) + return fmt.Errorf("failed to create farm with status code %s", resp.Status) + } + + var result uint64 + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return errors.Wrap(err, "failed to decode response body") + } + + return +} + +func (c RegistrarClient) getFarm(id uint64) (farm Farm, err error) { + url, err := url.JoinPath(c.baseURL, "farms", fmt.Sprint(id)) + if err != nil { + return farm, errors.Wrap(err, "failed to construct registrar url") + } + resp, err := c.httpClient.Get(url) + if err != nil { + return farm, err + } + + if resp.StatusCode == http.StatusNotFound { + return farm, ErrorFarmNotFround + } + + if resp.StatusCode != http.StatusOK { + err = parseResponseError(resp.Body) + return farm, errors.Wrapf(err, "failed to get farm with status code %s", resp.Status) + } + defer resp.Body.Close() + + err = json.NewDecoder(resp.Body).Decode(&farm) + if err != nil { + return farm, err + } + + return +} + +func (c RegistrarClient) listFarms(opts ...ListFarmOpts) (farms []Farm, err error) { + url, err := url.JoinPath(c.baseURL, "farms") + if err != nil { + return farms, errors.Wrap(err, "failed to construct registrar url") + } + + data := parseListFarmOpts(opts) + + var body bytes.Buffer + err = json.NewEncoder(&body).Encode(data) + if err != nil { + return farms, errors.Wrap(err, "failed to encode request body") + } + + req, err := http.NewRequest("GET", url, &body) + if err != nil { + return farms, errors.Wrap(err, "failed to construct http request to the registrar") + } + + resp, err := c.httpClient.Do(req) + if err != nil { + return farms, errors.Wrap(err, "failed to send request to list farm") + } + + if resp.StatusCode != http.StatusOK { + err = parseResponseError(resp.Body) + return farms, errors.Wrapf(err, "failed to get list farms with status code %s", resp.Status) + } + defer resp.Body.Close() + err = json.NewDecoder(resp.Body).Decode(&farms) + if err != nil { + return farms, errors.Wrap(err, "failed to decode response body") + } + + return +} + +func parseListFarmOpts(opts []ListFarmOpts) map[string]any { + cfg := farmCfg{ + farmName: "", + farmID: 0, + twinID: 0, + dedicated: false, + page: 1, + size: 50, + } + + for _, opt := range opts { + opt(&cfg) + } + + data := map[string]any{} + + if len(cfg.farmName) != 0 { + data["farm_name"] = cfg.farmName + } + + if cfg.farmID != 0 { + data["farm_id"] = cfg.farmID + } + + if cfg.twinID != 0 { + data["twin_id"] = cfg.twinID + } + + if cfg.dedicated { + data["dedicated"] = true + } + + data["page"] = cfg.page + data["size"] = cfg.size + + return data +} + +func parseUpdateFarmOpts(opts ...UpdateFarmOpts) map[string]any { + cfg := farmCfg{ + farmName: "", + dedicated: false, + } + + for _, opt := range opts { + opt(&cfg) + } + + data := map[string]any{} + + if len(cfg.farmName) != 0 { + data["farm_name"] = cfg.farmName + } + + if cfg.dedicated { + data["dedicated"] = true + } + + return data +} From 24fe63579255e977b6ac43c4ebf72e88d5a23c19 Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Wed, 19 Feb 2025 13:10:47 +0200 Subject: [PATCH 06/27] add node implementation --- node-registrar/client/account.go | 16 +- node-registrar/client/node.go | 328 +++++++++++++++++++++++++++---- 2 files changed, 307 insertions(+), 37 deletions(-) diff --git a/node-registrar/client/account.go b/node-registrar/client/account.go index e1d87c9..8697701 100644 --- a/node-registrar/client/account.go +++ b/node-registrar/client/account.go @@ -163,7 +163,7 @@ func (c RegistrarClient) updateAccount(relays []string, rmbEncKey string) (err e } func (c RegistrarClient) getAccountByPK(pk []byte) (account Account, err error) { - url, err := url.JoinPath(c.baseURL, "accounts", fmt.Sprint(c.twinID)) + url, err := url.JoinPath(c.baseURL, "accounts") if err != nil { return account, errors.Wrap(err, "failed to construct registrar url") } @@ -213,3 +213,17 @@ func (c RegistrarClient) ensureAccount(pk []byte, relays []string, rmbEncKey str return } + +func (c *RegistrarClient) ensureTwinID() error { + if c.twinID != 0 { + return nil + } + + twin, err := c.getAccountByPK(c.keyPair.publicKey) + if err != nil { + return errors.Wrap(err, "failed to get the account of the node, registrar client was not set up properly") + } + + c.twinID = twin.TwinID + return nil +} diff --git a/node-registrar/client/node.go b/node-registrar/client/node.go index 32e1a99..56b4e66 100644 --- a/node-registrar/client/node.go +++ b/node-registrar/client/node.go @@ -1,101 +1,357 @@ package client -import "fmt" +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" + "reflect" + "time" + + "github.com/pkg/errors" +) var ErrorNodeNotFround = fmt.Errorf("failed to get requested node from node regiatrar") func (c RegistrarClient) RegisterNode( farmID uint64, twinID uint64, - interfaces Interface, + interfaces []Interface, location Location, resources Resources, serialNumber string, secureBoot, virtualized bool, -) (node Node, err error) { - return +) (nodeID uint64, err error) { + return c.registerNode(farmID, twinID, interfaces, location, resources, serialNumber, secureBoot, virtualized) } -func (c RegistrarClient) UpdateNode( - nodeID uint64, - farmID uint64, - interfaces Interface, - location Location, - resources Resources, - serialNumber string, - secureBoot, - virtualized bool, -) (err error) { - return +func (c RegistrarClient) UpdateNode(opts ...UpdateNodeOpts) (err error) { + return c.updateNode(opts) } -func (c RegistrarClient) ReportUptime(id uint64, report UptimeReport) (err error) { - return +func (c RegistrarClient) ReportUptime(report UptimeReport) (err error) { + return c.reportUptime(report) } func (c RegistrarClient) GetNode(id uint64) (node Node, err error) { - return + return c.getNode(id) } func (c RegistrarClient) GetNodeByTwinID(id uint64) (node Node, err error) { - return + return c.getNodeByTwinID(id) } -func (c RegistrarClient) ListNodes(opts ...NodeOpts) (nodes []Node, err error) { - return +func (c RegistrarClient) ListNodes(opts ...ListNodeOpts) (nodes []Node, err error) { + return c.ListNodes(opts) } type nodeCfg struct { - nodeID uint64 - farmID uint64 - twinID uint64 - status string - healthy bool - page uint32 - size uint32 + nodeID uint64 + farmID uint64 + twinID uint64 + status string + healthy bool + Location Location + Resources Resources + Interfaces []Interface + SecureBoot bool + Virtualized bool + SerialNumber string + UptimeReports []UptimeReport + Approved bool + page uint32 + size uint32 } -type NodeOpts func(*nodeCfg) +type ( + ListNodeOpts func(*nodeCfg) + UpdateNodeOpts func(*nodeCfg) +) -func NodeWithNodeID(id uint64) NodeOpts { +func ListNodesWithNodeID(id uint64) ListNodeOpts { return func(n *nodeCfg) { n.nodeID = id } } -func NodeWithFarmID(id uint64) NodeOpts { +func ListNodesWithFarmID(id uint64) ListNodeOpts { return func(n *nodeCfg) { n.farmID = id } } -func NodeWithStatus(status string) NodeOpts { +func ListNodesWithStatus(status string) ListNodeOpts { return func(n *nodeCfg) { n.status = status } } -func NodeHealthy() NodeOpts { +func ListNodesWithHealthy() ListNodeOpts { return func(n *nodeCfg) { n.healthy = true } } -func NodeWithTwinID(id uint64) NodeOpts { +func ListNodesWithTwinID(id uint64) ListNodeOpts { return func(n *nodeCfg) { n.twinID = id } } -func NodeWithPage(page uint32) NodeOpts { +func ListNodesWithPage(page uint32) ListNodeOpts { return func(n *nodeCfg) { n.page = page } } -func NodeWithSize(size uint32) NodeOpts { +func ListNodesWithSize(size uint32) ListNodeOpts { return func(n *nodeCfg) { n.size = size } } + +func UpdateNodesWithFarmID(id uint64) UpdateNodeOpts { + return func(n *nodeCfg) { + n.farmID = id + } +} + +func UpdateNodesWithInterfaces(interfaces []Interface) UpdateNodeOpts { + return func(n *nodeCfg) { + n.Interfaces = interfaces + } +} + +func UpdateNodesWithResources(resources Resources) UpdateNodeOpts { + return func(n *nodeCfg) { + n.Resources = resources + } +} + +func UpdateNodesWithSerialNumber(serialNumbe string) UpdateNodeOpts { + return func(n *nodeCfg) { + n.SerialNumber = serialNumbe + } +} + +func UpdateNodesWithSecureBoot() UpdateNodeOpts { + return func(n *nodeCfg) { + n.SecureBoot = true + } +} + +func UpdateNodesWithVirtualized() UpdateNodeOpts { + return func(n *nodeCfg) { + n.Virtualized = true + } +} + +func UpdateNodeWithStatus(status string) UpdateNodeOpts { + return func(n *nodeCfg) { + n.status = status + } +} + +func UpdateNodeWithHealthy() UpdateNodeOpts { + return func(n *nodeCfg) { + n.healthy = true + } +} + +func (c RegistrarClient) registerNode( + farmID uint64, + twinID uint64, + interfaces []Interface, + location Location, + resources Resources, + serialNumber string, + secureBoot, + virtualized bool, +) (nodeID uint64, err error) { + url, err := url.JoinPath(c.baseURL, "nodes") + if err != nil { + return nodeID, errors.Wrap(err, "failed to construct registrar url") + } + + data := Node{ + FarmID: farmID, + TwinID: twinID, + Location: location, + Resources: resources, + Interfaces: interfaces, + SecureBoot: secureBoot, + Virtualized: virtualized, + SerialNumber: serialNumber, + } + + var body bytes.Buffer + err = json.NewEncoder(&body).Encode(data) + if err != nil { + return nodeID, errors.Wrap(err, "failed to encode request body") + } + + req, err := http.NewRequest("POST", url, &body) + if err != nil { + return nodeID, errors.Wrap(err, "failed to construct http request to the registrar") + } + + req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) + req.Header.Set("Content-Type", "application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + return nodeID, errors.Wrap(err, "failed to send request to registrer the node") + } + + if resp == nil || resp.StatusCode != http.StatusCreated { + err = parseResponseError(resp.Body) + return 0, errors.Wrapf(err, "failed to update node on the registrar with status code %s", resp.Status) + } + defer resp.Body.Close() + + err = json.NewDecoder(resp.Body).Decode(&nodeID) + + c.nodeID = nodeID + return +} + +func (c RegistrarClient) updateNode(opts []UpdateNodeOpts) (err error) { + err = c.ensureNodeID() + if err != nil { + return err + } + + node, err := c.getNode(c.nodeID) + if err != nil { + return err + } + + url, err := url.JoinPath(c.baseURL, "nodes", fmt.Sprint(c.nodeID)) + if err != nil { + return errors.Wrap(err, "failed to construct registrar url") + } + + node = c.parseUpdateNodeOpts(node, opts) + + var body bytes.Buffer + err = json.NewEncoder(&body).Encode(node) + if err != nil { + return errors.Wrap(err, "failed to encode request body") + } + + req, err := http.NewRequest("PATCH", url, &body) + if err != nil { + return errors.Wrap(err, "failed to construct http request to the registrar") + } + + req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) + req.Header.Set("Content-Type", "application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + return errors.Wrap(err, "failed to send request to update node") + } + + if resp == nil || resp.StatusCode != http.StatusOK { + err = parseResponseError(resp.Body) + return errors.Wrapf(err, "failed to update node with twin id %d with status code %s", c.twinID, resp.Status) + } + defer resp.Body.Close() + + return +} + +func (c RegistrarClient) reportUptime(report UptimeReport) (err error) { + err = c.ensureNodeID() + if err != nil { + return err + } + + url, err := url.JoinPath(c.baseURL, "nodes", fmt.Sprint(c.nodeID)) + if err != nil { + return errors.Wrap(err, "failed to construct registrar url") + } + + return +} + +func (c RegistrarClient) getNode(id uint64) (node Node, err error) { + url, err := url.JoinPath(c.baseURL, "nodes", fmt.Sprint(id)) + if err != nil { + return node, errors.Wrap(err, "failed to construct registrar url") + } + + return +} + +func (c RegistrarClient) getNodeByTwinID(id uint64) (node Node, err error) { + url, err := url.JoinPath(c.baseURL, "nodes") + if err != nil { + return node, errors.Wrap(err, "failed to construct registrar url") + } + + return +} + +func (c RegistrarClient) listNodes(opts ...ListNodeOpts) (nodes []Node, err error) { + url, err := url.JoinPath(c.baseURL, "nodes") + if err != nil { + return nodes, errors.Wrap(err, "failed to construct registrar url") + } + return +} + +func (c *RegistrarClient) ensureNodeID() error { + if c.nodeID != 0 { + return nil + } + + err := c.ensureTwinID() + if err != nil { + return err + } + + node, err := c.getNodeByTwinID(c.twinID) + if err != nil { + return errors.Wrapf(err, "failed to get the node id, registrar client was set up with a normal account not a node") + } + + c.nodeID = node.NodeID + return nil +} + +func (c RegistrarClient) parseUpdateNodeOpts(node Node, opts []UpdateNodeOpts) Node { + cfg := nodeCfg{ + farmID: 0, + Location: Location{}, + Resources: Resources{}, + Interfaces: []Interface{}, + SecureBoot: false, + Virtualized: false, + Approved: false, + } + + for _, opt := range opts { + opt(&cfg) + } + + if cfg.farmID != 0 { + node.FarmID = cfg.farmID + } + + if !reflect.DeepEqual(cfg.Location, Location{}) { + node.Location = cfg.Location + } + + if !reflect.DeepEqual(cfg.Resources, Resources{}) { + node.Resources = cfg.Resources + } + + if len(cfg.Interfaces) != 0 { + node.Interfaces = cfg.Interfaces + } + + return node +} From 08f4d7ad91651234b0ffcd6c07356702b8b93eec Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Wed, 19 Feb 2025 16:52:10 +0200 Subject: [PATCH 07/27] update accounts and farms to filter and update with options --- node-registrar/client/account.go | 144 +++++++++++++++--------- node-registrar/client/farm.go | 32 +++--- node-registrar/client/node.go | 187 +++++++++++++++++++++++++++++-- 3 files changed, 288 insertions(+), 75 deletions(-) diff --git a/node-registrar/client/account.go b/node-registrar/client/account.go index 8697701..4c6af57 100644 --- a/node-registrar/client/account.go +++ b/node-registrar/client/account.go @@ -26,18 +26,18 @@ func (c RegistrarClient) GetAccountByPK(pk []byte) (account Account, err error) return c.getAccountByPK(pk) } -func (c RegistrarClient) UpdateAccount(relays []string, rmbEncKey string) (err error) { - return c.updateAccount(relays, rmbEncKey) +func (c RegistrarClient) UpdateAccount(opts ...UpdateAccountOpts) (err error) { + return c.updateAccount(opts) } func (c RegistrarClient) EnsureAccount(pk []byte, relays []string, rmbEncKey string) (account Account, err error) { return c.ensureAccount(pk, relays, rmbEncKey) } -func (c *RegistrarClient) createAccount(relays []string, rmbEncKey string) (result Account, err error) { +func (c *RegistrarClient) createAccount(relays []string, rmbEncKey string) (account Account, err error) { url, err := url.JoinPath(c.baseURL, "accounts") if err != nil { - return result, errors.Wrap(err, "failed to construct registrar url") + return account, errors.Wrap(err, "failed to construct registrar url") } publicKeyBase64 := base64.StdEncoding.EncodeToString(c.keyPair.publicKey) @@ -45,7 +45,7 @@ func (c *RegistrarClient) createAccount(relays []string, rmbEncKey string) (resu timestamp := time.Now().Unix() signature := c.signRequest(timestamp) - account := map[string]any{ + data := map[string]any{ "public_key": publicKeyBase64, "signature": signature, "timestamp": timestamp, @@ -54,25 +54,25 @@ func (c *RegistrarClient) createAccount(relays []string, rmbEncKey string) (resu } var body bytes.Buffer - err = json.NewEncoder(&body).Encode(account) + err = json.NewEncoder(&body).Encode(data) if err != nil { - return result, errors.Wrap(err, "failed to parse request body") + return account, errors.Wrap(err, "failed to parse request body") } resp, err := c.httpClient.Post(url, "application/json", &body) if err != nil { - return result, errors.Wrap(err, "failed to send request to the registrar") + return account, errors.Wrap(err, "failed to send request to the registrar") } if resp.StatusCode != http.StatusCreated { err = parseResponseError(resp.Body) - return result, errors.Wrapf(err, "failed to create account with status %s", resp.Status) + return account, errors.Wrapf(err, "failed to create account with status %s", resp.Status) } defer resp.Body.Close() - err = json.NewDecoder(resp.Body).Decode(&result) + err = json.NewDecoder(resp.Body).Decode(&account) - c.twinID = result.TwinID + c.twinID = account.TwinID return } @@ -114,24 +114,57 @@ func (c RegistrarClient) getAccount(id uint64) (account Account, err error) { return } -func (c RegistrarClient) updateAccount(relays []string, rmbEncKey string) (err error) { - url, err := url.JoinPath(c.baseURL, "accounts", fmt.Sprint(c.twinID)) +func (c RegistrarClient) getAccountByPK(pk []byte) (account Account, err error) { + url, err := url.JoinPath(c.baseURL, "accounts") if err != nil { - return errors.Wrap(err, "failed to construct registrar url") + return account, errors.Wrap(err, "failed to construct registrar url") + } + + publicKeyBase64 := base64.StdEncoding.EncodeToString(pk) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return account, err + } + + q := req.URL.Query() + q.Add("public_key", publicKeyBase64) + req.URL.RawQuery = q.Encode() + + resp, err := c.httpClient.Do(req) + if err != nil { + return account, err } - acc := map[string]any{} + if resp == nil { + return account, errors.New("no response received") + } + defer resp.Body.Close() - if len(relays) != 0 { - acc["relays"] = relays + if resp.StatusCode == http.StatusNotFound { + return account, ErrorAccountNotFround } - if len(rmbEncKey) != 0 { - acc["rmb_enc_key"] = rmbEncKey + if resp.StatusCode != http.StatusOK { + err = parseResponseError(resp.Body) + return account, errors.Wrapf(err, "failed to get account by public_key with status code %s", resp.Status) + } + + err = json.NewDecoder(resp.Body).Decode(&account) + + return account, err +} + +func (c RegistrarClient) updateAccount(opts []UpdateAccountOpts) (err error) { + url, err := url.JoinPath(c.baseURL, "accounts", fmt.Sprint(c.twinID)) + if err != nil { + return errors.Wrap(err, "failed to construct registrar url") } var body bytes.Buffer - err = json.NewEncoder(&body).Encode(acc) + data := parseUpdateAccountOpts(opts) + + err = json.NewEncoder(&body).Encode(data) if err != nil { return errors.Wrap(err, "failed to parse request body") } @@ -162,45 +195,25 @@ func (c RegistrarClient) updateAccount(relays []string, rmbEncKey string) (err e return } -func (c RegistrarClient) getAccountByPK(pk []byte) (account Account, err error) { - url, err := url.JoinPath(c.baseURL, "accounts") - if err != nil { - return account, errors.Wrap(err, "failed to construct registrar url") - } - - publicKeyBase64 := base64.StdEncoding.EncodeToString(pk) - - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return account, err - } - - q := req.URL.Query() - q.Add("public_key", publicKeyBase64) - req.URL.RawQuery = q.Encode() - - resp, err := c.httpClient.Do(req) - if err != nil { - return account, err - } +type accountCfg struct { + relays []string + rmbEncKey string +} - if resp == nil { - return account, errors.New("no response received") - } - defer resp.Body.Close() +type ( + UpdateAccountOpts func(*accountCfg) +) - if resp.StatusCode == http.StatusNotFound { - return account, ErrorAccountNotFround +func UpdateAccountWithRelays(relays []string) UpdateAccountOpts { + return func(n *accountCfg) { + n.relays = relays } +} - if resp.StatusCode != http.StatusOK { - err = parseResponseError(resp.Body) - return account, errors.Wrapf(err, "failed to get account by public_key with status code %s", resp.Status) +func UpdateAccountWithRMBEncKey(rmbEncKey string) UpdateAccountOpts { + return func(n *accountCfg) { + n.rmbEncKey = rmbEncKey } - - err = json.NewDecoder(resp.Body).Decode(&account) - - return account, err } func (c RegistrarClient) ensureAccount(pk []byte, relays []string, rmbEncKey string) (account Account, err error) { @@ -227,3 +240,26 @@ func (c *RegistrarClient) ensureTwinID() error { c.twinID = twin.TwinID return nil } + +func parseUpdateAccountOpts(opts []UpdateAccountOpts) map[string]any { + cfg := accountCfg{ + rmbEncKey: "", + relays: []string{}, + } + + for _, opt := range opts { + opt(&cfg) + } + + data := map[string]any{} + + if len(cfg.relays) != 0 { + data["relays"] = cfg.relays + } + + if len(cfg.rmbEncKey) != 0 { + data["rmb_enc_key"] = cfg.rmbEncKey + } + + return data +} diff --git a/node-registrar/client/farm.go b/node-registrar/client/farm.go index 7ab5ac2..9e28eef 100644 --- a/node-registrar/client/farm.go +++ b/node-registrar/client/farm.go @@ -97,10 +97,10 @@ func (c RegistrarClient) createFarm(farmName string, twinID uint64, dedicated bo return farmID, errors.Wrap(err, "failed to construct registrar url") } - data := map[string]any{ - "farm_name": farmName, - "twin_id": twinID, - "dedicated": dedicated, + data := Farm{ + FarmName: farmName, + TwinID: twinID, + Dedicated: dedicated, } var body bytes.Buffer @@ -129,12 +129,15 @@ func (c RegistrarClient) createFarm(farmName string, twinID uint64, dedicated bo return farmID, fmt.Errorf("failed to create farm with status code %s", resp.Status) } - var result uint64 + result := struct { + FarmID uint64 `json:"farm_id"` + }{} + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return farmID, errors.Wrap(err, "failed to decode response body") } - return result, nil + return result.FarmID, nil } func (c RegistrarClient) updateFarm(farmID uint64, opts []UpdateFarmOpts) (err error) { @@ -144,7 +147,7 @@ func (c RegistrarClient) updateFarm(farmID uint64, opts []UpdateFarmOpts) (err e } var body bytes.Buffer - data := parseUpdateFarmOpts(opts...) + data := parseUpdateFarmOpts(opts) err = json.NewEncoder(&body).Encode(data) if err != nil { @@ -215,16 +218,17 @@ func (c RegistrarClient) listFarms(opts ...ListFarmOpts) (farms []Farm, err erro data := parseListFarmOpts(opts) - var body bytes.Buffer - err = json.NewEncoder(&body).Encode(data) + req, err := http.NewRequest("GET", url, nil) if err != nil { - return farms, errors.Wrap(err, "failed to encode request body") + return farms, errors.Wrap(err, "failed to construct http request to the registrar") } - req, err := http.NewRequest("GET", url, &body) - if err != nil { - return farms, errors.Wrap(err, "failed to construct http request to the registrar") + q := req.URL.Query() + + for key, val := range data { + q.Add(key, fmt.Sprint(val)) } + req.URL.RawQuery = q.Encode() resp, err := c.httpClient.Do(req) if err != nil { @@ -282,7 +286,7 @@ func parseListFarmOpts(opts []ListFarmOpts) map[string]any { return data } -func parseUpdateFarmOpts(opts ...UpdateFarmOpts) map[string]any { +func parseUpdateFarmOpts(opts []UpdateFarmOpts) map[string]any { cfg := farmCfg{ farmName: "", dedicated: false, diff --git a/node-registrar/client/node.go b/node-registrar/client/node.go index 56b4e66..a08f150 100644 --- a/node-registrar/client/node.go +++ b/node-registrar/client/node.go @@ -44,7 +44,7 @@ func (c RegistrarClient) GetNodeByTwinID(id uint64) (node Node, err error) { } func (c RegistrarClient) ListNodes(opts ...ListNodeOpts) (nodes []Node, err error) { - return c.ListNodes(opts) + return c.listNodes(opts) } type nodeCfg struct { @@ -211,10 +211,14 @@ func (c RegistrarClient) registerNode( } defer resp.Body.Close() - err = json.NewDecoder(resp.Body).Decode(&nodeID) + result := struct { + NodeID uint64 `json:"node_id"` + }{} - c.nodeID = nodeID - return + err = json.NewDecoder(resp.Body).Decode(&result) + + c.nodeID = result.NodeID + return result.NodeID, err } func (c RegistrarClient) updateNode(opts []UpdateNodeOpts) (err error) { @@ -274,6 +278,38 @@ func (c RegistrarClient) reportUptime(report UptimeReport) (err error) { return errors.Wrap(err, "failed to construct registrar url") } + timestamp := time.Now().Unix() + var body bytes.Buffer + + data := map[string]any{ + "uptime": report, + "timestamp": timestamp, + } + + err = json.NewEncoder(&body).Encode(data) + if err != nil { + return errors.Wrap(err, "failed to encode request body") + } + + req, err := http.NewRequest("PATCH", url, &body) + if err != nil { + return errors.Wrap(err, "failed to construct http request to the registrar") + } + + req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) + req.Header.Set("Content-Type", "application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + return errors.Wrap(err, "failed to send request to update uptime of the node") + } + + if resp == nil || resp.StatusCode != http.StatusOK { + err = parseResponseError(resp.Body) + return errors.Wrapf(err, "failed to update node uptime for node with id %d with status code %s", c.nodeID, resp.Status) + } + defer resp.Body.Close() + return } @@ -283,6 +319,26 @@ func (c RegistrarClient) getNode(id uint64) (node Node, err error) { return node, errors.Wrap(err, "failed to construct registrar url") } + resp, err := c.httpClient.Get(url) + if err != nil { + return + } + + if resp.StatusCode == http.StatusNotFound { + return node, ErrorNodeNotFround + } + + if resp.StatusCode != http.StatusOK { + err = parseResponseError(resp.Body) + return node, errors.Wrapf(err, "failed to get node with status code %s", resp.Status) + } + defer resp.Body.Close() + + err = json.NewDecoder(resp.Body).Decode(&node) + if err != nil { + return + } + return } @@ -292,15 +348,92 @@ func (c RegistrarClient) getNodeByTwinID(id uint64) (node Node, err error) { return node, errors.Wrap(err, "failed to construct registrar url") } - return + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return + } + + q := req.URL.Query() + q.Add("twin_id", fmt.Sprint(id)) + req.URL.RawQuery = q.Encode() + + resp, err := c.httpClient.Do(req) + if err != nil { + return + } + + if resp == nil { + return node, errors.New("no response received") + } + + if resp.StatusCode == http.StatusNotFound { + return node, ErrorNodeNotFround + } + + if resp.StatusCode != http.StatusOK { + err = parseResponseError(resp.Body) + return node, errors.Wrapf(err, "failed to get node by twin id with status code %s", resp.Status) + } + + defer resp.Body.Close() + + var nodes []Node + err = json.NewDecoder(resp.Body).Decode(&nodes) + if err != nil { + return + } + if len(nodes) == 0 { + return node, ErrorNodeNotFround + } + + return nodes[0], nil } -func (c RegistrarClient) listNodes(opts ...ListNodeOpts) (nodes []Node, err error) { +func (c RegistrarClient) listNodes(opts []ListNodeOpts) (nodes []Node, err error) { url, err := url.JoinPath(c.baseURL, "nodes") if err != nil { return nodes, errors.Wrap(err, "failed to construct registrar url") } - return + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nodes, errors.Wrap(err, "failed to construct http request to the registrar") + } + + q := req.URL.Query() + data := parseListNodeOpts(opts) + + for key, val := range data { + q.Add(key, fmt.Sprint(val)) + } + + req.URL.RawQuery = q.Encode() + + resp, err := c.httpClient.Do(req) + if err != nil { + return + } + + if resp == nil { + return nodes, errors.New("no response received") + } + + if resp.StatusCode == http.StatusNotFound { + return nodes, ErrorNodeNotFround + } + + if resp.StatusCode != http.StatusOK { + err = parseResponseError(resp.Body) + return nodes, errors.Wrapf(err, "failed to list nodes with with status code %s", resp.Status) + } + defer resp.Body.Close() + + err = json.NewDecoder(resp.Body).Decode(&nodes) + if err != nil { + return + } + + return nodes, nil } func (c *RegistrarClient) ensureNodeID() error { @@ -355,3 +488,43 @@ func (c RegistrarClient) parseUpdateNodeOpts(node Node, opts []UpdateNodeOpts) N return node } + +func parseListNodeOpts(opts []ListNodeOpts) map[string]any { + cfg := nodeCfg{ + nodeID: 0, + twinID: 0, + farmID: 0, + status: "", + healthy: false, + size: 50, + page: 1, + } + + for _, opt := range opts { + opt(&cfg) + } + + data := map[string]any{} + + if cfg.nodeID != 0 { + data["node_id"] = cfg.nodeID + } + + if cfg.twinID != 0 { + data["twin_id"] = cfg.twinID + } + + if cfg.farmID != 0 { + data["farm_id"] = cfg.farmID + } + + if len(cfg.status) != 0 { + data["status"] = cfg.status + } + + data["healthy"] = cfg.healthy + data["farm_id"] = cfg.farmID + data["farm_id"] = cfg.farmID + + return data +} From c51c466fdcf765a11b554f5aea7453c403ae17d6 Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Thu, 20 Feb 2025 13:46:48 +0200 Subject: [PATCH 08/27] fix client create account signature --- node-registrar/client/account.go | 8 +++++--- node-registrar/client/client.go | 6 +++--- node-registrar/client/utils.go | 1 - 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/node-registrar/client/account.go b/node-registrar/client/account.go index 4c6af57..3e44296 100644 --- a/node-registrar/client/account.go +++ b/node-registrar/client/account.go @@ -2,6 +2,7 @@ package client import ( "bytes" + "crypto/ed25519" "encoding/base64" "encoding/json" "fmt" @@ -40,13 +41,14 @@ func (c *RegistrarClient) createAccount(relays []string, rmbEncKey string) (acco return account, errors.Wrap(err, "failed to construct registrar url") } + timestamp := time.Now().Unix() publicKeyBase64 := base64.StdEncoding.EncodeToString(c.keyPair.publicKey) - timestamp := time.Now().Unix() - signature := c.signRequest(timestamp) + challenge := []byte(fmt.Sprintf("%d:%v", timestamp, publicKeyBase64)) + signature := ed25519.Sign(c.keyPair.privateKey, challenge) data := map[string]any{ - "public_key": publicKeyBase64, + "public_key": c.keyPair.publicKey, "signature": signature, "timestamp": timestamp, "rmb_enc_key": rmbEncKey, diff --git a/node-registrar/client/client.go b/node-registrar/client/client.go index 3be0d11..7e5c41a 100644 --- a/node-registrar/client/client.go +++ b/node-registrar/client/client.go @@ -20,11 +20,11 @@ type RegistrarClient struct { baseURL string } -func NewRegistrarClient(baseURL string, privateKey []byte) (cli RegistrarClient, err error) { +func NewRegistrarClient(baseURL string, sk []byte) (cli RegistrarClient, err error) { client := http.DefaultClient - sk := ed25519.NewKeyFromSeed(privateKey) - publicKey, ok := sk.Public().(ed25519.PublicKey) + privateKey := ed25519.NewKeyFromSeed(sk) + publicKey, ok := privateKey.Public().(ed25519.PublicKey) if !ok { return cli, errors.Wrap(err, "failed to get public key of provided private key") } diff --git a/node-registrar/client/utils.go b/node-registrar/client/utils.go index a56657f..3cf3995 100644 --- a/node-registrar/client/utils.go +++ b/node-registrar/client/utils.go @@ -12,7 +12,6 @@ import ( func (c RegistrarClient) signRequest(timestamp int64) (authHeader string) { challenge := []byte(fmt.Sprintf("%d:%v", timestamp, c.twinID)) - signature := ed25519.Sign(c.keyPair.privateKey, challenge) authHeader = fmt.Sprintf( From db6fe6c2fd9d179b05e9a0ca3cac4f51d6cbc702 Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Sun, 23 Feb 2025 17:14:31 +0200 Subject: [PATCH 09/27] add relays and RMBEncKey on creating account --- node-registrar/pkg/server/handlers.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/node-registrar/pkg/server/handlers.go b/node-registrar/pkg/server/handlers.go index 80369ea..06bdca0 100644 --- a/node-registrar/pkg/server/handlers.go +++ b/node-registrar/pkg/server/handlers.go @@ -557,6 +557,8 @@ func (s *Server) createAccountHandler(c *gin.Context) { // Now we can create new account account := &db.Account{ PublicKey: req.PublicKey, + Relays: req.Relays, + RMBEncKey: req.RMBEncKey, } if err := s.db.CreateAccount(account); err != nil { From 116099c8a56a1f621160679e33f4e5be232cdab2 Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Sun, 23 Feb 2025 17:15:17 +0200 Subject: [PATCH 10/27] add mock tests for account and client --- go.work.sum | 1 + node-registrar/client/account.go | 4 + node-registrar/client/account_test.go | 147 ++++++++++++++++++++ node-registrar/client/client_test.go | 63 +++++++++ node-registrar/client/utils_test.go | 189 ++++++++++++++++++++++++++ node-registrar/go.mod | 3 + node-registrar/go.sum | 2 + 7 files changed, 409 insertions(+) create mode 100644 node-registrar/client/account_test.go create mode 100644 node-registrar/client/client_test.go create mode 100644 node-registrar/client/utils_test.go diff --git a/go.work.sum b/go.work.sum index b66d6fc..3d14c0f 100644 --- a/go.work.sum +++ b/go.work.sum @@ -5,6 +5,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= diff --git a/node-registrar/client/account.go b/node-registrar/client/account.go index 3e44296..bde256b 100644 --- a/node-registrar/client/account.go +++ b/node-registrar/client/account.go @@ -158,6 +158,10 @@ func (c RegistrarClient) getAccountByPK(pk []byte) (account Account, err error) } func (c RegistrarClient) updateAccount(opts []UpdateAccountOpts) (err error) { + err = c.ensureTwinID() + if err != nil { + return errors.Wrap(err, "failed to ensure twin id") + } url, err := url.JoinPath(c.baseURL, "accounts", fmt.Sprint(c.twinID)) if err != nil { return errors.Wrap(err, "failed to construct registrar url") diff --git a/node-registrar/client/account_test.go b/node-registrar/client/account_test.go new file mode 100644 index 0000000..7502fc0 --- /dev/null +++ b/node-registrar/client/account_test.go @@ -0,0 +1,147 @@ +package client + +import ( + "encoding/base64" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCreateAccount(t *testing.T) { + var request int + var count int + require := require.New(t) + + pk, seed, err := aliceKeys() + require.NoError(err) + publicKeyBase64 := base64.StdEncoding.EncodeToString(pk) + account.PublicKey = publicKeyBase64 + + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + statusCode, body := accountHandler(r, request, count, require) + w.WriteHeader(statusCode) + _, err := w.Write(body) + require.NoError(err) + count++ + })) + defer testServer.Close() + + baseURL, err := url.JoinPath(testServer.URL, "v1") + require.NoError(err) + + count = 0 + request = newClientWithNoAccount + c, err := NewRegistrarClient(baseURL, seed) + require.NoError(err) + + t.Run("test create account created successfully", func(t *testing.T) { + count = 0 + request = createAccountStatusCreated + result, err := c.CreateAccount(account.Relays, account.RMBEncKey) + require.NoError(err) + require.Equal(account, result) + }) +} + +func TestUpdateAccount(t *testing.T) { + var request int + var count int + require := require.New(t) + + pk, seed, err := aliceKeys() + require.NoError(err) + publicKeyBase64 := base64.StdEncoding.EncodeToString(pk) + account.PublicKey = publicKeyBase64 + + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + statusCode, body := accountHandler(r, request, count, require) + w.WriteHeader(statusCode) + _, err := w.Write(body) + require.NoError(err) + + count++ + })) + defer testServer.Close() + + baseURL, err := url.JoinPath(testServer.URL, "v1") + require.NoError(err) + + t.Run("test update account updated successfully", func(t *testing.T) { + count = 0 + request = newClientWithAccountNoNode + c, err := NewRegistrarClient(baseURL, seed) + + require.NoError(err) + require.Equal(c.twinID, account.TwinID) + require.Equal([]byte(c.keyPair.publicKey), pk) + + count = 0 + request = updateAccountWithStatusOK + relays := []string{"relay1"} + err = c.UpdateAccount(UpdateAccountWithRelays(relays)) + require.NoError(err) + }) + + t.Run("test update account account not found", func(t *testing.T) { + count = 0 + request = newClientWithNoAccount + c, err := NewRegistrarClient(baseURL, seed) + + require.NoError(err) + require.Equal([]byte(c.keyPair.publicKey), pk) + + count = 0 + request = updateAccountWithNoAccount + relays := []string{"relay1"} + err = c.UpdateAccount(UpdateAccountWithRelays(relays)) + require.Error(err) + }) +} + +func TestGetAccount(t *testing.T) { + var request int + var count int + require := require.New(t) + + pk, seed, err := aliceKeys() + require.NoError(err) + publicKeyBase64 := base64.StdEncoding.EncodeToString(pk) + account.PublicKey = publicKeyBase64 + + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + statusCode, body := accountHandler(r, request, count, require) + w.WriteHeader(statusCode) + _, err := w.Write(body) + require.NoError(err) + count++ + })) + defer testServer.Close() + + baseURL, err := url.JoinPath(testServer.URL, "v1") + require.NoError(err) + + count = 0 + request = newClientWithAccountNoNode + c, err := NewRegistrarClient(baseURL, seed) + require.NoError(err) + require.Equal(c.twinID, account.TwinID) + require.Equal([]byte(c.keyPair.publicKey), pk) + + t.Run("test get account with id account not found", func(t *testing.T) { + count = 0 + request = getAccountWithIDStatusNotFount + _, err := c.GetAccount(account.TwinID) + require.Error(err) + }) + + t.Run("test update account account not found", func(t *testing.T) { + count = 0 + request = getAccountWithIDStatusOK + acc, err := c.GetAccount(account.TwinID) + require.NoError(err) + require.Equal(account, acc) + }) +} diff --git a/node-registrar/client/client_test.go b/node-registrar/client/client_test.go new file mode 100644 index 0000000..a18f8ae --- /dev/null +++ b/node-registrar/client/client_test.go @@ -0,0 +1,63 @@ +package client + +import ( + "encoding/base64" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewRegistrarClient(t *testing.T) { + var request int + var count int + require := require.New(t) + + pk, seed, err := aliceKeys() + require.NoError(err) + publicKeyBase64 := base64.StdEncoding.EncodeToString(pk) + account.PublicKey = publicKeyBase64 + + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + statusCode, body := accountHandler(r, request, count, require) + w.WriteHeader(statusCode) + _, err := w.Write(body) + require.NoError(err) + count++ + })) + defer testServer.Close() + + baseURL, err := url.JoinPath(testServer.URL, "v1") + require.NoError(err) + + t.Run("test new registrar client with no account", func(t *testing.T) { + count = 0 + request = newClientWithNoAccount + c, err := NewRegistrarClient(baseURL, seed) + require.NoError(err) + require.Equal(c.twinID, uint64(0)) + require.Equal(c.nodeID, uint64(0)) + require.Equal([]byte(c.keyPair.publicKey), pk) + }) + + t.Run("test new registrar client with account and no node", func(t *testing.T) { + count = 0 + request = newClientWithAccountNoNode + c, err := NewRegistrarClient(baseURL, seed) + require.NoError(err) + require.Equal(c.twinID, account.TwinID) + require.Equal(c.nodeID, uint64(0)) + require.Equal([]byte(c.keyPair.publicKey), pk) + }) + t.Run("test new registrar client with no account", func(t *testing.T) { + count = 0 + request = newClientWithAccountAndNode + c, err := NewRegistrarClient(baseURL, seed) + require.NoError(err) + require.Equal(c.twinID, account.TwinID) + require.Equal(c.nodeID, uint64(1)) + require.Equal([]byte(c.keyPair.publicKey), pk) + }) +} diff --git a/node-registrar/client/utils_test.go b/node-registrar/client/utils_test.go new file mode 100644 index 0000000..15a425e --- /dev/null +++ b/node-registrar/client/utils_test.go @@ -0,0 +1,189 @@ +package client + +import ( + "crypto/ed25519" + "encoding/hex" + "encoding/json" + "fmt" + "net/http" + + "github.com/stretchr/testify/require" +) + +var ( + aliceSeed = "e5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a" + account = Account{TwinID: 1, Relays: []string{}, RMBEncKey: ""} + farm = Farm{FarmID: 1, FarmName: "freeFarm", TwinID: 1} + node = Node{FarmID: farmID, TwinID: twinID} +) + +const ( + newClientWithNoAccount = iota + newClientWithAccountNoNode + newClientWithAccountAndNode + + createAccountStatusCreated + updateAccountWithNoAccount + updateAccountWithStatusOK + getAccountWithPKStatusOK + getAccountWithPKStatusNotFount + getAccountWithIDStatusOK + getAccountWithIDStatusNotFount + + createFarmStatusCreated + createFarmStatusConflict + updateFarmWithStatusOK + updateFarmWithStatusUnauthorized + getFarmWithStatusNotfound + getFarmWithStatusOK + + registerNodeStatusCreated + registerNodeWithNoAccount + registerNodeStatusConflict + updateNodeStatusOK + updateNodeStatusUnauthorized + updateNodeSendUptimeReport + getNodeWithIDStatusOK + getNodeWithIDStatusNotFound + getNodeWithTwinID + listNodesInFarm + + farmID = 1 + nodeID = 1 + twinID = 1 +) + +func accountHandler(r *http.Request, request, count int, require *require.Assertions) (statusCode int, body []byte) { + switch request { + // NewRegistrarClient handlers + case newClientWithAccountNoNode: + switch count { + case 0: + require.Equal(r.URL.Path, "/v1/accounts") + require.Equal(r.URL.Query().Get("public_key"), account.PublicKey) + require.Equal(r.Method, http.MethodGet) + resp, err := json.Marshal(account) + require.NoError(err) + return http.StatusOK, resp + case 1: + require.Equal(r.URL.Path, "/v1/nodes") + require.Equal(r.URL.Query().Get("twin_id"), fmt.Sprint(account.TwinID)) + require.Equal(r.Method, http.MethodGet) + return http.StatusNotFound, nil + } + + case newClientWithAccountAndNode: + switch count { + case 0: + require.Equal(r.URL.Path, "/v1/accounts") + require.Equal(r.URL.Query().Get("public_key"), account.PublicKey) + require.Equal(r.Method, http.MethodGet) + resp, err := json.Marshal(account) + require.NoError(err) + return http.StatusOK, resp + case 1: + require.Equal(r.URL.Path, "/v1/nodes") + require.Equal(r.URL.Query().Get("twin_id"), fmt.Sprint(account.TwinID)) + require.Equal(r.Method, http.MethodGet) + resp, err := json.Marshal([]Node{{NodeID: nodeID, TwinID: account.TwinID}}) + require.NoError(err) + return http.StatusOK, resp + } + + case newClientWithNoAccount, + + // Accounts routes handlers + getAccountWithPKStatusNotFount, + updateAccountWithNoAccount: + require.Equal(r.URL.Path, "/v1/accounts") + require.Equal(r.URL.Query().Get("public_key"), account.PublicKey) + require.Equal(r.Method, http.MethodGet) + return http.StatusNotFound, nil + + case createAccountStatusCreated: + require.Equal(r.URL.Path, "/v1/accounts") + require.Equal(r.Method, http.MethodPost) + require.NotEmpty(r.Body) + resp, err := json.Marshal(account) + require.NoError(err) + return http.StatusCreated, resp + + case getAccountWithPKStatusOK: + require.Equal(r.URL.Path, "/v1/accounts") + require.Equal(r.URL.Query().Get("public_key"), account.PublicKey) + require.Equal(r.Method, http.MethodGet) + resp, err := json.Marshal(account) + require.NoError(err) + return http.StatusOK, resp + + case getAccountWithIDStatusNotFount: + require.Equal(r.URL.Path, "/v1/accounts") + require.Equal(r.URL.Query().Get("twin_id"), fmt.Sprint(account.TwinID)) + require.Equal(r.Method, http.MethodGet) + return http.StatusNotFound, nil + + case getAccountWithIDStatusOK: + require.Equal(r.URL.Path, "/v1/accounts") + require.Equal(r.URL.Query().Get("twin_id"), fmt.Sprint(account.TwinID)) + require.Equal(r.Method, http.MethodGet) + resp, err := json.Marshal(account) + require.NoError(err) + return http.StatusOK, resp + + case updateAccountWithStatusOK: + require.Equal(r.URL.Path, "/v1/accounts/1") + require.Equal(r.Method, http.MethodPatch) + return http.StatusOK, nil + + // Farm routes handlers + case createFarmStatusCreated: + require.Equal(r.URL.Path, "/v1/farms") + require.Equal(r.Method, http.MethodPost) + require.NotEmpty(r.Body) + resp, err := json.Marshal(`{"farm_id": 1}`) + require.NoError(err) + return http.StatusCreated, resp + + case updateFarmWithStatusOK: + require.Equal(r.URL.Path, "/v1/farms/1") + require.Equal(r.Method, http.MethodPatch) + require.NotEmpty(r.Body) + return http.StatusOK, nil + + case updateFarmWithStatusUnauthorized: + require.Equal(r.URL.Path, "/v1/farms/1") + require.Equal(r.Method, http.MethodPatch) + require.NotEmpty(r.Body) + return http.StatusUnauthorized, nil + + case getFarmWithStatusOK: + require.Equal(r.URL.Path, "/v1/farms/1") + require.Equal(r.Method, http.MethodGet) + resp, err := json.Marshal(farm) + require.NoError(err) + return http.StatusOK, resp + + case getFarmWithStatusNotfound: + require.Equal(r.URL.Path, "/v1/farms/1") + require.Equal(r.Method, http.MethodGet) + return http.StatusNotFound, nil + + } + + return http.StatusNotAcceptable, nil +} + +func aliceKeys() (pk, seed []byte, err error) { + seed, err = hex.DecodeString(aliceSeed) + if err != nil { + return + } + + privateKey := ed25519.NewKeyFromSeed(seed) + pk, ok := privateKey.Public().(ed25519.PublicKey) + if !ok { + return pk, seed, fmt.Errorf("failed to get public key of provided private key") + } + + return pk, seed, nil +} diff --git a/node-registrar/go.mod b/node-registrar/go.mod index d9198e8..7ec9c7c 100644 --- a/node-registrar/go.mod +++ b/node-registrar/go.mod @@ -24,6 +24,7 @@ require ( github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/base58 v1.0.4 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect github.com/gabriel-vasile/mimetype v1.4.7 // indirect @@ -55,7 +56,9 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect golang.org/x/arch v0.12.0 // indirect diff --git a/node-registrar/go.sum b/node-registrar/go.sum index 281a665..336ff8a 100644 --- a/node-registrar/go.sum +++ b/node-registrar/go.sum @@ -146,6 +146,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= From ee6c0c4d60cd89f7ae9100588031ec289026df31 Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Mon, 24 Feb 2025 13:39:45 +0200 Subject: [PATCH 11/27] add tests for node and farm in registrar client --- node-registrar/client/account.go | 1 - node-registrar/client/account_test.go | 25 +--- node-registrar/client/client_test.go | 24 ++-- node-registrar/client/farm.go | 10 +- node-registrar/client/farm_test.go | 132 ++++++++++++++++++ node-registrar/client/node.go | 42 +----- node-registrar/client/node_test.go | 168 +++++++++++++++++++++++ node-registrar/client/utils_test.go | 185 ++++++++++++++++++-------- 8 files changed, 455 insertions(+), 132 deletions(-) create mode 100644 node-registrar/client/farm_test.go create mode 100644 node-registrar/client/node_test.go diff --git a/node-registrar/client/account.go b/node-registrar/client/account.go index bde256b..e7b9a1e 100644 --- a/node-registrar/client/account.go +++ b/node-registrar/client/account.go @@ -185,7 +185,6 @@ func (c RegistrarClient) updateAccount(opts []UpdateAccountOpts) (err error) { resp, err := c.httpClient.Do(req) if err != nil { - fmt.Println("Error sending request:", err) return } diff --git a/node-registrar/client/account_test.go b/node-registrar/client/account_test.go index 7502fc0..36388fd 100644 --- a/node-registrar/client/account_test.go +++ b/node-registrar/client/account_test.go @@ -1,7 +1,6 @@ package client import ( - "encoding/base64" "net/http" "net/http/httptest" "net/url" @@ -15,13 +14,12 @@ func TestCreateAccount(t *testing.T) { var count int require := require.New(t) - pk, seed, err := aliceKeys() + _, seed, publicKeyBase64, err := aliceKeys() require.NoError(err) - publicKeyBase64 := base64.StdEncoding.EncodeToString(pk) account.PublicKey = publicKeyBase64 testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - statusCode, body := accountHandler(r, request, count, require) + statusCode, body := serverHandler(r, request, count, require) w.WriteHeader(statusCode) _, err := w.Write(body) require.NoError(err) @@ -32,13 +30,11 @@ func TestCreateAccount(t *testing.T) { baseURL, err := url.JoinPath(testServer.URL, "v1") require.NoError(err) - count = 0 request = newClientWithNoAccount c, err := NewRegistrarClient(baseURL, seed) require.NoError(err) t.Run("test create account created successfully", func(t *testing.T) { - count = 0 request = createAccountStatusCreated result, err := c.CreateAccount(account.Relays, account.RMBEncKey) require.NoError(err) @@ -51,13 +47,12 @@ func TestUpdateAccount(t *testing.T) { var count int require := require.New(t) - pk, seed, err := aliceKeys() + pk, seed, publicKeyBase64, err := aliceKeys() require.NoError(err) - publicKeyBase64 := base64.StdEncoding.EncodeToString(pk) account.PublicKey = publicKeyBase64 testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - statusCode, body := accountHandler(r, request, count, require) + statusCode, body := serverHandler(r, request, count, require) w.WriteHeader(statusCode) _, err := w.Write(body) require.NoError(err) @@ -78,7 +73,6 @@ func TestUpdateAccount(t *testing.T) { require.Equal(c.twinID, account.TwinID) require.Equal([]byte(c.keyPair.publicKey), pk) - count = 0 request = updateAccountWithStatusOK relays := []string{"relay1"} err = c.UpdateAccount(UpdateAccountWithRelays(relays)) @@ -86,14 +80,12 @@ func TestUpdateAccount(t *testing.T) { }) t.Run("test update account account not found", func(t *testing.T) { - count = 0 request = newClientWithNoAccount c, err := NewRegistrarClient(baseURL, seed) require.NoError(err) require.Equal([]byte(c.keyPair.publicKey), pk) - count = 0 request = updateAccountWithNoAccount relays := []string{"relay1"} err = c.UpdateAccount(UpdateAccountWithRelays(relays)) @@ -106,13 +98,12 @@ func TestGetAccount(t *testing.T) { var count int require := require.New(t) - pk, seed, err := aliceKeys() + pk, seed, publicKeyBase64, err := aliceKeys() require.NoError(err) - publicKeyBase64 := base64.StdEncoding.EncodeToString(pk) account.PublicKey = publicKeyBase64 testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - statusCode, body := accountHandler(r, request, count, require) + statusCode, body := serverHandler(r, request, count, require) w.WriteHeader(statusCode) _, err := w.Write(body) require.NoError(err) @@ -131,14 +122,12 @@ func TestGetAccount(t *testing.T) { require.Equal([]byte(c.keyPair.publicKey), pk) t.Run("test get account with id account not found", func(t *testing.T) { - count = 0 request = getAccountWithIDStatusNotFount _, err := c.GetAccount(account.TwinID) require.Error(err) }) - t.Run("test update account account not found", func(t *testing.T) { - count = 0 + t.Run("test get account account not found", func(t *testing.T) { request = getAccountWithIDStatusOK acc, err := c.GetAccount(account.TwinID) require.NoError(err) diff --git a/node-registrar/client/client_test.go b/node-registrar/client/client_test.go index a18f8ae..dada9ee 100644 --- a/node-registrar/client/client_test.go +++ b/node-registrar/client/client_test.go @@ -1,7 +1,6 @@ package client import ( - "encoding/base64" "net/http" "net/http/httptest" "net/url" @@ -15,13 +14,12 @@ func TestNewRegistrarClient(t *testing.T) { var count int require := require.New(t) - pk, seed, err := aliceKeys() + pk, seed, publicKeyBase64, err := aliceKeys() require.NoError(err) - publicKeyBase64 := base64.StdEncoding.EncodeToString(pk) account.PublicKey = publicKeyBase64 testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - statusCode, body := accountHandler(r, request, count, require) + statusCode, body := serverHandler(r, request, count, require) w.WriteHeader(statusCode) _, err := w.Write(body) require.NoError(err) @@ -37,8 +35,8 @@ func TestNewRegistrarClient(t *testing.T) { request = newClientWithNoAccount c, err := NewRegistrarClient(baseURL, seed) require.NoError(err) - require.Equal(c.twinID, uint64(0)) - require.Equal(c.nodeID, uint64(0)) + require.Equal(uint64(0), c.twinID) + require.Equal(uint64(0), c.nodeID) require.Equal([]byte(c.keyPair.publicKey), pk) }) @@ -47,17 +45,17 @@ func TestNewRegistrarClient(t *testing.T) { request = newClientWithAccountNoNode c, err := NewRegistrarClient(baseURL, seed) require.NoError(err) - require.Equal(c.twinID, account.TwinID) - require.Equal(c.nodeID, uint64(0)) - require.Equal([]byte(c.keyPair.publicKey), pk) + require.Equal(account.TwinID, c.twinID) + require.Equal(uint64(0), c.nodeID) + require.Equal(pk, []byte(c.keyPair.publicKey)) }) - t.Run("test new registrar client with no account", func(t *testing.T) { + t.Run("test new registrar client with account and node", func(t *testing.T) { count = 0 request = newClientWithAccountAndNode c, err := NewRegistrarClient(baseURL, seed) require.NoError(err) - require.Equal(c.twinID, account.TwinID) - require.Equal(c.nodeID, uint64(1)) - require.Equal([]byte(c.keyPair.publicKey), pk) + require.Equal(account.TwinID, c.twinID) + require.Equal(nodeID, c.nodeID) + require.Equal(pk, []byte(c.keyPair.publicKey)) }) } diff --git a/node-registrar/client/farm.go b/node-registrar/client/farm.go index 9e28eef..e0df292 100644 --- a/node-registrar/client/farm.go +++ b/node-registrar/client/farm.go @@ -141,6 +141,11 @@ func (c RegistrarClient) createFarm(farmName string, twinID uint64, dedicated bo } func (c RegistrarClient) updateFarm(farmID uint64, opts []UpdateFarmOpts) (err error) { + err = c.ensureTwinID() + if err != nil { + return errors.Wrap(err, "failed to ensure twin id") + } + url, err := url.JoinPath(c.baseURL, "farms", fmt.Sprint(farmID)) if err != nil { return errors.Wrap(err, "failed to construct registrar url") @@ -174,11 +179,6 @@ func (c RegistrarClient) updateFarm(farmID uint64, opts []UpdateFarmOpts) (err e return fmt.Errorf("failed to create farm with status code %s", resp.Status) } - var result uint64 - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { - return errors.Wrap(err, "failed to decode response body") - } - return } diff --git a/node-registrar/client/farm_test.go b/node-registrar/client/farm_test.go new file mode 100644 index 0000000..1af1eba --- /dev/null +++ b/node-registrar/client/farm_test.go @@ -0,0 +1,132 @@ +package client + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCreateFarm(t *testing.T) { + var request int + var count int + require := require.New(t) + + _, seed, publicKeyBase64, err := aliceKeys() + require.NoError(err) + account.PublicKey = publicKeyBase64 + + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + statusCode, body := serverHandler(r, request, count, require) + w.WriteHeader(statusCode) + _, err := w.Write(body) + require.NoError(err) + count++ + })) + defer testServer.Close() + + baseURL, err := url.JoinPath(testServer.URL, "v1") + require.NoError(err) + + request = newClientWithAccountNoNode + c, err := NewRegistrarClient(baseURL, seed) + require.NoError(err) + + t.Run("test create farm with status conflict", func(t *testing.T) { + request = createFarmStatusConflict + _, err = c.CreateFarm(farm.FarmName, farm.TwinID, farm.Dedicated) + require.Error(err) + }) + + t.Run("test create farm with status ok", func(t *testing.T) { + request = createFarmStatusCreated + result, err := c.CreateFarm(farm.FarmName, farm.TwinID, farm.Dedicated) + require.NoError(err) + require.Equal(farm.FarmID, result) + }) +} + +func TestUpdateFarm(t *testing.T) { + var request int + var count int + require := require.New(t) + + _, seed, publicKeyBase64, err := aliceKeys() + require.NoError(err) + account.PublicKey = publicKeyBase64 + + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + statusCode, body := serverHandler(r, request, count, require) + w.WriteHeader(statusCode) + _, err := w.Write(body) + require.NoError(err) + count++ + })) + defer testServer.Close() + + baseURL, err := url.JoinPath(testServer.URL, "v1") + require.NoError(err) + + t.Run("test update farm with status unauthorzed", func(t *testing.T) { + request = newClientWithNoAccount + c, err := NewRegistrarClient(baseURL, seed) + require.NoError(err) + + request = updateFarmWithStatusUnauthorized + err = c.UpdateFarm(farmID, UpdateFarmWithName("notFreeFarm")) + require.Error(err) + }) + + t.Run("test update farm with status ok", func(t *testing.T) { + count = 0 + request = newClientWithAccountNoNode + c, err := NewRegistrarClient(baseURL, seed) + require.NoError(err) + + request = updateFarmWithStatusOK + err = c.UpdateFarm(farmID, UpdateFarmWithName("notFreeFarm")) + require.NoError(err) + }) +} + +func TestGetFarm(t *testing.T) { + var request int + var count int + require := require.New(t) + + _, seed, publicKeyBase64, err := aliceKeys() + require.NoError(err) + account.PublicKey = publicKeyBase64 + + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + statusCode, body := serverHandler(r, request, count, require) + w.WriteHeader(statusCode) + _, err := w.Write(body) + require.NoError(err) + count++ + })) + defer testServer.Close() + + baseURL, err := url.JoinPath(testServer.URL, "v1") + require.NoError(err) + + count = 0 + request = newClientWithAccountNoNode + c, err := NewRegistrarClient(baseURL, seed) + require.NoError(err) + + t.Run("test get farm with status not found", func(t *testing.T) { + request = getFarmWithStatusNotfound + _, err = c.GetFarm(farmID) + require.Error(err) + }) + + t.Run("test get farm with status ok", func(t *testing.T) { + request = getFarmWithStatusOK + result, err := c.GetFarm(farmID) + require.NoError(err) + require.Equal(result, farm) + }) +} diff --git a/node-registrar/client/node.go b/node-registrar/client/node.go index a08f150..a020485 100644 --- a/node-registrar/client/node.go +++ b/node-registrar/client/node.go @@ -170,6 +170,10 @@ func (c RegistrarClient) registerNode( secureBoot, virtualized bool, ) (nodeID uint64, err error) { + err = c.ensureTwinID() + if err != nil { + return nodeID, errors.Wrap(err, "failed to ensure twin id") + } url, err := url.JoinPath(c.baseURL, "nodes") if err != nil { return nodeID, errors.Wrap(err, "failed to construct registrar url") @@ -258,7 +262,7 @@ func (c RegistrarClient) updateNode(opts []UpdateNodeOpts) (err error) { return errors.Wrap(err, "failed to send request to update node") } - if resp == nil || resp.StatusCode != http.StatusOK { + if resp.StatusCode != http.StatusOK { err = parseResponseError(resp.Body) return errors.Wrapf(err, "failed to update node with twin id %d with status code %s", c.twinID, resp.Status) } @@ -343,45 +347,11 @@ func (c RegistrarClient) getNode(id uint64) (node Node, err error) { } func (c RegistrarClient) getNodeByTwinID(id uint64) (node Node, err error) { - url, err := url.JoinPath(c.baseURL, "nodes") - if err != nil { - return node, errors.Wrap(err, "failed to construct registrar url") - } - - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return - } - - q := req.URL.Query() - q.Add("twin_id", fmt.Sprint(id)) - req.URL.RawQuery = q.Encode() - - resp, err := c.httpClient.Do(req) + nodes, err := c.ListNodes(ListNodesWithTwinID(id)) if err != nil { return } - if resp == nil { - return node, errors.New("no response received") - } - - if resp.StatusCode == http.StatusNotFound { - return node, ErrorNodeNotFround - } - - if resp.StatusCode != http.StatusOK { - err = parseResponseError(resp.Body) - return node, errors.Wrapf(err, "failed to get node by twin id with status code %s", resp.Status) - } - - defer resp.Body.Close() - - var nodes []Node - err = json.NewDecoder(resp.Body).Decode(&nodes) - if err != nil { - return - } if len(nodes) == 0 { return node, ErrorNodeNotFround } diff --git a/node-registrar/client/node_test.go b/node-registrar/client/node_test.go new file mode 100644 index 0000000..eb43ec3 --- /dev/null +++ b/node-registrar/client/node_test.go @@ -0,0 +1,168 @@ +package client + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestRegistarNode(t *testing.T) { + var request int + var count int + require := require.New(t) + + _, seed, publicKeyBase64, err := aliceKeys() + require.NoError(err) + account.PublicKey = publicKeyBase64 + + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + statusCode, body := serverHandler(r, request, count, require) + w.WriteHeader(statusCode) + _, err := w.Write(body) + require.NoError(err) + count++ + })) + defer testServer.Close() + + baseURL, err := url.JoinPath(testServer.URL, "v1") + require.NoError(err) + + t.Run("test registar node no account", func(t *testing.T) { + request = newClientWithNoAccount + c, err := NewRegistrarClient(baseURL, seed) + require.NoError(err) + + request = registerNodeWithNoAccount + _, err = c.RegisterNode(farmID, twinID, []Interface{}, Location{}, Resources{}, "", false, false) + require.Error(err) + }) + + t.Run("test registar node, node already exist", func(t *testing.T) { + count = 0 + request = newClientWithAccountAndNode + c, err := NewRegistrarClient(baseURL, seed) + require.NoError(err) + + count = 0 + request = registerNodeStatusConflict + _, err = c.RegisterNode(farmID, twinID, []Interface{}, Location{}, Resources{}, "", false, false) + require.Error(err) + }) + + t.Run("test registar node, created successfully", func(t *testing.T) { + count = 0 + request = newClientWithAccountNoNode + c, err := NewRegistrarClient(baseURL, seed) + require.NoError(err) + + count = 0 + request = registerNodeStatusCreated + result, err := c.RegisterNode(farmID, twinID, []Interface{}, Location{}, Resources{}, "", false, false) + require.NoError(err) + require.Equal(nodeID, result) + }) +} + +func TestUpdateNode(t *testing.T) { + var request int + var count int + require := require.New(t) + + _, seed, publicKeyBase64, err := aliceKeys() + require.NoError(err) + account.PublicKey = publicKeyBase64 + + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + statusCode, body := serverHandler(r, request, count, require) + w.WriteHeader(statusCode) + _, err := w.Write(body) + require.NoError(err) + count++ + })) + defer testServer.Close() + + baseURL, err := url.JoinPath(testServer.URL, "v1") + require.NoError(err) + + request = newClientWithAccountAndNode + c, err := NewRegistrarClient(baseURL, seed) + require.NoError(err) + + t.Run("test update node with status ok", func(t *testing.T) { + count = 0 + request = updateNodeStatusOK + err = c.UpdateNode(UpdateNodesWithFarmID(2)) + require.NoError(err) + }) + + t.Run("test update node uptime", func(t *testing.T) { + request = updateNodeSendUptimeReport + + report := UptimeReport{ + ID: 1, + NodeID: 1, + Duration: time.Hour, + Timestamp: time.Now(), + WasRestart: false, + } + err = c.ReportUptime(report) + require.NoError(err) + }) +} + +func TestGetNode(t *testing.T) { + var request int + var count int + require := require.New(t) + + _, seed, publicKeyBase64, err := aliceKeys() + require.NoError(err) + account.PublicKey = publicKeyBase64 + + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + statusCode, body := serverHandler(r, request, count, require) + w.WriteHeader(statusCode) + _, err := w.Write(body) + require.NoError(err) + count++ + })) + defer testServer.Close() + + baseURL, err := url.JoinPath(testServer.URL, "v1") + require.NoError(err) + + request = newClientWithAccountAndNode + c, err := NewRegistrarClient(baseURL, seed) + require.NoError(err) + + t.Run("test get node status not found", func(t *testing.T) { + request = getNodeWithIDStatusNotFound + _, err = c.GetNode(nodeID) + require.Error(err) + }) + + t.Run("test get node, status ok", func(t *testing.T) { + request = getNodeWithIDStatusOK + result, err := c.GetNode(nodeID) + require.NoError(err) + require.Equal(node, result) + }) + + t.Run("test get node with twin id", func(t *testing.T) { + request = getNodeWithTwinID + result, err := c.GetNodeByTwinID(twinID) + require.NoError(err) + require.Equal(node, result) + }) + + t.Run("test list nodes of specific farm", func(t *testing.T) { + request = listNodesInFarm + result, err := c.ListNodes(ListNodesWithFarmID(farmID)) + require.NoError(err) + require.Equal([]Node{node}, result) + }) +} diff --git a/node-registrar/client/utils_test.go b/node-registrar/client/utils_test.go index 15a425e..becc121 100644 --- a/node-registrar/client/utils_test.go +++ b/node-registrar/client/utils_test.go @@ -2,6 +2,7 @@ package client import ( "crypto/ed25519" + "encoding/base64" "encoding/hex" "encoding/json" "fmt" @@ -14,7 +15,7 @@ var ( aliceSeed = "e5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a" account = Account{TwinID: 1, Relays: []string{}, RMBEncKey: ""} farm = Farm{FarmID: 1, FarmName: "freeFarm", TwinID: 1} - node = Node{FarmID: farmID, TwinID: twinID} + node = Node{NodeID: 1, FarmID: farmID, TwinID: twinID} ) const ( @@ -41,131 +42,195 @@ const ( registerNodeWithNoAccount registerNodeStatusConflict updateNodeStatusOK - updateNodeStatusUnauthorized updateNodeSendUptimeReport getNodeWithIDStatusOK getNodeWithIDStatusNotFound getNodeWithTwinID listNodesInFarm - farmID = 1 - nodeID = 1 - twinID = 1 + farmID uint64 = 1 + nodeID uint64 = 1 + twinID uint64 = 1 ) -func accountHandler(r *http.Request, request, count int, require *require.Assertions) (statusCode int, body []byte) { +func serverHandler(r *http.Request, request, count int, require *require.Assertions) (statusCode int, body []byte) { switch request { // NewRegistrarClient handlers case newClientWithAccountNoNode: switch count { case 0: - require.Equal(r.URL.Path, "/v1/accounts") - require.Equal(r.URL.Query().Get("public_key"), account.PublicKey) - require.Equal(r.Method, http.MethodGet) + require.Equal("/v1/accounts", r.URL.Path) + require.Equal(account.PublicKey, r.URL.Query().Get("public_key")) + require.Equal(http.MethodGet, r.Method) resp, err := json.Marshal(account) require.NoError(err) return http.StatusOK, resp case 1: - require.Equal(r.URL.Path, "/v1/nodes") - require.Equal(r.URL.Query().Get("twin_id"), fmt.Sprint(account.TwinID)) - require.Equal(r.Method, http.MethodGet) + require.Equal("/v1/nodes", r.URL.Path) + require.Equal(fmt.Sprint(account.TwinID), r.URL.Query().Get("twin_id")) + require.Equal(http.MethodGet, r.Method) return http.StatusNotFound, nil } case newClientWithAccountAndNode: switch count { case 0: - require.Equal(r.URL.Path, "/v1/accounts") - require.Equal(r.URL.Query().Get("public_key"), account.PublicKey) - require.Equal(r.Method, http.MethodGet) + require.Equal("/v1/accounts", r.URL.Path) + require.Equal(account.PublicKey, r.URL.Query().Get("public_key")) + require.Equal(http.MethodGet, r.Method) resp, err := json.Marshal(account) require.NoError(err) return http.StatusOK, resp case 1: - require.Equal(r.URL.Path, "/v1/nodes") - require.Equal(r.URL.Query().Get("twin_id"), fmt.Sprint(account.TwinID)) - require.Equal(r.Method, http.MethodGet) - resp, err := json.Marshal([]Node{{NodeID: nodeID, TwinID: account.TwinID}}) + require.Equal("/v1/nodes", r.URL.Path) + require.Equal(fmt.Sprint(account.TwinID), r.URL.Query().Get("twin_id")) + require.Equal(http.MethodGet, r.Method) + resp, err := json.Marshal([]Node{node}) require.NoError(err) return http.StatusOK, resp } - case newClientWithNoAccount, - // Accounts routes handlers - getAccountWithPKStatusNotFount, - updateAccountWithNoAccount: - require.Equal(r.URL.Path, "/v1/accounts") - require.Equal(r.URL.Query().Get("public_key"), account.PublicKey) - require.Equal(r.Method, http.MethodGet) - return http.StatusNotFound, nil - case createAccountStatusCreated: - require.Equal(r.URL.Path, "/v1/accounts") - require.Equal(r.Method, http.MethodPost) + require.Equal("/v1/accounts", r.URL.Path) + require.Equal(http.MethodPost, r.Method) require.NotEmpty(r.Body) resp, err := json.Marshal(account) require.NoError(err) return http.StatusCreated, resp case getAccountWithPKStatusOK: - require.Equal(r.URL.Path, "/v1/accounts") - require.Equal(r.URL.Query().Get("public_key"), account.PublicKey) - require.Equal(r.Method, http.MethodGet) + require.Equal("/v1/accounts", r.URL.Path) + require.Equal(account.PublicKey, r.URL.Query().Get("public_key")) + require.Equal(http.MethodGet, r.Method) resp, err := json.Marshal(account) require.NoError(err) return http.StatusOK, resp case getAccountWithIDStatusNotFount: - require.Equal(r.URL.Path, "/v1/accounts") - require.Equal(r.URL.Query().Get("twin_id"), fmt.Sprint(account.TwinID)) - require.Equal(r.Method, http.MethodGet) + require.Equal("/v1/accounts", r.URL.Path) + require.Equal(fmt.Sprint(account.TwinID), r.URL.Query().Get("twin_id")) + require.Equal(http.MethodGet, r.Method) return http.StatusNotFound, nil case getAccountWithIDStatusOK: - require.Equal(r.URL.Path, "/v1/accounts") - require.Equal(r.URL.Query().Get("twin_id"), fmt.Sprint(account.TwinID)) - require.Equal(r.Method, http.MethodGet) + require.Equal("/v1/accounts", r.URL.Path) + require.Equal(fmt.Sprint(account.TwinID), r.URL.Query().Get("twin_id")) + require.Equal(http.MethodGet, r.Method) resp, err := json.Marshal(account) require.NoError(err) return http.StatusOK, resp case updateAccountWithStatusOK: - require.Equal(r.URL.Path, "/v1/accounts/1") - require.Equal(r.Method, http.MethodPatch) + require.Equal("/v1/accounts/1", r.URL.Path) + require.Equal(http.MethodPatch, r.Method) return http.StatusOK, nil // Farm routes handlers + case createFarmStatusConflict: + require.Equal("/v1/farms", r.URL.Path) + require.Equal(http.MethodPost, r.Method) + require.NotEmpty(r.Body) + return http.StatusConflict, nil + case createFarmStatusCreated: - require.Equal(r.URL.Path, "/v1/farms") - require.Equal(r.Method, http.MethodPost) + require.Equal("/v1/farms", r.URL.Path) + require.Equal(http.MethodPost, r.Method) require.NotEmpty(r.Body) - resp, err := json.Marshal(`{"farm_id": 1}`) + resp, err := json.Marshal(map[string]uint64{"farm_id": farmID}) require.NoError(err) return http.StatusCreated, resp case updateFarmWithStatusOK: - require.Equal(r.URL.Path, "/v1/farms/1") - require.Equal(r.Method, http.MethodPatch) + require.Equal("/v1/farms/1", r.URL.Path) + require.Equal(http.MethodPatch, r.Method) require.NotEmpty(r.Body) return http.StatusOK, nil - case updateFarmWithStatusUnauthorized: - require.Equal(r.URL.Path, "/v1/farms/1") - require.Equal(r.Method, http.MethodPatch) - require.NotEmpty(r.Body) - return http.StatusUnauthorized, nil - case getFarmWithStatusOK: - require.Equal(r.URL.Path, "/v1/farms/1") - require.Equal(r.Method, http.MethodGet) + require.Equal("/v1/farms/1", r.URL.Path) + require.Equal(http.MethodGet, r.Method) resp, err := json.Marshal(farm) require.NoError(err) return http.StatusOK, resp case getFarmWithStatusNotfound: - require.Equal(r.URL.Path, "/v1/farms/1") - require.Equal(r.Method, http.MethodGet) + require.Equal("/v1/farms/1", r.URL.Path) + require.Equal(http.MethodGet, r.Method) + return http.StatusNotFound, nil + + // Node routes handlers + case registerNodeStatusConflict: + require.Equal("/v1/nodes", r.URL.Path) + require.Equal(http.MethodPost, r.Method) + return http.StatusConflict, nil + + case registerNodeStatusCreated: + require.Equal("/v1/nodes", r.URL.Path) + require.Equal(http.MethodPost, r.Method) + require.NotEmpty(r.Body) + resp, err := json.Marshal(map[string]uint64{"node_id": nodeID}) + require.NoError(err) + return http.StatusCreated, resp + + case updateNodeStatusOK: + switch count { + case 0: + require.Equal("/v1/nodes/1", r.URL.Path) + require.Equal(http.MethodGet, r.Method) + resp, err := json.Marshal(node) + require.NoError(err) + return http.StatusOK, resp + case 1: + require.Equal("/v1/nodes/1", r.URL.Path) + require.Equal(http.MethodPatch, r.Method) + require.NotEmpty(r.Body) + return http.StatusOK, nil + } + + case updateNodeSendUptimeReport: + require.Equal("/v1/nodes/1", r.URL.Path) + require.Equal(http.MethodPatch, r.Method) + require.NotEmpty(r.Body) + return http.StatusOK, nil + + case getNodeWithIDStatusOK: + require.Equal("/v1/nodes/1", r.URL.Path) + require.Equal(http.MethodGet, r.Method) + resp, err := json.Marshal(node) + require.NoError(err) + return http.StatusOK, resp + + case getNodeWithIDStatusNotFound: + require.Equal("/v1/nodes/1", r.URL.Path) + require.Equal(http.MethodGet, r.Method) + return http.StatusNotFound, nil + + case getNodeWithTwinID: + require.Equal("/v1/nodes", r.URL.Path) + require.Equal(fmt.Sprint(account.TwinID), r.URL.Query().Get("twin_id")) + require.Equal(http.MethodGet, r.Method) + resp, err := json.Marshal([]Node{node}) + require.NoError(err) + return http.StatusOK, resp + + case listNodesInFarm: + require.Equal("/v1/nodes", r.URL.Path) + require.Equal(fmt.Sprint(farmID), r.URL.Query().Get("farm_id")) + require.Equal(http.MethodGet, r.Method) + resp, err := json.Marshal([]Node{node}) + require.NoError(err) + return http.StatusOK, resp + + // unauthorized requests + case newClientWithNoAccount, + getAccountWithPKStatusNotFount, + updateAccountWithNoAccount, + updateFarmWithStatusUnauthorized, + registerNodeWithNoAccount: + require.Equal("/v1/accounts", r.URL.Path) + require.Equal(account.PublicKey, r.URL.Query().Get("public_key")) + require.Equal(http.MethodGet, r.Method) return http.StatusNotFound, nil } @@ -173,7 +238,7 @@ func accountHandler(r *http.Request, request, count int, require *require.Assert return http.StatusNotAcceptable, nil } -func aliceKeys() (pk, seed []byte, err error) { +func aliceKeys() (pk, seed []byte, pkBase64 string, err error) { seed, err = hex.DecodeString(aliceSeed) if err != nil { return @@ -182,8 +247,10 @@ func aliceKeys() (pk, seed []byte, err error) { privateKey := ed25519.NewKeyFromSeed(seed) pk, ok := privateKey.Public().(ed25519.PublicKey) if !ok { - return pk, seed, fmt.Errorf("failed to get public key of provided private key") + return pk, seed, pkBase64, fmt.Errorf("failed to get public key of provided private key") } - return pk, seed, nil + pkBase64 = base64.StdEncoding.EncodeToString(pk) + + return pk, seed, pkBase64, nil } From 8e4109ccba93cfbb1db523e83ee2d702760ee461 Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Mon, 24 Feb 2025 16:35:48 +0200 Subject: [PATCH 12/27] fix registrar client --- node-registrar/client/farm.go | 5 +++++ node-registrar/client/node.go | 31 +++++++++++++++------------ node-registrar/client/node_test.go | 7 ++---- node-registrar/client/types.go | 9 +++----- node-registrar/pkg/server/handlers.go | 2 -- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/node-registrar/client/farm.go b/node-registrar/client/farm.go index e0df292..f82bece 100644 --- a/node-registrar/client/farm.go +++ b/node-registrar/client/farm.go @@ -92,6 +92,11 @@ func UpdateFarmWithDedicated() UpdateFarmOpts { } func (c RegistrarClient) createFarm(farmName string, twinID uint64, dedicated bool) (farmID uint64, err error) { + err = c.ensureTwinID() + if err != nil { + return farmID, errors.Wrap(err, "failed to ensure twin id") + } + url, err := url.JoinPath(c.baseURL, "farms") if err != nil { return farmID, errors.Wrap(err, "failed to construct registrar url") diff --git a/node-registrar/client/node.go b/node-registrar/client/node.go index a020485..e2d2bac 100644 --- a/node-registrar/client/node.go +++ b/node-registrar/client/node.go @@ -124,6 +124,12 @@ func UpdateNodesWithInterfaces(interfaces []Interface) UpdateNodeOpts { } } +func UpdateNodesWithLocation(location Location) UpdateNodeOpts { + return func(n *nodeCfg) { + n.Location = location + } +} + func UpdateNodesWithResources(resources Resources) UpdateNodeOpts { return func(n *nodeCfg) { n.Resources = resources @@ -211,7 +217,7 @@ func (c RegistrarClient) registerNode( if resp == nil || resp.StatusCode != http.StatusCreated { err = parseResponseError(resp.Body) - return 0, errors.Wrapf(err, "failed to update node on the registrar with status code %s", resp.Status) + return 0, errors.Wrapf(err, "failed to create node on the registrar with status code %s", resp.Status) } defer resp.Body.Close() @@ -277,25 +283,19 @@ func (c RegistrarClient) reportUptime(report UptimeReport) (err error) { return err } - url, err := url.JoinPath(c.baseURL, "nodes", fmt.Sprint(c.nodeID)) + url, err := url.JoinPath(c.baseURL, "nodes", fmt.Sprint(c.nodeID), "uptime") if err != nil { return errors.Wrap(err, "failed to construct registrar url") } - timestamp := time.Now().Unix() var body bytes.Buffer - data := map[string]any{ - "uptime": report, - "timestamp": timestamp, - } - - err = json.NewEncoder(&body).Encode(data) + err = json.NewEncoder(&body).Encode(report) if err != nil { return errors.Wrap(err, "failed to encode request body") } - req, err := http.NewRequest("PATCH", url, &body) + req, err := http.NewRequest("POST", url, &body) if err != nil { return errors.Wrap(err, "failed to construct http request to the registrar") } @@ -308,7 +308,7 @@ func (c RegistrarClient) reportUptime(report UptimeReport) (err error) { return errors.Wrap(err, "failed to send request to update uptime of the node") } - if resp == nil || resp.StatusCode != http.StatusOK { + if resp.StatusCode != http.StatusCreated { err = parseResponseError(resp.Body) return errors.Wrapf(err, "failed to update node uptime for node with id %d with status code %s", c.nodeID, resp.Status) } @@ -492,9 +492,12 @@ func parseListNodeOpts(opts []ListNodeOpts) map[string]any { data["status"] = cfg.status } - data["healthy"] = cfg.healthy - data["farm_id"] = cfg.farmID - data["farm_id"] = cfg.farmID + if cfg.healthy { + data["healthy"] = cfg.healthy + } + + data["size"] = cfg.size + data["page"] = cfg.page return data } diff --git a/node-registrar/client/node_test.go b/node-registrar/client/node_test.go index eb43ec3..0d10bf2 100644 --- a/node-registrar/client/node_test.go +++ b/node-registrar/client/node_test.go @@ -103,11 +103,8 @@ func TestUpdateNode(t *testing.T) { request = updateNodeSendUptimeReport report := UptimeReport{ - ID: 1, - NodeID: 1, - Duration: time.Hour, - Timestamp: time.Now(), - WasRestart: false, + Uptime: 40 * time.Minute, + Timestamp: time.Now(), } err = c.ReportUptime(report) require.NoError(err) diff --git a/node-registrar/client/types.go b/node-registrar/client/types.go index 8c8d6d0..d7b1201 100644 --- a/node-registrar/client/types.go +++ b/node-registrar/client/types.go @@ -24,7 +24,7 @@ type Node struct { TwinID uint64 `json:"twin_id"` Location Location `json:"location"` Resources Resources `json:"resources"` - Interfaces []Interface `json:"interface"` + Interfaces []Interface `json:"interfaces"` SecureBoot bool `json:"secure_boot"` Virtualized bool `json:"virtualized"` SerialNumber string `json:"serial_number"` @@ -33,11 +33,8 @@ type Node struct { } type UptimeReport struct { - ID uint64 - NodeID uint64 `json:"node_id"` - Duration time.Duration `json:"duration"` - Timestamp time.Time `json:"timestamp"` - WasRestart bool `json:"was_restart"` + Uptime time.Duration `json:"uptime"` + Timestamp time.Time `json:"timestamp"` } type ZosVersion struct { diff --git a/node-registrar/pkg/server/handlers.go b/node-registrar/pkg/server/handlers.go index 06bdca0..1e9b8e5 100644 --- a/node-registrar/pkg/server/handlers.go +++ b/node-registrar/pkg/server/handlers.go @@ -377,7 +377,6 @@ func (s *Server) updateNodeHandler(c *gin.Context) { return } - log.Info().Any("req is", c.Request.Body) var req UpdateNodeRequest if err := c.ShouldBindJSON(&req); err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid request body"}) @@ -692,7 +691,6 @@ func (s *Server) getAccountHandler(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get account"}) return } - log.Info().Any("account", account).Send() c.JSON(http.StatusOK, account) return } From 18931e7b9be947cc5564342bb034127ab6a56595 Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Mon, 24 Feb 2025 17:29:31 +0200 Subject: [PATCH 13/27] ensure twin is set on setting zos version --- node-registrar/client/zos_version.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/node-registrar/client/zos_version.go b/node-registrar/client/zos_version.go index af7e66c..9351261 100644 --- a/node-registrar/client/zos_version.go +++ b/node-registrar/client/zos_version.go @@ -60,6 +60,11 @@ func (c RegistrarClient) getZosVersion() (version ZosVersion, err error) { } func (c RegistrarClient) setZosVersion(v string, safeToUpgrade bool) (err error) { + err = c.ensureTwinID() + if err != nil { + return errors.Wrap(err, "failed to ensure twin id") + } + url, err := url.JoinPath(c.baseURL, "zos", "version") if err != nil { return errors.Wrap(err, "failed to construct registrar url") From d88d7cb1b89b333fa2fc84822cfd314ada70039f Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Tue, 25 Feb 2025 11:30:18 +0200 Subject: [PATCH 14/27] fix uptime tests --- node-registrar/client/utils_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/node-registrar/client/utils_test.go b/node-registrar/client/utils_test.go index becc121..ea95657 100644 --- a/node-registrar/client/utils_test.go +++ b/node-registrar/client/utils_test.go @@ -189,10 +189,10 @@ func serverHandler(r *http.Request, request, count int, require *require.Asserti } case updateNodeSendUptimeReport: - require.Equal("/v1/nodes/1", r.URL.Path) - require.Equal(http.MethodPatch, r.Method) + require.Equal("/v1/nodes/1/uptime", r.URL.Path) + require.Equal(http.MethodPost, r.Method) require.NotEmpty(r.Body) - return http.StatusOK, nil + return http.StatusCreated, nil case getNodeWithIDStatusOK: require.Equal("/v1/nodes/1", r.URL.Path) From 0c310f4e3a0652f6db0056448621448c6aa2aced Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Tue, 25 Feb 2025 12:02:46 +0200 Subject: [PATCH 15/27] remove pk from ensure account --- node-registrar/client/account.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/node-registrar/client/account.go b/node-registrar/client/account.go index e7b9a1e..890dce9 100644 --- a/node-registrar/client/account.go +++ b/node-registrar/client/account.go @@ -31,8 +31,8 @@ func (c RegistrarClient) UpdateAccount(opts ...UpdateAccountOpts) (err error) { return c.updateAccount(opts) } -func (c RegistrarClient) EnsureAccount(pk []byte, relays []string, rmbEncKey string) (account Account, err error) { - return c.ensureAccount(pk, relays, rmbEncKey) +func (c RegistrarClient) EnsureAccount(relays []string, rmbEncKey string) (account Account, err error) { + return c.ensureAccount(relays, rmbEncKey) } func (c *RegistrarClient) createAccount(relays []string, rmbEncKey string) (account Account, err error) { @@ -221,8 +221,8 @@ func UpdateAccountWithRMBEncKey(rmbEncKey string) UpdateAccountOpts { } } -func (c RegistrarClient) ensureAccount(pk []byte, relays []string, rmbEncKey string) (account Account, err error) { - account, err = c.GetAccountByPK(pk) +func (c RegistrarClient) ensureAccount(relays []string, rmbEncKey string) (account Account, err error) { + account, err = c.GetAccountByPK(c.keyPair.publicKey) if errors.Is(err, ErrorAccountNotFround) { return c.CreateAccount(relays, rmbEncKey) } else if err != nil { From b0dded760e299966264e6dd0f0e70d6da4205d61 Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Wed, 26 Feb 2025 11:43:31 +0200 Subject: [PATCH 16/27] add registrar client readme file --- node-registrar/client/README.md | 291 ++++++++++++++++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 node-registrar/client/README.md diff --git a/node-registrar/client/README.md b/node-registrar/client/README.md new file mode 100644 index 0000000..a900b55 --- /dev/null +++ b/node-registrar/client/README.md @@ -0,0 +1,291 @@ +# ThreeFold Grid Node Registrar Client + +A Go client for interacting with the ThreeFold Grid Node Registrar service. Facilitates node registration and management on the ThreeFold Grid. + +## Overview + +The Node Registrar Client enables communication with the ThreeFold Grid's node registration service. It provides methods to: + +* Register nodes +* Manage node metadata +* Retrieve node information +* Delete node registrations + +## Features + +### Version + +* **Get Zos Version**: Loads zos version for the current network +* **Set Zos Version**: Set zos version to specific version (can only be done by the network admin) + +### Accounts + +* **Create Account**: Create new account on the registrar with uniqe key. +* **Update Account**: Update the account configuration (relays & rmbEncKey). +* **Ensure Account**: Ensures that an account is created with specific seed. +* **Get Account**: Get an account using either its twin\_id or its public\_key. + +### Farms + +* **Create Farm**: Create new farm on the registrar with uniqe name. +* **update Farm**: update farm configuration (farm\_id, dedicated). +* **Get Farm**: Get a farm using either its farm\_id. + +### Node + +* **Register Node**: Register physical/virtual nodes with the TFGrid. +* **Update Node**: Update node configuration (farm\_id, interfaces, resources, location, secure\_boot, virtualized). +* **Get Node**: Fetch registered node details using (node\_id, twin\_id, farm\_id). +* **Update Node Uptime**: Update node Uptime. + +### API Methods + +#### Version Operations + +| Method | Description | Parameters | Returns | +|-----------------|----------------------------------|----------------------------|---------------------| +| GetZosVersion | Get current zos version | None | (ZosVersion, error) | +| SetZosVersion | Update zos version (admin-only) | version string, force bool | error | + +#### Account Management + +| Method | Description | Parameters | Returns | +|----------------|---------------------------|-----------------------------------|------------------| +| CreateAccount | Create new account | relays []string, rmbEncKey string | (Account, error) | +| EnsureAccount | Create account if missing | relays []string, rmbEncKey string | (Account, error) | +| GetAccount | Get account by twin ID | twinID uint64 | (Account, error) | +| GetAccountByPK | Get account by public key | publicKey string | (Account, error) | +| UpdateAccount | Modify account config | ...UpdateOption | error | + +#### Farm Operations + +| Method | Description | Parameters | Returns | +|-------------|------------------------|--------------------------------------------|-----------------| +| CreateFarm | Register new farm | name string, twinID uint64, dedicated bool | (uint64, error) | +| UpdateFarm | Modify farm properties | farmID uint64, ...UpdateOption | error | +| GetFarm | Get farm by ID | farmID uint64 | (Farm, error) | +| ListFarms | List farms | ...ListOption | ([]Farm, error) | + +#### Node Operations + +| Method | Description | Parameters | Returns | +|-----------------|-----------------------|------------------------------------------------|-----------------| +| RegisterNode | Register new node | farmID uint64, twinID uint64, interfaces []Interface, location Location, resources Resources, serial string, secureBoot bool, virtualized bool | (uint64, error) | +| UpdateNode | Modify node config | ...UpdateOption | error | +| GetNode | Get node by node ID | nodeID uint64 | (Node, error) | +| GetNodeByTwinID | Get node by twin ID | twinID uint64 | (Node, error) | +| ListNodes | List nodes | ...ListOption | ([]Node, error) | +| ReportUptime | Submit uptime metrics | report UptimeReport | error | + +## Installation + +```bash +go get github.com/threefoldtech/tfgrid4-sdk-go/node-registrar/client +``` + +## Usage + +### Initialize Client + +```go +import ( + "github.com/threefoldtech/tfgrid4-sdk-go/node-registrar/client" +) + +func main() { + registrarURL := "https://registrar.dev4.grid.tf/v1" + + s := make([]byte, 32) + _, err := rand.Read(s) + if err != nil { + log.Fatal().Err(err).Send() + } + seed = hex.EncodeToString(s) + fmt.Println("New Seed (Hex):", seed) + + cli, err := client.NewRegistrarClient(registrarURL, s) + if err != nil { + panic(err) + } +} +``` + +### Get Zos Version + +```go + version, err := c.GetZosVersion() + if err != nil { + log.Fatal().Err(err).Msg("failed to set registrar version") + } + + log.Info().Msgf("%s version is: %+v", network, version) +``` + +### Set Zos Version (ONLY for network admin) + +```go + err := c.SetZosVersion("v0.1.8", true) + if err != nil { + log.Fatal().Err(err).Msg("failed to set registrar version") + } + + log.Info().Msg("version is updated successfully") +``` + +### Create Account + +```go + account, err := c.CreateAccount(relays, rmbEncKey) + if err != nil { + log.Fatal().Err(err).Msg("failed to create new account on registrar") + } + +log.Info().Uint64("twinID", account.TwinID).Msg("account created successfully") + +``` + +### Get Account + +#### Get Account By Public Key + +```go + account, err := c.GetAccountByPK(pk) + if err != nil { + log.Fatal().Err(err).Msg("failed to get account from registrar") + } + log.Info().Any("account", account).Send() +``` + +#### Get Account By Twin ID + +```go + account, err := c.GetAccount(id) + if err != nil { + log.Fatal().Err(err).Msg("failed to get account from registrar") + } + log.Info().Any("account", account).Send() + +``` + +### Update Account + +```go + err := c.UpdateAccount(client.UpdateAccountWithRelays(relays), client.UpdateAccountWithRMBEncKey(rmbEncKey)) + if err != nil { + log.Fatal().Err(err).Msg("failed to get account from registrar") + } + log.Info().Msg("account updated successfully") +``` + +### Ensure Account + +```go + account, err := c.EnsureAccount(relays, rmbEncKey) + if err != nil { + log.Fatal().Err(err).Msg("failed to ensure account account from registrar") + } + log.Info().Any("account", account).Send() +``` + +### Create Farm + +```go + id, err := c.CreateFarm(farmName, twinID, false) + if err != nil { + log.Fatal().Err(err).Msg("failed to create new farm on registrar") + } + + log.Info().Uint64("farmID", id).Msg("farm created successfully") +``` + +### Get Farm + +```go + farm, err := c.GetFarm(id) + if err != nil { + log.Fatal().Err(err).Msg("failed to get farm from registrar") + } + log.Info().Any("farm", farm).Send() +``` + +### List Farms + +```go + farms, err := c.ListFarms(ListFarmWithName(name)) + if err != nil { + log.Fatal().Err(err).Msg("failed to list farms from registrar") + } + log.Info().Any("farm", farms[0]).Send() +``` + +### Update Farm + +```go + err := c.UpdateFarm(farmID, client.UpdateFarmWithName(name)) + if err != nil { + log.Fatal().Err(err).Msg("failed to get farm from registrar") + } + log.Info().Msg("farm updated successfully") +``` + +### Register a Node + +```go + id, err := c.RegisterNode(farmID, twinID, interfaces, location, resources, serialNumber, secureBoot, virtualized) + if err != nil { + log.Fatal().Err(err).Msg("failed to register a new node on registrar") + } + log.Info().Uint64("nodeID", id).Msg("node registered successfully") +``` + +### Get Node + +#### Get Node With Node ID + +```go + node, err := c.GetNode(id) + if err != nil { + log.Fatal().Err(err).Msg("failed to get node from registrar") + } + log.Info().Any("node", node).Send() +``` + +#### Get Node With Twin ID + +```go + node, err := c.GetNodeByTwinID(id) + if err != nil { + log.Fatal().Err(err).Msg("failed to get node from registrar") + } + log.Info().Any("node", node).Send() +``` + +### List Nodes + +```go + nodes, err := c.ListNodes(client.ListNodesWithFarmID(id)) + if err != nil { + log.Fatal().Err(err).Msg("failed to list nodes from registrar") + } + log.Info().Any("node", node[0]).Send() +``` + +### Update Node + +```go + err := c.UpdateNode(client.UpdateNodesWithLocation(location)) + if err != nil { + log.Fatal().Err(err).Msg("failed to update node location on the registrar") + } + log.Info().Msg("node updated successfully") +``` + +### Update Node Uptime + +```go + err := c.ReportUptime(report) + if err != nil { + log.Fatal().Err(err).Msg("failed to update node uptime in the registrar") + } + log.Info().Msg("node uptime is updated successfully") +``` From caf590c728efd06e7ef8913d48ac134859848be4 Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Wed, 26 Feb 2025 16:00:24 +0200 Subject: [PATCH 17/27] remove twin id from create farm --- node-registrar/client/farm.go | 8 ++++---- node-registrar/client/farm_test.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/node-registrar/client/farm.go b/node-registrar/client/farm.go index f82bece..1e718d2 100644 --- a/node-registrar/client/farm.go +++ b/node-registrar/client/farm.go @@ -13,8 +13,8 @@ import ( var ErrorFarmNotFround = fmt.Errorf("failed to get requested farm from node regiatrar") -func (c RegistrarClient) CreateFarm(farmName string, twinID uint64, dedicated bool) (farmID uint64, err error) { - return c.createFarm(farmName, twinID, dedicated) +func (c RegistrarClient) CreateFarm(farmName string, dedicated bool) (farmID uint64, err error) { + return c.createFarm(farmName, dedicated) } func (c RegistrarClient) UpdateFarm(farmID uint64, opts ...UpdateFarmOpts) (err error) { @@ -91,7 +91,7 @@ func UpdateFarmWithDedicated() UpdateFarmOpts { } } -func (c RegistrarClient) createFarm(farmName string, twinID uint64, dedicated bool) (farmID uint64, err error) { +func (c RegistrarClient) createFarm(farmName string, dedicated bool) (farmID uint64, err error) { err = c.ensureTwinID() if err != nil { return farmID, errors.Wrap(err, "failed to ensure twin id") @@ -104,7 +104,7 @@ func (c RegistrarClient) createFarm(farmName string, twinID uint64, dedicated bo data := Farm{ FarmName: farmName, - TwinID: twinID, + TwinID: c.twinID, Dedicated: dedicated, } diff --git a/node-registrar/client/farm_test.go b/node-registrar/client/farm_test.go index 1af1eba..045c549 100644 --- a/node-registrar/client/farm_test.go +++ b/node-registrar/client/farm_test.go @@ -36,13 +36,13 @@ func TestCreateFarm(t *testing.T) { t.Run("test create farm with status conflict", func(t *testing.T) { request = createFarmStatusConflict - _, err = c.CreateFarm(farm.FarmName, farm.TwinID, farm.Dedicated) + _, err = c.CreateFarm(farm.FarmName, farm.Dedicated) require.Error(err) }) t.Run("test create farm with status ok", func(t *testing.T) { request = createFarmStatusCreated - result, err := c.CreateFarm(farm.FarmName, farm.TwinID, farm.Dedicated) + result, err := c.CreateFarm(farm.FarmName, farm.Dedicated) require.NoError(err) require.Equal(farm.FarmID, result) }) From 8fcef4f219723cec9e91732e90e8d9bb94ac2fee Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Wed, 26 Feb 2025 21:42:26 +0200 Subject: [PATCH 18/27] update node registrar client to accept private key instead of seed --- node-registrar/client/account_test.go | 29 ++++++++++++++------------- node-registrar/client/client.go | 3 +-- node-registrar/client/client_test.go | 17 ++++++++-------- node-registrar/client/farm_test.go | 21 ++++++++++--------- node-registrar/client/node_test.go | 23 +++++++++++---------- node-registrar/client/utils_test.go | 15 ++++++-------- 6 files changed, 54 insertions(+), 54 deletions(-) diff --git a/node-registrar/client/account_test.go b/node-registrar/client/account_test.go index 36388fd..76e4e8b 100644 --- a/node-registrar/client/account_test.go +++ b/node-registrar/client/account_test.go @@ -1,6 +1,7 @@ package client import ( + "encoding/base64" "net/http" "net/http/httptest" "net/url" @@ -14,9 +15,9 @@ func TestCreateAccount(t *testing.T) { var count int require := require.New(t) - _, seed, publicKeyBase64, err := aliceKeys() + publicKey, privateKey, err := aliceKeys() require.NoError(err) - account.PublicKey = publicKeyBase64 + account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -31,7 +32,7 @@ func TestCreateAccount(t *testing.T) { require.NoError(err) request = newClientWithNoAccount - c, err := NewRegistrarClient(baseURL, seed) + c, err := NewRegistrarClient(baseURL, privateKey) require.NoError(err) t.Run("test create account created successfully", func(t *testing.T) { @@ -47,9 +48,9 @@ func TestUpdateAccount(t *testing.T) { var count int require := require.New(t) - pk, seed, publicKeyBase64, err := aliceKeys() + publicKey, privateKey, err := aliceKeys() require.NoError(err) - account.PublicKey = publicKeyBase64 + account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -67,11 +68,11 @@ func TestUpdateAccount(t *testing.T) { t.Run("test update account updated successfully", func(t *testing.T) { count = 0 request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, seed) + c, err := NewRegistrarClient(baseURL, privateKey) require.NoError(err) require.Equal(c.twinID, account.TwinID) - require.Equal([]byte(c.keyPair.publicKey), pk) + require.Equal(c.keyPair.publicKey, publicKey) request = updateAccountWithStatusOK relays := []string{"relay1"} @@ -81,10 +82,10 @@ func TestUpdateAccount(t *testing.T) { t.Run("test update account account not found", func(t *testing.T) { request = newClientWithNoAccount - c, err := NewRegistrarClient(baseURL, seed) + c, err := NewRegistrarClient(baseURL, privateKey) require.NoError(err) - require.Equal([]byte(c.keyPair.publicKey), pk) + require.Equal(c.keyPair.publicKey, publicKey) request = updateAccountWithNoAccount relays := []string{"relay1"} @@ -98,9 +99,9 @@ func TestGetAccount(t *testing.T) { var count int require := require.New(t) - pk, seed, publicKeyBase64, err := aliceKeys() + publicKey, privateKey, err := aliceKeys() require.NoError(err) - account.PublicKey = publicKeyBase64 + account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -116,10 +117,10 @@ func TestGetAccount(t *testing.T) { count = 0 request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, seed) + c, err := NewRegistrarClient(baseURL, privateKey) require.NoError(err) - require.Equal(c.twinID, account.TwinID) - require.Equal([]byte(c.keyPair.publicKey), pk) + require.Equal(account.TwinID, c.twinID) + require.Equal(publicKey, c.keyPair.publicKey) t.Run("test get account with id account not found", func(t *testing.T) { request = getAccountWithIDStatusNotFount diff --git a/node-registrar/client/client.go b/node-registrar/client/client.go index 7e5c41a..0f3576f 100644 --- a/node-registrar/client/client.go +++ b/node-registrar/client/client.go @@ -20,10 +20,9 @@ type RegistrarClient struct { baseURL string } -func NewRegistrarClient(baseURL string, sk []byte) (cli RegistrarClient, err error) { +func NewRegistrarClient(baseURL string, privateKey ed25519.PrivateKey) (cli RegistrarClient, err error) { client := http.DefaultClient - privateKey := ed25519.NewKeyFromSeed(sk) publicKey, ok := privateKey.Public().(ed25519.PublicKey) if !ok { return cli, errors.Wrap(err, "failed to get public key of provided private key") diff --git a/node-registrar/client/client_test.go b/node-registrar/client/client_test.go index dada9ee..187ee21 100644 --- a/node-registrar/client/client_test.go +++ b/node-registrar/client/client_test.go @@ -1,6 +1,7 @@ package client import ( + "encoding/base64" "net/http" "net/http/httptest" "net/url" @@ -14,9 +15,9 @@ func TestNewRegistrarClient(t *testing.T) { var count int require := require.New(t) - pk, seed, publicKeyBase64, err := aliceKeys() + publicKey, privateKey, err := aliceKeys() require.NoError(err) - account.PublicKey = publicKeyBase64 + account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -33,29 +34,29 @@ func TestNewRegistrarClient(t *testing.T) { t.Run("test new registrar client with no account", func(t *testing.T) { count = 0 request = newClientWithNoAccount - c, err := NewRegistrarClient(baseURL, seed) + c, err := NewRegistrarClient(baseURL, privateKey) require.NoError(err) require.Equal(uint64(0), c.twinID) require.Equal(uint64(0), c.nodeID) - require.Equal([]byte(c.keyPair.publicKey), pk) + require.Equal(publicKey, c.keyPair.publicKey) }) t.Run("test new registrar client with account and no node", func(t *testing.T) { count = 0 request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, seed) + c, err := NewRegistrarClient(baseURL, privateKey) require.NoError(err) require.Equal(account.TwinID, c.twinID) require.Equal(uint64(0), c.nodeID) - require.Equal(pk, []byte(c.keyPair.publicKey)) + require.Equal(publicKey, c.keyPair.publicKey) }) t.Run("test new registrar client with account and node", func(t *testing.T) { count = 0 request = newClientWithAccountAndNode - c, err := NewRegistrarClient(baseURL, seed) + c, err := NewRegistrarClient(baseURL, privateKey) require.NoError(err) require.Equal(account.TwinID, c.twinID) require.Equal(nodeID, c.nodeID) - require.Equal(pk, []byte(c.keyPair.publicKey)) + require.Equal(publicKey, c.keyPair.publicKey) }) } diff --git a/node-registrar/client/farm_test.go b/node-registrar/client/farm_test.go index 045c549..1ce134b 100644 --- a/node-registrar/client/farm_test.go +++ b/node-registrar/client/farm_test.go @@ -1,6 +1,7 @@ package client import ( + "encoding/base64" "net/http" "net/http/httptest" "net/url" @@ -14,9 +15,9 @@ func TestCreateFarm(t *testing.T) { var count int require := require.New(t) - _, seed, publicKeyBase64, err := aliceKeys() + publicKey, privateKey, err := aliceKeys() require.NoError(err) - account.PublicKey = publicKeyBase64 + account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -31,7 +32,7 @@ func TestCreateFarm(t *testing.T) { require.NoError(err) request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, seed) + c, err := NewRegistrarClient(baseURL, privateKey) require.NoError(err) t.Run("test create farm with status conflict", func(t *testing.T) { @@ -53,9 +54,9 @@ func TestUpdateFarm(t *testing.T) { var count int require := require.New(t) - _, seed, publicKeyBase64, err := aliceKeys() + publicKey, privateKey, err := aliceKeys() require.NoError(err) - account.PublicKey = publicKeyBase64 + account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -71,7 +72,7 @@ func TestUpdateFarm(t *testing.T) { t.Run("test update farm with status unauthorzed", func(t *testing.T) { request = newClientWithNoAccount - c, err := NewRegistrarClient(baseURL, seed) + c, err := NewRegistrarClient(baseURL, privateKey) require.NoError(err) request = updateFarmWithStatusUnauthorized @@ -82,7 +83,7 @@ func TestUpdateFarm(t *testing.T) { t.Run("test update farm with status ok", func(t *testing.T) { count = 0 request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, seed) + c, err := NewRegistrarClient(baseURL, privateKey) require.NoError(err) request = updateFarmWithStatusOK @@ -96,9 +97,9 @@ func TestGetFarm(t *testing.T) { var count int require := require.New(t) - _, seed, publicKeyBase64, err := aliceKeys() + publicKey, privateKey, err := aliceKeys() require.NoError(err) - account.PublicKey = publicKeyBase64 + account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -114,7 +115,7 @@ func TestGetFarm(t *testing.T) { count = 0 request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, seed) + c, err := NewRegistrarClient(baseURL, privateKey) require.NoError(err) t.Run("test get farm with status not found", func(t *testing.T) { diff --git a/node-registrar/client/node_test.go b/node-registrar/client/node_test.go index 0d10bf2..b39318b 100644 --- a/node-registrar/client/node_test.go +++ b/node-registrar/client/node_test.go @@ -1,6 +1,7 @@ package client import ( + "encoding/base64" "net/http" "net/http/httptest" "net/url" @@ -15,9 +16,9 @@ func TestRegistarNode(t *testing.T) { var count int require := require.New(t) - _, seed, publicKeyBase64, err := aliceKeys() + publicKey, privateKey, err := aliceKeys() require.NoError(err) - account.PublicKey = publicKeyBase64 + account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -33,7 +34,7 @@ func TestRegistarNode(t *testing.T) { t.Run("test registar node no account", func(t *testing.T) { request = newClientWithNoAccount - c, err := NewRegistrarClient(baseURL, seed) + c, err := NewRegistrarClient(baseURL, privateKey) require.NoError(err) request = registerNodeWithNoAccount @@ -44,7 +45,7 @@ func TestRegistarNode(t *testing.T) { t.Run("test registar node, node already exist", func(t *testing.T) { count = 0 request = newClientWithAccountAndNode - c, err := NewRegistrarClient(baseURL, seed) + c, err := NewRegistrarClient(baseURL, privateKey) require.NoError(err) count = 0 @@ -56,7 +57,7 @@ func TestRegistarNode(t *testing.T) { t.Run("test registar node, created successfully", func(t *testing.T) { count = 0 request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, seed) + c, err := NewRegistrarClient(baseURL, privateKey) require.NoError(err) count = 0 @@ -72,9 +73,9 @@ func TestUpdateNode(t *testing.T) { var count int require := require.New(t) - _, seed, publicKeyBase64, err := aliceKeys() + publicKey, privateKey, err := aliceKeys() require.NoError(err) - account.PublicKey = publicKeyBase64 + account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -89,7 +90,7 @@ func TestUpdateNode(t *testing.T) { require.NoError(err) request = newClientWithAccountAndNode - c, err := NewRegistrarClient(baseURL, seed) + c, err := NewRegistrarClient(baseURL, privateKey) require.NoError(err) t.Run("test update node with status ok", func(t *testing.T) { @@ -116,9 +117,9 @@ func TestGetNode(t *testing.T) { var count int require := require.New(t) - _, seed, publicKeyBase64, err := aliceKeys() + publicKey, privateKey, err := aliceKeys() require.NoError(err) - account.PublicKey = publicKeyBase64 + account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -133,7 +134,7 @@ func TestGetNode(t *testing.T) { require.NoError(err) request = newClientWithAccountAndNode - c, err := NewRegistrarClient(baseURL, seed) + c, err := NewRegistrarClient(baseURL, privateKey) require.NoError(err) t.Run("test get node status not found", func(t *testing.T) { diff --git a/node-registrar/client/utils_test.go b/node-registrar/client/utils_test.go index ea95657..bdb83b3 100644 --- a/node-registrar/client/utils_test.go +++ b/node-registrar/client/utils_test.go @@ -2,7 +2,6 @@ package client import ( "crypto/ed25519" - "encoding/base64" "encoding/hex" "encoding/json" "fmt" @@ -238,19 +237,17 @@ func serverHandler(r *http.Request, request, count int, require *require.Asserti return http.StatusNotAcceptable, nil } -func aliceKeys() (pk, seed []byte, pkBase64 string, err error) { - seed, err = hex.DecodeString(aliceSeed) +func aliceKeys() (publicKey ed25519.PublicKey, privateKey ed25519.PrivateKey, err error) { + seed, err := hex.DecodeString(aliceSeed) if err != nil { return } - privateKey := ed25519.NewKeyFromSeed(seed) - pk, ok := privateKey.Public().(ed25519.PublicKey) + privateKey = ed25519.NewKeyFromSeed(seed) + publicKey, ok := privateKey.Public().(ed25519.PublicKey) if !ok { - return pk, seed, pkBase64, fmt.Errorf("failed to get public key of provided private key") + return publicKey, privateKey, fmt.Errorf("failed to get public key of provided private key") } - pkBase64 = base64.StdEncoding.EncodeToString(pk) - - return pk, seed, pkBase64, nil + return } From 3aaaf8fe823dc2786cde6f2385d604caee938039 Mon Sep 17 00:00:00 2001 From: Eslam Nawara <67752395+Eslam-Nawara@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:36:15 +0200 Subject: [PATCH 19/27] Add mnemonics to registrar client (#23) * support mnemonics in registrar client * fix drive keys from seed * update registrar client readme file * remove extra print lines * remove extra comments * lower case error messages --- go.work.sum | 1 + node-registrar/client/README.md | 31 ++++--- node-registrar/client/account.go | 114 +++++++++++++++----------- node-registrar/client/account_test.go | 28 +++---- node-registrar/client/client.go | 34 ++++---- node-registrar/client/client_test.go | 17 ++-- node-registrar/client/farm.go | 28 ++++--- node-registrar/client/farm_test.go | 20 ++--- node-registrar/client/mnemonic.go | 44 ++++++++++ node-registrar/client/node.go | 44 ++++++---- node-registrar/client/node_test.go | 29 +++---- node-registrar/client/utils.go | 8 +- node-registrar/client/utils_test.go | 26 ++---- node-registrar/client/zos_version.go | 14 ++-- node-registrar/go.mod | 1 + node-registrar/go.sum | 2 + 16 files changed, 260 insertions(+), 181 deletions(-) create mode 100644 node-registrar/client/mnemonic.go diff --git a/go.work.sum b/go.work.sum index 3d14c0f..732a963 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,5 +1,6 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= diff --git a/node-registrar/client/README.md b/node-registrar/client/README.md index a900b55..b663e61 100644 --- a/node-registrar/client/README.md +++ b/node-registrar/client/README.md @@ -22,7 +22,7 @@ The Node Registrar Client enables communication with the ThreeFold Grid's node r * **Create Account**: Create new account on the registrar with uniqe key. * **Update Account**: Update the account configuration (relays & rmbEncKey). -* **Ensure Account**: Ensures that an account is created with specific seed. +* **Ensure Account**: Ensures that an account is created with specific seed/mnemonic. * **Get Account**: Get an account using either its twin\_id or its public\_key. ### Farms @@ -42,10 +42,10 @@ The Node Registrar Client enables communication with the ThreeFold Grid's node r #### Version Operations -| Method | Description | Parameters | Returns | -|-----------------|----------------------------------|----------------------------|---------------------| -| GetZosVersion | Get current zos version | None | (ZosVersion, error) | -| SetZosVersion | Update zos version (admin-only) | version string, force bool | error | +| Method | Description | Parameters | Returns | +|-----------------|----------------------------------|------------------------------------|---------------------| +| GetZosVersion | Get current zos version | None | (ZosVersion, error) | +| SetZosVersion | Update zos version (admin-only) | version string, safeToUpgrade bool | error | #### Account Management @@ -95,15 +95,20 @@ import ( func main() { registrarURL := "https://registrar.dev4.grid.tf/v1" - s := make([]byte, 32) - _, err := rand.Read(s) - if err != nil { - log.Fatal().Err(err).Send() - } - seed = hex.EncodeToString(s) - fmt.Println("New Seed (Hex):", seed) + // Generate 128-bit entropy (12-word mnemonic) + entropy, err := bip39.NewEntropy(128) + if err != nil { + panic(err) + } + + // Generate mnemonic from entropy + mnemonic, err = bip39.NewMnemonic(entropy) + if err != nil { + panic(err) + } + fmt.Println("New Mnemonic:", mnemonic) - cli, err := client.NewRegistrarClient(registrarURL, s) + cli, err := client.NewRegistrarClient(registrarURL, mnemonic) if err != nil { panic(err) } diff --git a/node-registrar/client/account.go b/node-registrar/client/account.go index 890dce9..f671105 100644 --- a/node-registrar/client/account.go +++ b/node-registrar/client/account.go @@ -2,7 +2,6 @@ package client import ( "bytes" - "crypto/ed25519" "encoding/base64" "encoding/json" "fmt" @@ -11,44 +10,83 @@ import ( "time" "github.com/pkg/errors" + "github.com/vedhavyas/go-subkey/v2" ) var ErrorAccountNotFround = fmt.Errorf("failed to get requested account from node regiatrar") -func (c RegistrarClient) CreateAccount(relays []string, rmbEncKey string) (account Account, err error) { +func (c *RegistrarClient) CreateAccount(relays []string, rmbEncKey string) (account Account, mnemonic string, err error) { return c.createAccount(relays, rmbEncKey) } -func (c RegistrarClient) GetAccount(id uint64) (account Account, err error) { +func (c *RegistrarClient) GetAccount(id uint64) (account Account, err error) { return c.getAccount(id) } -func (c RegistrarClient) GetAccountByPK(pk []byte) (account Account, err error) { +func (c *RegistrarClient) GetAccountByPK(pk []byte) (account Account, err error) { return c.getAccountByPK(pk) } -func (c RegistrarClient) UpdateAccount(opts ...UpdateAccountOpts) (err error) { +func (c *RegistrarClient) UpdateAccount(opts ...UpdateAccountOpts) (err error) { return c.updateAccount(opts) } -func (c RegistrarClient) EnsureAccount(relays []string, rmbEncKey string) (account Account, err error) { +type accountCfg struct { + relays []string + rmbEncKey string +} + +type ( + UpdateAccountOpts func(*accountCfg) +) + +func UpdateAccountWithRelays(relays []string) UpdateAccountOpts { + return func(n *accountCfg) { + n.relays = relays + } +} + +func UpdateAccountWithRMBEncKey(rmbEncKey string) UpdateAccountOpts { + return func(n *accountCfg) { + n.rmbEncKey = rmbEncKey + } +} + +func (c *RegistrarClient) EnsureAccount(relays []string, rmbEncKey string) (account Account, err error) { return c.ensureAccount(relays, rmbEncKey) } -func (c *RegistrarClient) createAccount(relays []string, rmbEncKey string) (account Account, err error) { +func (c *RegistrarClient) createAccount(relays []string, rmbEncKey string) (account Account, mnemonic string, err error) { url, err := url.JoinPath(c.baseURL, "accounts") if err != nil { - return account, errors.Wrap(err, "failed to construct registrar url") + return account, mnemonic, errors.Wrap(err, "failed to construct registrar url") } - timestamp := time.Now().Unix() - publicKeyBase64 := base64.StdEncoding.EncodeToString(c.keyPair.publicKey) + var keyPair subkey.KeyPair + if len(c.mnemonic) != 0 { + mnemonic = c.mnemonic + keyPair, err = parseKeysFromMnemonicOrSeed(c.mnemonic) + } else { + mnemonic, keyPair, err = generateNewMnemonic() + } + if err != nil { + return account, mnemonic, err + } + c.keyPair = keyPair + c.mnemonic = mnemonic + + publicKeyBase64 := base64.StdEncoding.EncodeToString(c.keyPair.Public()) + + timestamp := time.Now().Unix() challenge := []byte(fmt.Sprintf("%d:%v", timestamp, publicKeyBase64)) - signature := ed25519.Sign(c.keyPair.privateKey, challenge) + signature, err := keyPair.Sign(challenge) + if err != nil { + return account, mnemonic, errors.Wrap(err, "failed to sign account creation request") + } data := map[string]any{ - "public_key": c.keyPair.publicKey, + "public_key": c.keyPair.Public(), "signature": signature, "timestamp": timestamp, "rmb_enc_key": rmbEncKey, @@ -58,17 +96,17 @@ func (c *RegistrarClient) createAccount(relays []string, rmbEncKey string) (acco var body bytes.Buffer err = json.NewEncoder(&body).Encode(data) if err != nil { - return account, errors.Wrap(err, "failed to parse request body") + return account, mnemonic, errors.Wrap(err, "failed to parse request body") } resp, err := c.httpClient.Post(url, "application/json", &body) if err != nil { - return account, errors.Wrap(err, "failed to send request to the registrar") + return account, mnemonic, errors.Wrap(err, "failed to send request to the registrar") } if resp.StatusCode != http.StatusCreated { err = parseResponseError(resp.Body) - return account, errors.Wrapf(err, "failed to create account with status %s", resp.Status) + return account, mnemonic, errors.Wrapf(err, "failed to create account with status %s", resp.Status) } defer resp.Body.Close() @@ -78,7 +116,7 @@ func (c *RegistrarClient) createAccount(relays []string, rmbEncKey string) (acco return } -func (c RegistrarClient) getAccount(id uint64) (account Account, err error) { +func (c *RegistrarClient) getAccount(id uint64) (account Account, err error) { url, err := url.JoinPath(c.baseURL, "accounts") if err != nil { return account, errors.Wrap(err, "failed to construct registrar url") @@ -116,7 +154,7 @@ func (c RegistrarClient) getAccount(id uint64) (account Account, err error) { return } -func (c RegistrarClient) getAccountByPK(pk []byte) (account Account, err error) { +func (c *RegistrarClient) getAccountByPK(pk []byte) (account Account, err error) { url, err := url.JoinPath(c.baseURL, "accounts") if err != nil { return account, errors.Wrap(err, "failed to construct registrar url") @@ -157,7 +195,7 @@ func (c RegistrarClient) getAccountByPK(pk []byte) (account Account, err error) return account, err } -func (c RegistrarClient) updateAccount(opts []UpdateAccountOpts) (err error) { +func (c *RegistrarClient) updateAccount(opts []UpdateAccountOpts) (err error) { err = c.ensureTwinID() if err != nil { return errors.Wrap(err, "failed to ensure twin id") @@ -180,7 +218,11 @@ func (c RegistrarClient) updateAccount(opts []UpdateAccountOpts) (err error) { return } - req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) + authHeader, err := c.signRequest(time.Now().Unix()) + if err != nil { + return errors.Wrap(err, "failed to sign request") + } + req.Header.Set("X-Auth", authHeader) req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) @@ -200,36 +242,12 @@ func (c RegistrarClient) updateAccount(opts []UpdateAccountOpts) (err error) { return } -type accountCfg struct { - relays []string - rmbEncKey string -} - -type ( - UpdateAccountOpts func(*accountCfg) -) - -func UpdateAccountWithRelays(relays []string) UpdateAccountOpts { - return func(n *accountCfg) { - n.relays = relays - } -} - -func UpdateAccountWithRMBEncKey(rmbEncKey string) UpdateAccountOpts { - return func(n *accountCfg) { - n.rmbEncKey = rmbEncKey - } -} - -func (c RegistrarClient) ensureAccount(relays []string, rmbEncKey string) (account Account, err error) { - account, err = c.GetAccountByPK(c.keyPair.publicKey) +func (c *RegistrarClient) ensureAccount(relays []string, rmbEncKey string) (account Account, err error) { + account, err = c.GetAccountByPK(c.keyPair.Public()) if errors.Is(err, ErrorAccountNotFround) { - return c.CreateAccount(relays, rmbEncKey) - } else if err != nil { - return account, errors.Wrap(err, "failed to get account from the registrar") + account, _, err = c.CreateAccount(relays, rmbEncKey) } - - return + return account, err } func (c *RegistrarClient) ensureTwinID() error { @@ -237,7 +255,7 @@ func (c *RegistrarClient) ensureTwinID() error { return nil } - twin, err := c.getAccountByPK(c.keyPair.publicKey) + twin, err := c.getAccountByPK(c.keyPair.Public()) if err != nil { return errors.Wrap(err, "failed to get the account of the node, registrar client was not set up properly") } diff --git a/node-registrar/client/account_test.go b/node-registrar/client/account_test.go index 76e4e8b..798ae4f 100644 --- a/node-registrar/client/account_test.go +++ b/node-registrar/client/account_test.go @@ -15,9 +15,9 @@ func TestCreateAccount(t *testing.T) { var count int require := require.New(t) - publicKey, privateKey, err := aliceKeys() + keyPair, err := parseKeysFromMnemonicOrSeed(testMnemonic) require.NoError(err) - account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) + account.PublicKey = base64.StdEncoding.EncodeToString(keyPair.Public()) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -32,12 +32,12 @@ func TestCreateAccount(t *testing.T) { require.NoError(err) request = newClientWithNoAccount - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) t.Run("test create account created successfully", func(t *testing.T) { request = createAccountStatusCreated - result, err := c.CreateAccount(account.Relays, account.RMBEncKey) + result, _, err := c.CreateAccount(account.Relays, account.RMBEncKey) require.NoError(err) require.Equal(account, result) }) @@ -48,9 +48,9 @@ func TestUpdateAccount(t *testing.T) { var count int require := require.New(t) - publicKey, privateKey, err := aliceKeys() + keyPair, err := parseKeysFromMnemonicOrSeed(testMnemonic) require.NoError(err) - account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) + account.PublicKey = base64.StdEncoding.EncodeToString(keyPair.Public()) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -68,11 +68,11 @@ func TestUpdateAccount(t *testing.T) { t.Run("test update account updated successfully", func(t *testing.T) { count = 0 request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) require.Equal(c.twinID, account.TwinID) - require.Equal(c.keyPair.publicKey, publicKey) + require.Equal(c.keyPair, keyPair) request = updateAccountWithStatusOK relays := []string{"relay1"} @@ -82,10 +82,10 @@ func TestUpdateAccount(t *testing.T) { t.Run("test update account account not found", func(t *testing.T) { request = newClientWithNoAccount - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) - require.Equal(c.keyPair.publicKey, publicKey) + require.Equal(c.keyPair, keyPair) request = updateAccountWithNoAccount relays := []string{"relay1"} @@ -99,9 +99,9 @@ func TestGetAccount(t *testing.T) { var count int require := require.New(t) - publicKey, privateKey, err := aliceKeys() + keyPair, err := parseKeysFromMnemonicOrSeed(testMnemonic) require.NoError(err) - account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) + account.PublicKey = base64.StdEncoding.EncodeToString(keyPair.Public()) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -117,10 +117,10 @@ func TestGetAccount(t *testing.T) { count = 0 request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) require.Equal(account.TwinID, c.twinID) - require.Equal(publicKey, c.keyPair.publicKey) + require.Equal(keyPair, c.keyPair) t.Run("test get account with id account not found", func(t *testing.T) { request = getAccountWithIDStatusNotFount diff --git a/node-registrar/client/client.go b/node-registrar/client/client.go index 0f3576f..57e3174 100644 --- a/node-registrar/client/client.go +++ b/node-registrar/client/client.go @@ -1,40 +1,42 @@ package client import ( - "crypto/ed25519" "net/http" "github.com/pkg/errors" + "github.com/vedhavyas/go-subkey" ) -type keyPair struct { - privateKey ed25519.PrivateKey - publicKey ed25519.PublicKey -} - type RegistrarClient struct { httpClient http.Client - keyPair keyPair + baseURL string + keyPair subkey.KeyPair + mnemonic string nodeID uint64 twinID uint64 - baseURL string } -func NewRegistrarClient(baseURL string, privateKey ed25519.PrivateKey) (cli RegistrarClient, err error) { +func NewRegistrarClient(baseURL string, mnemonicOrSeed ...string) (cli RegistrarClient, err error) { client := http.DefaultClient - publicKey, ok := privateKey.Public().(ed25519.PublicKey) - if !ok { - return cli, errors.Wrap(err, "failed to get public key of provided private key") - } - cli = RegistrarClient{ httpClient: *client, - keyPair: keyPair{privateKey, publicKey}, baseURL: baseURL, } - account, err := cli.GetAccountByPK(publicKey) + if len(mnemonicOrSeed) == 0 { + return cli, nil + } + + keyPair, err := parseKeysFromMnemonicOrSeed(mnemonicOrSeed[0]) + if err != nil { + return cli, errors.Wrapf(err, "Failed to derive key pair from mnemonic/seed phrase %s", mnemonicOrSeed[0]) + } + + cli.keyPair = keyPair + cli.mnemonic = mnemonicOrSeed[0] + + account, err := cli.GetAccountByPK(keyPair.Public()) if errors.Is(err, ErrorAccountNotFround) { return cli, nil } else if err != nil { diff --git a/node-registrar/client/client_test.go b/node-registrar/client/client_test.go index 187ee21..0a2a973 100644 --- a/node-registrar/client/client_test.go +++ b/node-registrar/client/client_test.go @@ -15,9 +15,9 @@ func TestNewRegistrarClient(t *testing.T) { var count int require := require.New(t) - publicKey, privateKey, err := aliceKeys() + keyPair, err := parseKeysFromMnemonicOrSeed(testMnemonic) require.NoError(err) - account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) + account.PublicKey = base64.StdEncoding.EncodeToString(keyPair.Public()) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -34,29 +34,30 @@ func TestNewRegistrarClient(t *testing.T) { t.Run("test new registrar client with no account", func(t *testing.T) { count = 0 request = newClientWithNoAccount - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) require.Equal(uint64(0), c.twinID) require.Equal(uint64(0), c.nodeID) - require.Equal(publicKey, c.keyPair.publicKey) + require.Equal(keyPair, c.keyPair) }) t.Run("test new registrar client with account and no node", func(t *testing.T) { count = 0 request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) require.Equal(account.TwinID, c.twinID) require.Equal(uint64(0), c.nodeID) - require.Equal(publicKey, c.keyPair.publicKey) + require.Equal(keyPair, c.keyPair) }) + t.Run("test new registrar client with account and node", func(t *testing.T) { count = 0 request = newClientWithAccountAndNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) require.Equal(account.TwinID, c.twinID) require.Equal(nodeID, c.nodeID) - require.Equal(publicKey, c.keyPair.publicKey) + require.Equal(keyPair, c.keyPair) }) } diff --git a/node-registrar/client/farm.go b/node-registrar/client/farm.go index 1e718d2..2dedbc9 100644 --- a/node-registrar/client/farm.go +++ b/node-registrar/client/farm.go @@ -13,19 +13,19 @@ import ( var ErrorFarmNotFround = fmt.Errorf("failed to get requested farm from node regiatrar") -func (c RegistrarClient) CreateFarm(farmName string, dedicated bool) (farmID uint64, err error) { +func (c *RegistrarClient) CreateFarm(farmName string, dedicated bool) (farmID uint64, err error) { return c.createFarm(farmName, dedicated) } -func (c RegistrarClient) UpdateFarm(farmID uint64, opts ...UpdateFarmOpts) (err error) { +func (c *RegistrarClient) UpdateFarm(farmID uint64, opts ...UpdateFarmOpts) (err error) { return c.updateFarm(farmID, opts) } -func (c RegistrarClient) GetFarm(id uint64) (farm Farm, err error) { +func (c *RegistrarClient) GetFarm(id uint64) (farm Farm, err error) { return c.getFarm(id) } -func (c RegistrarClient) ListFarms(opts ...ListFarmOpts) (farms []Farm, err error) { +func (c *RegistrarClient) ListFarms(opts ...ListFarmOpts) (farms []Farm, err error) { return c.listFarms(opts...) } @@ -91,7 +91,7 @@ func UpdateFarmWithDedicated() UpdateFarmOpts { } } -func (c RegistrarClient) createFarm(farmName string, dedicated bool) (farmID uint64, err error) { +func (c *RegistrarClient) createFarm(farmName string, dedicated bool) (farmID uint64, err error) { err = c.ensureTwinID() if err != nil { return farmID, errors.Wrap(err, "failed to ensure twin id") @@ -119,7 +119,11 @@ func (c RegistrarClient) createFarm(farmName string, dedicated bool) (farmID uin return farmID, errors.Wrap(err, "failed to construct http request to the registrar") } - req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) + authHeader, err := c.signRequest(time.Now().Unix()) + if err != nil { + return farmID, errors.Wrap(err, "failed to sign request") + } + req.Header.Set("X-Auth", authHeader) req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) @@ -145,7 +149,7 @@ func (c RegistrarClient) createFarm(farmName string, dedicated bool) (farmID uin return result.FarmID, nil } -func (c RegistrarClient) updateFarm(farmID uint64, opts []UpdateFarmOpts) (err error) { +func (c *RegistrarClient) updateFarm(farmID uint64, opts []UpdateFarmOpts) (err error) { err = c.ensureTwinID() if err != nil { return errors.Wrap(err, "failed to ensure twin id") @@ -169,7 +173,11 @@ func (c RegistrarClient) updateFarm(farmID uint64, opts []UpdateFarmOpts) (err e return errors.Wrap(err, "failed to construct http request to the registrar") } - req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) + authHeader, err := c.signRequest(time.Now().Unix()) + if err != nil { + return errors.Wrap(err, "failed to sign request") + } + req.Header.Set("X-Auth", authHeader) req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) @@ -187,7 +195,7 @@ func (c RegistrarClient) updateFarm(farmID uint64, opts []UpdateFarmOpts) (err e return } -func (c RegistrarClient) getFarm(id uint64) (farm Farm, err error) { +func (c *RegistrarClient) getFarm(id uint64) (farm Farm, err error) { url, err := url.JoinPath(c.baseURL, "farms", fmt.Sprint(id)) if err != nil { return farm, errors.Wrap(err, "failed to construct registrar url") @@ -215,7 +223,7 @@ func (c RegistrarClient) getFarm(id uint64) (farm Farm, err error) { return } -func (c RegistrarClient) listFarms(opts ...ListFarmOpts) (farms []Farm, err error) { +func (c *RegistrarClient) listFarms(opts ...ListFarmOpts) (farms []Farm, err error) { url, err := url.JoinPath(c.baseURL, "farms") if err != nil { return farms, errors.Wrap(err, "failed to construct registrar url") diff --git a/node-registrar/client/farm_test.go b/node-registrar/client/farm_test.go index 1ce134b..4a4bae3 100644 --- a/node-registrar/client/farm_test.go +++ b/node-registrar/client/farm_test.go @@ -15,9 +15,9 @@ func TestCreateFarm(t *testing.T) { var count int require := require.New(t) - publicKey, privateKey, err := aliceKeys() + keyPair, err := parseKeysFromMnemonicOrSeed(testMnemonic) require.NoError(err) - account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) + account.PublicKey = base64.StdEncoding.EncodeToString(keyPair.Public()) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -32,7 +32,7 @@ func TestCreateFarm(t *testing.T) { require.NoError(err) request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) t.Run("test create farm with status conflict", func(t *testing.T) { @@ -54,9 +54,9 @@ func TestUpdateFarm(t *testing.T) { var count int require := require.New(t) - publicKey, privateKey, err := aliceKeys() + keyPair, err := parseKeysFromMnemonicOrSeed(testMnemonic) require.NoError(err) - account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) + account.PublicKey = base64.StdEncoding.EncodeToString(keyPair.Public()) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -72,7 +72,7 @@ func TestUpdateFarm(t *testing.T) { t.Run("test update farm with status unauthorzed", func(t *testing.T) { request = newClientWithNoAccount - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) request = updateFarmWithStatusUnauthorized @@ -83,7 +83,7 @@ func TestUpdateFarm(t *testing.T) { t.Run("test update farm with status ok", func(t *testing.T) { count = 0 request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) request = updateFarmWithStatusOK @@ -97,9 +97,9 @@ func TestGetFarm(t *testing.T) { var count int require := require.New(t) - publicKey, privateKey, err := aliceKeys() + keyPair, err := parseKeysFromMnemonicOrSeed(testMnemonic) require.NoError(err) - account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) + account.PublicKey = base64.StdEncoding.EncodeToString(keyPair.Public()) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -115,7 +115,7 @@ func TestGetFarm(t *testing.T) { count = 0 request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) t.Run("test get farm with status not found", func(t *testing.T) { diff --git a/node-registrar/client/mnemonic.go b/node-registrar/client/mnemonic.go new file mode 100644 index 0000000..f4cbb46 --- /dev/null +++ b/node-registrar/client/mnemonic.go @@ -0,0 +1,44 @@ +package client + +import ( + "github.com/cosmos/go-bip39" + "github.com/pkg/errors" + subkeyEd25519 "github.com/vedhavyas/go-subkey/v2/ed25519" + + "github.com/vedhavyas/go-subkey/v2" +) + +func (c *RegistrarClient) Mnemonic() string { + return c.mnemonic +} + +func parseKeysFromMnemonicOrSeed(mnemonicOrSeed string) (keypair subkey.KeyPair, err error) { + // otherwise drive key pair from seed + keypair, err = subkey.DeriveKeyPair(subkeyEd25519.Scheme{}, mnemonicOrSeed) + if err != nil { + return keypair, errors.Wrapf(err, "failed to derive key pair from seed %s", mnemonicOrSeed) + } + + return keypair, nil +} + +func generateNewMnemonic() (mnemonic string, keypair subkey.KeyPair, err error) { + // Generate 128-bit entropy (12-word mnemonic) + entropy, err := bip39.NewEntropy(128) + if err != nil { + return mnemonic, keypair, errors.Wrap(err, "failed to generate entropy") + } + + // Generate mnemonic from entropy + mnemonic, err = bip39.NewMnemonic(entropy) + if err != nil { + return mnemonic, keypair, errors.Wrap(err, "failed to generate mnemonic") + } + + // Drive key pair from mnemonic + keypair, err = subkey.DeriveKeyPair(subkeyEd25519.Scheme{}, mnemonic) + if err != nil { + return mnemonic, keypair, errors.Wrapf(err, "failed to derive key pair from mnemonic phrase %s", mnemonic) + } + return +} diff --git a/node-registrar/client/node.go b/node-registrar/client/node.go index e2d2bac..ee88731 100644 --- a/node-registrar/client/node.go +++ b/node-registrar/client/node.go @@ -14,7 +14,7 @@ import ( var ErrorNodeNotFround = fmt.Errorf("failed to get requested node from node regiatrar") -func (c RegistrarClient) RegisterNode( +func (c *RegistrarClient) RegisterNode( farmID uint64, twinID uint64, interfaces []Interface, @@ -27,23 +27,23 @@ func (c RegistrarClient) RegisterNode( return c.registerNode(farmID, twinID, interfaces, location, resources, serialNumber, secureBoot, virtualized) } -func (c RegistrarClient) UpdateNode(opts ...UpdateNodeOpts) (err error) { +func (c *RegistrarClient) UpdateNode(opts ...UpdateNodeOpts) (err error) { return c.updateNode(opts) } -func (c RegistrarClient) ReportUptime(report UptimeReport) (err error) { +func (c *RegistrarClient) ReportUptime(report UptimeReport) (err error) { return c.reportUptime(report) } -func (c RegistrarClient) GetNode(id uint64) (node Node, err error) { +func (c *RegistrarClient) GetNode(id uint64) (node Node, err error) { return c.getNode(id) } -func (c RegistrarClient) GetNodeByTwinID(id uint64) (node Node, err error) { +func (c *RegistrarClient) GetNodeByTwinID(id uint64) (node Node, err error) { return c.getNodeByTwinID(id) } -func (c RegistrarClient) ListNodes(opts ...ListNodeOpts) (nodes []Node, err error) { +func (c *RegistrarClient) ListNodes(opts ...ListNodeOpts) (nodes []Node, err error) { return c.listNodes(opts) } @@ -166,7 +166,7 @@ func UpdateNodeWithHealthy() UpdateNodeOpts { } } -func (c RegistrarClient) registerNode( +func (c *RegistrarClient) registerNode( farmID uint64, twinID uint64, interfaces []Interface, @@ -207,7 +207,11 @@ func (c RegistrarClient) registerNode( return nodeID, errors.Wrap(err, "failed to construct http request to the registrar") } - req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) + authHeader, err := c.signRequest(time.Now().Unix()) + if err != nil { + return nodeID, errors.Wrap(err, "failed to sign request") + } + req.Header.Set("X-Auth", authHeader) req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) @@ -231,7 +235,7 @@ func (c RegistrarClient) registerNode( return result.NodeID, err } -func (c RegistrarClient) updateNode(opts []UpdateNodeOpts) (err error) { +func (c *RegistrarClient) updateNode(opts []UpdateNodeOpts) (err error) { err = c.ensureNodeID() if err != nil { return err @@ -260,7 +264,11 @@ func (c RegistrarClient) updateNode(opts []UpdateNodeOpts) (err error) { return errors.Wrap(err, "failed to construct http request to the registrar") } - req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) + authHeader, err := c.signRequest(time.Now().Unix()) + if err != nil { + return errors.Wrap(err, "failed to sign request") + } + req.Header.Set("X-Auth", authHeader) req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) @@ -277,7 +285,7 @@ func (c RegistrarClient) updateNode(opts []UpdateNodeOpts) (err error) { return } -func (c RegistrarClient) reportUptime(report UptimeReport) (err error) { +func (c *RegistrarClient) reportUptime(report UptimeReport) (err error) { err = c.ensureNodeID() if err != nil { return err @@ -300,7 +308,11 @@ func (c RegistrarClient) reportUptime(report UptimeReport) (err error) { return errors.Wrap(err, "failed to construct http request to the registrar") } - req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) + authHeader, err := c.signRequest(time.Now().Unix()) + if err != nil { + return errors.Wrap(err, "failed to sign request") + } + req.Header.Set("X-Auth", authHeader) req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) @@ -317,7 +329,7 @@ func (c RegistrarClient) reportUptime(report UptimeReport) (err error) { return } -func (c RegistrarClient) getNode(id uint64) (node Node, err error) { +func (c *RegistrarClient) getNode(id uint64) (node Node, err error) { url, err := url.JoinPath(c.baseURL, "nodes", fmt.Sprint(id)) if err != nil { return node, errors.Wrap(err, "failed to construct registrar url") @@ -346,7 +358,7 @@ func (c RegistrarClient) getNode(id uint64) (node Node, err error) { return } -func (c RegistrarClient) getNodeByTwinID(id uint64) (node Node, err error) { +func (c *RegistrarClient) getNodeByTwinID(id uint64) (node Node, err error) { nodes, err := c.ListNodes(ListNodesWithTwinID(id)) if err != nil { return @@ -359,7 +371,7 @@ func (c RegistrarClient) getNodeByTwinID(id uint64) (node Node, err error) { return nodes[0], nil } -func (c RegistrarClient) listNodes(opts []ListNodeOpts) (nodes []Node, err error) { +func (c *RegistrarClient) listNodes(opts []ListNodeOpts) (nodes []Node, err error) { url, err := url.JoinPath(c.baseURL, "nodes") if err != nil { return nodes, errors.Wrap(err, "failed to construct registrar url") @@ -425,7 +437,7 @@ func (c *RegistrarClient) ensureNodeID() error { return nil } -func (c RegistrarClient) parseUpdateNodeOpts(node Node, opts []UpdateNodeOpts) Node { +func (c *RegistrarClient) parseUpdateNodeOpts(node Node, opts []UpdateNodeOpts) Node { cfg := nodeCfg{ farmID: 0, Location: Location{}, diff --git a/node-registrar/client/node_test.go b/node-registrar/client/node_test.go index b39318b..1028054 100644 --- a/node-registrar/client/node_test.go +++ b/node-registrar/client/node_test.go @@ -1,7 +1,6 @@ package client import ( - "encoding/base64" "net/http" "net/http/httptest" "net/url" @@ -16,10 +15,6 @@ func TestRegistarNode(t *testing.T) { var count int require := require.New(t) - publicKey, privateKey, err := aliceKeys() - require.NoError(err) - account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) - testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) w.WriteHeader(statusCode) @@ -34,7 +29,7 @@ func TestRegistarNode(t *testing.T) { t.Run("test registar node no account", func(t *testing.T) { request = newClientWithNoAccount - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) request = registerNodeWithNoAccount @@ -45,7 +40,7 @@ func TestRegistarNode(t *testing.T) { t.Run("test registar node, node already exist", func(t *testing.T) { count = 0 request = newClientWithAccountAndNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) count = 0 @@ -57,7 +52,7 @@ func TestRegistarNode(t *testing.T) { t.Run("test registar node, created successfully", func(t *testing.T) { count = 0 request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) count = 0 @@ -73,10 +68,10 @@ func TestUpdateNode(t *testing.T) { var count int require := require.New(t) - publicKey, privateKey, err := aliceKeys() - require.NoError(err) - account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) - + // publicKey, privateKey, err := aliceKeys() + // require.NoError(err) + // account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) + // testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) w.WriteHeader(statusCode) @@ -90,7 +85,7 @@ func TestUpdateNode(t *testing.T) { require.NoError(err) request = newClientWithAccountAndNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) t.Run("test update node with status ok", func(t *testing.T) { @@ -117,9 +112,9 @@ func TestGetNode(t *testing.T) { var count int require := require.New(t) - publicKey, privateKey, err := aliceKeys() - require.NoError(err) - account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) + // publicKey, privateKey, err := aliceKeys() + // require.NoError(err) + // account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -134,7 +129,7 @@ func TestGetNode(t *testing.T) { require.NoError(err) request = newClientWithAccountAndNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) t.Run("test get node status not found", func(t *testing.T) { diff --git a/node-registrar/client/utils.go b/node-registrar/client/utils.go index 3cf3995..3d64cba 100644 --- a/node-registrar/client/utils.go +++ b/node-registrar/client/utils.go @@ -1,7 +1,6 @@ package client import ( - "crypto/ed25519" "encoding/base64" "encoding/json" "fmt" @@ -10,9 +9,12 @@ import ( "github.com/pkg/errors" ) -func (c RegistrarClient) signRequest(timestamp int64) (authHeader string) { +func (c *RegistrarClient) signRequest(timestamp int64) (authHeader string, err error) { challenge := []byte(fmt.Sprintf("%d:%v", timestamp, c.twinID)) - signature := ed25519.Sign(c.keyPair.privateKey, challenge) + signature, err := c.keyPair.Sign(challenge) + if err != nil { + return "", err + } authHeader = fmt.Sprintf( "%s:%s", diff --git a/node-registrar/client/utils_test.go b/node-registrar/client/utils_test.go index bdb83b3..80b5d1a 100644 --- a/node-registrar/client/utils_test.go +++ b/node-registrar/client/utils_test.go @@ -1,8 +1,6 @@ package client import ( - "crypto/ed25519" - "encoding/hex" "encoding/json" "fmt" "net/http" @@ -11,10 +9,9 @@ import ( ) var ( - aliceSeed = "e5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a" - account = Account{TwinID: 1, Relays: []string{}, RMBEncKey: ""} - farm = Farm{FarmID: 1, FarmName: "freeFarm", TwinID: 1} - node = Node{NodeID: 1, FarmID: farmID, TwinID: twinID} + account = Account{TwinID: 1, Relays: []string{}, RMBEncKey: ""} + farm = Farm{FarmID: 1, FarmName: "freeFarm", TwinID: 1} + node = Node{NodeID: 1, FarmID: farmID, TwinID: twinID} ) const ( @@ -47,6 +44,8 @@ const ( getNodeWithTwinID listNodesInFarm + testMnemonic = "bottom drive obey lake curtain smoke basket hold race lonely fit walk" + farmID uint64 = 1 nodeID uint64 = 1 twinID uint64 = 1 @@ -236,18 +235,3 @@ func serverHandler(r *http.Request, request, count int, require *require.Asserti return http.StatusNotAcceptable, nil } - -func aliceKeys() (publicKey ed25519.PublicKey, privateKey ed25519.PrivateKey, err error) { - seed, err := hex.DecodeString(aliceSeed) - if err != nil { - return - } - - privateKey = ed25519.NewKeyFromSeed(seed) - publicKey, ok := privateKey.Public().(ed25519.PublicKey) - if !ok { - return publicKey, privateKey, fmt.Errorf("failed to get public key of provided private key") - } - - return -} diff --git a/node-registrar/client/zos_version.go b/node-registrar/client/zos_version.go index 9351261..05547fa 100644 --- a/node-registrar/client/zos_version.go +++ b/node-registrar/client/zos_version.go @@ -12,15 +12,15 @@ import ( "github.com/pkg/errors" ) -func (c RegistrarClient) GetZosVersion() (version ZosVersion, err error) { +func (c *RegistrarClient) GetZosVersion() (version ZosVersion, err error) { return c.getZosVersion() } -func (c RegistrarClient) SetZosVersion(v string, safeToUpgrade bool) (err error) { +func (c *RegistrarClient) SetZosVersion(v string, safeToUpgrade bool) (err error) { return c.setZosVersion(v, safeToUpgrade) } -func (c RegistrarClient) getZosVersion() (version ZosVersion, err error) { +func (c *RegistrarClient) getZosVersion() (version ZosVersion, err error) { url, err := url.JoinPath(c.baseURL, "zos", "version") if err != nil { return version, errors.Wrap(err, "failed to construct registrar url") @@ -59,7 +59,7 @@ func (c RegistrarClient) getZosVersion() (version ZosVersion, err error) { return } -func (c RegistrarClient) setZosVersion(v string, safeToUpgrade bool) (err error) { +func (c *RegistrarClient) setZosVersion(v string, safeToUpgrade bool) (err error) { err = c.ensureTwinID() if err != nil { return errors.Wrap(err, "failed to ensure twin id") @@ -96,7 +96,11 @@ func (c RegistrarClient) setZosVersion(v string, safeToUpgrade bool) (err error) return errors.Wrap(err, "failed to construct http request to the registrar") } - req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) + authHeader, err := c.signRequest(time.Now().Unix()) + if err != nil { + return errors.Wrap(err, "failed to sign request") + } + req.Header.Set("X-Auth", authHeader) req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) diff --git a/node-registrar/go.mod b/node-registrar/go.mod index 7ec9c7c..bbd37d0 100644 --- a/node-registrar/go.mod +++ b/node-registrar/go.mod @@ -61,6 +61,7 @@ require ( github.com/stretchr/testify v1.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect + github.com/vedhavyas/go-subkey v1.0.4 // indirect golang.org/x/arch v0.12.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/net v0.33.0 // indirect diff --git a/node-registrar/go.sum b/node-registrar/go.sum index 336ff8a..d24bbde 100644 --- a/node-registrar/go.sum +++ b/node-registrar/go.sum @@ -158,6 +158,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/vedhavyas/go-subkey v1.0.4 h1:QwjBZx4w7qXC2lmqol2jJfhaNXPI9BsgLZiMiCwqGDU= +github.com/vedhavyas/go-subkey v1.0.4/go.mod h1:aOIil/KS9hJlnr9ZSQKSoXdu/MbnkCxG4x9IOlLsMtI= github.com/vedhavyas/go-subkey/v2 v2.0.0 h1:LemDIsrVtRSOkp0FA8HxP6ynfKjeOj3BY2U9UNfeDMA= github.com/vedhavyas/go-subkey/v2 v2.0.0/go.mod h1:95aZ+XDCWAUUynjlmi7BtPExjXgXxByE0WfBwbmIRH4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= From 41cbe7e955c9669a8b81f51d0dc3ba56b8942cf6 Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Sun, 9 Mar 2025 11:17:19 +0200 Subject: [PATCH 20/27] fix update farm error parsing --- node-registrar/client/farm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-registrar/client/farm.go b/node-registrar/client/farm.go index 2dedbc9..9878dee 100644 --- a/node-registrar/client/farm.go +++ b/node-registrar/client/farm.go @@ -189,7 +189,7 @@ func (c *RegistrarClient) updateFarm(farmID uint64, opts []UpdateFarmOpts) (err if resp.StatusCode != http.StatusOK { err = parseResponseError(resp.Body) - return fmt.Errorf("failed to create farm with status code %s", resp.Status) + return errors.Wrapf(err, "failed to create farm with status code %s", resp.Status) } return From 7908c98a0ec5c015749d341dd919146f4e7c124e Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Sun, 9 Mar 2025 11:35:29 +0200 Subject: [PATCH 21/27] add stellar address --- node-registrar/client/types.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/node-registrar/client/types.go b/node-registrar/client/types.go index d7b1201..2007195 100644 --- a/node-registrar/client/types.go +++ b/node-registrar/client/types.go @@ -12,10 +12,11 @@ type Account struct { } type Farm struct { - FarmID uint64 `json:"farm_id"` - FarmName string `json:"farm_name"` - TwinID uint64 `json:"twin_id"` - Dedicated bool `json:"dedicated"` + FarmID uint64 `json:"farm_id"` + FarmName string `json:"farm_name"` + TwinID uint64 `json:"twin_id"` + Dedicated bool `json:"dedicated"` + StellarAddress string `json:"stellar_address"` } type Node struct { From c5b1cffead026e6c767c01c0122c86aa35d1a12a Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Sun, 9 Mar 2025 17:40:29 +0200 Subject: [PATCH 22/27] support create/update farm with stellar address --- node-registrar/client/farm.go | 88 +++++++++++++++++++++++++---------- 1 file changed, 64 insertions(+), 24 deletions(-) diff --git a/node-registrar/client/farm.go b/node-registrar/client/farm.go index 9878dee..df379fe 100644 --- a/node-registrar/client/farm.go +++ b/node-registrar/client/farm.go @@ -6,15 +6,17 @@ import ( "fmt" "net/http" "net/url" + "strings" "time" + "unicode" "github.com/pkg/errors" ) var ErrorFarmNotFround = fmt.Errorf("failed to get requested farm from node regiatrar") -func (c *RegistrarClient) CreateFarm(farmName string, dedicated bool) (farmID uint64, err error) { - return c.createFarm(farmName, dedicated) +func (c *RegistrarClient) CreateFarm(farmName, stellarAddr string, dedicated bool) (farmID uint64, err error) { + return c.createFarm(farmName, stellarAddr, dedicated) } func (c *RegistrarClient) UpdateFarm(farmID uint64, opts ...UpdateFarmOpts) (err error) { @@ -30,12 +32,13 @@ func (c *RegistrarClient) ListFarms(opts ...ListFarmOpts) (farms []Farm, err err } type farmCfg struct { - farmName string - farmID uint64 - twinID uint64 - dedicated bool - page uint32 - size uint32 + farmName string + farmID uint64 + twinID uint64 + dedicated bool + stellarAddress string + page uint32 + size uint32 } type ( @@ -91,26 +94,36 @@ func UpdateFarmWithDedicated() UpdateFarmOpts { } } -func (c *RegistrarClient) createFarm(farmName string, dedicated bool) (farmID uint64, err error) { - err = c.ensureTwinID() - if err != nil { +// UpdateFarmWithName set farm status to dedicated +func UpdateFarmWithStellarAddress(address string) UpdateFarmOpts { + return func(n *farmCfg) { + n.stellarAddress = address + } +} + +func (c *RegistrarClient) createFarm(farmName, stellarAddr string, dedicated bool) (farmID uint64, err error) { + if err := c.ensureTwinID(); err != nil { return farmID, errors.Wrap(err, "failed to ensure twin id") } + if err = validateStellarAddress(stellarAddr); err != nil { + return + } + url, err := url.JoinPath(c.baseURL, "farms") if err != nil { return farmID, errors.Wrap(err, "failed to construct registrar url") } data := Farm{ - FarmName: farmName, - TwinID: c.twinID, - Dedicated: dedicated, + FarmName: farmName, + TwinID: c.twinID, + Dedicated: dedicated, + StellarAddress: stellarAddr, } var body bytes.Buffer - err = json.NewEncoder(&body).Encode(data) - if err != nil { + if err = json.NewEncoder(&body).Encode(data); err != nil { return farmID, errors.Wrap(err, "failed to encode request body") } @@ -150,8 +163,7 @@ func (c *RegistrarClient) createFarm(farmName string, dedicated bool) (farmID ui } func (c *RegistrarClient) updateFarm(farmID uint64, opts []UpdateFarmOpts) (err error) { - err = c.ensureTwinID() - if err != nil { + if c.ensureTwinID(); err != nil { return errors.Wrap(err, "failed to ensure twin id") } @@ -163,8 +175,13 @@ func (c *RegistrarClient) updateFarm(farmID uint64, opts []UpdateFarmOpts) (err var body bytes.Buffer data := parseUpdateFarmOpts(opts) - err = json.NewEncoder(&body).Encode(data) - if err != nil { + if stellarAddr, ok := data["stellar_address"]; ok { + if err = validateStellarAddress(stellarAddr.(string)); err != nil { + return + } + } + + if err = json.NewEncoder(&body).Encode(data); err != nil { return errors.Wrap(err, "failed to encode request body") } @@ -215,8 +232,7 @@ func (c *RegistrarClient) getFarm(id uint64) (farm Farm, err error) { } defer resp.Body.Close() - err = json.NewDecoder(resp.Body).Decode(&farm) - if err != nil { + if err = json.NewDecoder(resp.Body).Decode(&farm); err != nil { return farm, err } @@ -253,8 +269,7 @@ func (c *RegistrarClient) listFarms(opts ...ListFarmOpts) (farms []Farm, err err return farms, errors.Wrapf(err, "failed to get list farms with status code %s", resp.Status) } defer resp.Body.Close() - err = json.NewDecoder(resp.Body).Decode(&farms) - if err != nil { + if err = json.NewDecoder(resp.Body).Decode(&farms); err != nil { return farms, errors.Wrap(err, "failed to decode response body") } @@ -318,6 +333,31 @@ func parseUpdateFarmOpts(opts []UpdateFarmOpts) map[string]any { if cfg.dedicated { data["dedicated"] = true } + if len(cfg.stellarAddress) != 0 { + data["stellar_address"] = cfg.stellarAddress + } return data } + +func validateStellarAddress(stellarAddr string) error { + stellarAddr = strings.TrimSpace(stellarAddr) + if len(stellarAddr) != 56 { + return fmt.Errorf("invalid stellar address %s, address length should be 56 characters", stellarAddr) + } + if stellarAddr[0] != 'G' { + return fmt.Errorf("invalid stellar address %s, address should should start with 'G'", stellarAddr) + } + + if strings.Compare(stellarAddr, strings.ToUpper(stellarAddr)) != 0 { + return fmt.Errorf("invalid stellar address %s, address should be all uppercase", stellarAddr) + } + + // check if not alphanumeric + for _, c := range stellarAddr { + if !unicode.IsLetter(c) && !unicode.IsNumber(c) { + return fmt.Errorf("invalid stellar address %s, address character should be alphanumeric only", stellarAddr) + } + } + return nil +} From 79f9bcb5105ee40ea981790bc4b2b54f84832b95 Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Sun, 9 Mar 2025 17:49:03 +0200 Subject: [PATCH 23/27] fix farm tests --- node-registrar/client/farm.go | 8 +++----- node-registrar/client/farm_test.go | 5 +++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/node-registrar/client/farm.go b/node-registrar/client/farm.go index df379fe..d552d13 100644 --- a/node-registrar/client/farm.go +++ b/node-registrar/client/farm.go @@ -163,7 +163,7 @@ func (c *RegistrarClient) createFarm(farmName, stellarAddr string, dedicated boo } func (c *RegistrarClient) updateFarm(farmID uint64, opts []UpdateFarmOpts) (err error) { - if c.ensureTwinID(); err != nil { + if err = c.ensureTwinID(); err != nil { return errors.Wrap(err, "failed to ensure twin id") } @@ -315,10 +315,7 @@ func parseListFarmOpts(opts []ListFarmOpts) map[string]any { } func parseUpdateFarmOpts(opts []UpdateFarmOpts) map[string]any { - cfg := farmCfg{ - farmName: "", - dedicated: false, - } + cfg := farmCfg{} for _, opt := range opts { opt(&cfg) @@ -333,6 +330,7 @@ func parseUpdateFarmOpts(opts []UpdateFarmOpts) map[string]any { if cfg.dedicated { data["dedicated"] = true } + if len(cfg.stellarAddress) != 0 { data["stellar_address"] = cfg.stellarAddress } diff --git a/node-registrar/client/farm_test.go b/node-registrar/client/farm_test.go index 4a4bae3..c75efac 100644 --- a/node-registrar/client/farm_test.go +++ b/node-registrar/client/farm_test.go @@ -35,15 +35,16 @@ func TestCreateFarm(t *testing.T) { c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) + stellarAddr := "GBB3H4F7N3R26I6XU2V2P5WYJZQ5N7E4FQH6E5D2X3OGJ2KLTGZXQW34" t.Run("test create farm with status conflict", func(t *testing.T) { request = createFarmStatusConflict - _, err = c.CreateFarm(farm.FarmName, farm.Dedicated) + _, err = c.CreateFarm(farm.FarmName, stellarAddr, farm.Dedicated) require.Error(err) }) t.Run("test create farm with status ok", func(t *testing.T) { request = createFarmStatusCreated - result, err := c.CreateFarm(farm.FarmName, farm.Dedicated) + result, err := c.CreateFarm(farm.FarmName, stellarAddr, farm.Dedicated) require.NoError(err) require.Equal(farm.FarmID, result) }) From 0761f4fd22a6e38afda3fa35fdb13cb7a7000f60 Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Mon, 10 Mar 2025 11:59:55 +0200 Subject: [PATCH 24/27] update stellarAddress json constraints --- node-registrar/pkg/db/models.go | 2 +- node-registrar/pkg/server/handlers.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/node-registrar/pkg/db/models.go b/node-registrar/pkg/db/models.go index dcf56fa..3540ef3 100644 --- a/node-registrar/pkg/db/models.go +++ b/node-registrar/pkg/db/models.go @@ -24,7 +24,7 @@ type Farm struct { FarmID uint64 `gorm:"primaryKey;autoIncrement" json:"farm_id"` FarmName string `gorm:"size:40;not null;unique;check:farm_name <> ''" json:"farm_name" binding:"alphanum,required"` TwinID uint64 `json:"twin_id" gorm:"not null;check:twin_id > 0"` // Farmer account reference - StellarAddress string `json:"stellar_address" binding:"max=56,startswith=G,len=56,alphanum,uppercase"` + StellarAddress string `json:"stellar_address" binding:"required,startswith=G,len=56,alphanum,uppercase"` Dedicated bool `json:"dedicated"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` diff --git a/node-registrar/pkg/server/handlers.go b/node-registrar/pkg/server/handlers.go index 1e9b8e5..d4cc2e2 100644 --- a/node-registrar/pkg/server/handlers.go +++ b/node-registrar/pkg/server/handlers.go @@ -132,7 +132,7 @@ func (s Server) createFarmHandler(c *gin.Context) { type UpdateFarmRequest struct { FarmName string `json:"farm_name" binding:"max=40"` - StellarAddress string `json:"stellar_address" binding:"max=56,startswith=G,len=56,alphanum,uppercase"` + StellarAddress string `json:"stellar_address" binding:"startswith=G,len=56,alphanum,uppercase"` } // @Summary Update farm From b9fc0760bc27f9ed7ae4216e2edc793f12ad0b92 Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Mon, 10 Mar 2025 14:06:38 +0200 Subject: [PATCH 25/27] fix typos in client --- node-registrar/client/account.go | 8 ++++---- node-registrar/client/client.go | 4 ++-- node-registrar/client/farm.go | 4 ++-- node-registrar/client/node.go | 16 ++++++++++------ 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/node-registrar/client/account.go b/node-registrar/client/account.go index f671105..7f5e262 100644 --- a/node-registrar/client/account.go +++ b/node-registrar/client/account.go @@ -13,7 +13,7 @@ import ( "github.com/vedhavyas/go-subkey/v2" ) -var ErrorAccountNotFround = fmt.Errorf("failed to get requested account from node regiatrar") +var ErrorAccountNotFound = fmt.Errorf("failed to get requested account from node registrar") func (c *RegistrarClient) CreateAccount(relays []string, rmbEncKey string) (account Account, mnemonic string, err error) { return c.createAccount(relays, rmbEncKey) @@ -141,7 +141,7 @@ func (c *RegistrarClient) getAccount(id uint64) (account Account, err error) { } if resp.StatusCode == http.StatusNotFound { - return account, ErrorAccountNotFround + return account, ErrorAccountNotFound } if resp.StatusCode != http.StatusOK { @@ -182,7 +182,7 @@ func (c *RegistrarClient) getAccountByPK(pk []byte) (account Account, err error) defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { - return account, ErrorAccountNotFround + return account, ErrorAccountNotFound } if resp.StatusCode != http.StatusOK { @@ -244,7 +244,7 @@ func (c *RegistrarClient) updateAccount(opts []UpdateAccountOpts) (err error) { func (c *RegistrarClient) ensureAccount(relays []string, rmbEncKey string) (account Account, err error) { account, err = c.GetAccountByPK(c.keyPair.Public()) - if errors.Is(err, ErrorAccountNotFround) { + if errors.Is(err, ErrorAccountNotFound) { account, _, err = c.CreateAccount(relays, rmbEncKey) } return account, err diff --git a/node-registrar/client/client.go b/node-registrar/client/client.go index 57e3174..d520736 100644 --- a/node-registrar/client/client.go +++ b/node-registrar/client/client.go @@ -37,7 +37,7 @@ func NewRegistrarClient(baseURL string, mnemonicOrSeed ...string) (cli Registrar cli.mnemonic = mnemonicOrSeed[0] account, err := cli.GetAccountByPK(keyPair.Public()) - if errors.Is(err, ErrorAccountNotFround) { + if errors.Is(err, ErrorAccountNotFound) { return cli, nil } else if err != nil { return cli, errors.Wrap(err, "failed to get account with public key") @@ -45,7 +45,7 @@ func NewRegistrarClient(baseURL string, mnemonicOrSeed ...string) (cli Registrar cli.twinID = account.TwinID node, err := cli.GetNodeByTwinID(account.TwinID) - if errors.Is(err, ErrorNodeNotFround) { + if errors.Is(err, ErrorNodeNotFound) { return cli, nil } else if err != nil { return cli, errors.Wrapf(err, "failed to get node with twin id %d", account.TwinID) diff --git a/node-registrar/client/farm.go b/node-registrar/client/farm.go index d552d13..2f950b3 100644 --- a/node-registrar/client/farm.go +++ b/node-registrar/client/farm.go @@ -13,7 +13,7 @@ import ( "github.com/pkg/errors" ) -var ErrorFarmNotFround = fmt.Errorf("failed to get requested farm from node regiatrar") +var ErrorFarmNotFound = fmt.Errorf("failed to get requested farm from node registrar") func (c *RegistrarClient) CreateFarm(farmName, stellarAddr string, dedicated bool) (farmID uint64, err error) { return c.createFarm(farmName, stellarAddr, dedicated) @@ -223,7 +223,7 @@ func (c *RegistrarClient) getFarm(id uint64) (farm Farm, err error) { } if resp.StatusCode == http.StatusNotFound { - return farm, ErrorFarmNotFround + return farm, ErrorFarmNotFound } if resp.StatusCode != http.StatusOK { diff --git a/node-registrar/client/node.go b/node-registrar/client/node.go index ee88731..5dc5404 100644 --- a/node-registrar/client/node.go +++ b/node-registrar/client/node.go @@ -12,7 +12,7 @@ import ( "github.com/pkg/errors" ) -var ErrorNodeNotFround = fmt.Errorf("failed to get requested node from node regiatrar") +var ErrorNodeNotFound = fmt.Errorf("failed to get requested node from node registrar") func (c *RegistrarClient) RegisterNode( farmID uint64, @@ -219,11 +219,15 @@ func (c *RegistrarClient) registerNode( return nodeID, errors.Wrap(err, "failed to send request to registrer the node") } - if resp == nil || resp.StatusCode != http.StatusCreated { + if resp == nil { + return 0, errors.New("no response received") + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusCreated { err = parseResponseError(resp.Body) return 0, errors.Wrapf(err, "failed to create node on the registrar with status code %s", resp.Status) } - defer resp.Body.Close() result := struct { NodeID uint64 `json:"node_id"` @@ -341,7 +345,7 @@ func (c *RegistrarClient) getNode(id uint64) (node Node, err error) { } if resp.StatusCode == http.StatusNotFound { - return node, ErrorNodeNotFround + return node, ErrorNodeNotFound } if resp.StatusCode != http.StatusOK { @@ -365,7 +369,7 @@ func (c *RegistrarClient) getNodeByTwinID(id uint64) (node Node, err error) { } if len(nodes) == 0 { - return node, ErrorNodeNotFround + return node, ErrorNodeNotFound } return nodes[0], nil @@ -401,7 +405,7 @@ func (c *RegistrarClient) listNodes(opts []ListNodeOpts) (nodes []Node, err erro } if resp.StatusCode == http.StatusNotFound { - return nodes, ErrorNodeNotFround + return nodes, ErrorNodeNotFound } if resp.StatusCode != http.StatusOK { From 3eb0fb7f3ea377c708e96c69e23dfe16e8c72bff Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Mon, 10 Mar 2025 15:11:22 +0200 Subject: [PATCH 26/27] add functions documentation --- node-registrar/client/README.md | 6 +++--- node-registrar/client/account.go | 16 ++++++++++++---- node-registrar/client/client.go | 1 + node-registrar/client/farm.go | 17 +++++++++++++++-- node-registrar/client/mnemonic.go | 4 +++- node-registrar/client/node.go | 6 ++++++ node-registrar/client/utils.go | 2 ++ node-registrar/client/zos_version.go | 2 ++ 8 files changed, 44 insertions(+), 10 deletions(-) diff --git a/node-registrar/client/README.md b/node-registrar/client/README.md index b663e61..6220604 100644 --- a/node-registrar/client/README.md +++ b/node-registrar/client/README.md @@ -28,12 +28,12 @@ The Node Registrar Client enables communication with the ThreeFold Grid's node r ### Farms * **Create Farm**: Create new farm on the registrar with uniqe name. -* **update Farm**: update farm configuration (farm\_id, dedicated). -* **Get Farm**: Get a farm using either its farm\_id. +* **update Farm**: Update farm configuration (farm\_id, dedicated). +* **Get Farm**: Get a farm using its farm\_id. ### Node -* **Register Node**: Register physical/virtual nodes with the TFGrid. +* **Register Node**: Register physical/virtual nodes with on TFGrid. * **Update Node**: Update node configuration (farm\_id, interfaces, resources, location, secure\_boot, virtualized). * **Get Node**: Fetch registered node details using (node\_id, twin\_id, farm\_id). * **Update Node Uptime**: Update node Uptime. diff --git a/node-registrar/client/account.go b/node-registrar/client/account.go index 7f5e262..124f24e 100644 --- a/node-registrar/client/account.go +++ b/node-registrar/client/account.go @@ -15,18 +15,22 @@ import ( var ErrorAccountNotFound = fmt.Errorf("failed to get requested account from node registrar") +// CreateAccount create new account on the registrar with uniqe mnemonic. func (c *RegistrarClient) CreateAccount(relays []string, rmbEncKey string) (account Account, mnemonic string, err error) { return c.createAccount(relays, rmbEncKey) } -func (c *RegistrarClient) GetAccount(id uint64) (account Account, err error) { - return c.getAccount(id) +// GetAccount get an account using either its twinID +func (c *RegistrarClient) GetAccount(twinID uint64) (account Account, err error) { + return c.getAccount(twinID) } -func (c *RegistrarClient) GetAccountByPK(pk []byte) (account Account, err error) { - return c.getAccountByPK(pk) +// GetAccountByPK get an account using either its its publicKey. +func (c *RegistrarClient) GetAccountByPK(publicKey []byte) (account Account, err error) { + return c.getAccountByPK(publicKey) } +// UpdateAccount update the account configuration (relays or rmbEncKey). func (c *RegistrarClient) UpdateAccount(opts ...UpdateAccountOpts) (err error) { return c.updateAccount(opts) } @@ -40,18 +44,21 @@ type ( UpdateAccountOpts func(*accountCfg) ) +// UpdateAccountWithRelays update the account relays func UpdateAccountWithRelays(relays []string) UpdateAccountOpts { return func(n *accountCfg) { n.relays = relays } } +// UpdateAccountWithRMBEncKey update the account rmb encryption key func UpdateAccountWithRMBEncKey(rmbEncKey string) UpdateAccountOpts { return func(n *accountCfg) { n.rmbEncKey = rmbEncKey } } +// EnsureAccount ensures that an account is created with specific seed/mnemonic. func (c *RegistrarClient) EnsureAccount(relays []string, rmbEncKey string) (account Account, err error) { return c.ensureAccount(relays, rmbEncKey) } @@ -250,6 +257,7 @@ func (c *RegistrarClient) ensureAccount(relays []string, rmbEncKey string) (acco return account, err } +// ensureTwinID ensures that the RegistrarClient is set up properly with a valid public key representing an account on the registrar func (c *RegistrarClient) ensureTwinID() error { if c.twinID != 0 { return nil diff --git a/node-registrar/client/client.go b/node-registrar/client/client.go index d520736..3da00ef 100644 --- a/node-registrar/client/client.go +++ b/node-registrar/client/client.go @@ -16,6 +16,7 @@ type RegistrarClient struct { twinID uint64 } +// NewRegistrarClient creates a new client with optional seed or mnemonic func NewRegistrarClient(baseURL string, mnemonicOrSeed ...string) (cli RegistrarClient, err error) { client := http.DefaultClient diff --git a/node-registrar/client/farm.go b/node-registrar/client/farm.go index 2f950b3..e416fd4 100644 --- a/node-registrar/client/farm.go +++ b/node-registrar/client/farm.go @@ -15,18 +15,22 @@ import ( var ErrorFarmNotFound = fmt.Errorf("failed to get requested farm from node registrar") +// CreateFarm create new farm on the registrar with uniqe name. func (c *RegistrarClient) CreateFarm(farmName, stellarAddr string, dedicated bool) (farmID uint64, err error) { return c.createFarm(farmName, stellarAddr, dedicated) } +// UpdateFarm update farm configuration (farmName, stellarAddress, dedicated). func (c *RegistrarClient) UpdateFarm(farmID uint64, opts ...UpdateFarmOpts) (err error) { return c.updateFarm(farmID, opts) } -func (c *RegistrarClient) GetFarm(id uint64) (farm Farm, err error) { - return c.getFarm(id) +// GetFarm get a farm using its farmID +func (c *RegistrarClient) GetFarm(farmID uint64) (farm Farm, err error) { + return c.getFarm(farmID) } +// ListFarms get a list of farm using ListFarmOpts func (c *RegistrarClient) ListFarms(opts ...ListFarmOpts) (farms []Farm, err error) { return c.listFarms(opts...) } @@ -46,48 +50,56 @@ type ( UpdateFarmOpts func(*farmCfg) ) +// ListFarmWithName lists farms with farm name func ListFarmWithName(name string) ListFarmOpts { return func(n *farmCfg) { n.farmName = name } } +// ListFarmWithFarmID lists farms with farmID func ListFarmWithFarmID(id uint64) ListFarmOpts { return func(n *farmCfg) { n.farmID = id } } +// ListFarmWithTwinID lists farms with twinID func ListFarmWithTwinID(id uint64) ListFarmOpts { return func(n *farmCfg) { n.twinID = id } } +// ListFarmWithDedicated lists dedicated farms func ListFarmWithDedicated() ListFarmOpts { return func(n *farmCfg) { n.dedicated = true } } +// ListFarmWithPage lists farms in a certain page func ListFarmWithPage(page uint32) ListFarmOpts { return func(n *farmCfg) { n.page = page } } +// ListFarmWithPage lists size number of farms func ListFarmWithSize(size uint32) ListFarmOpts { return func(n *farmCfg) { n.size = size } } +// UpdateFarmWithName update farm name func UpdateFarmWithName(name string) UpdateFarmOpts { return func(n *farmCfg) { n.farmName = name } } +// UpdateFarmWithName set farm status to dedicated func UpdateFarmWithDedicated() UpdateFarmOpts { return func(n *farmCfg) { n.dedicated = true @@ -338,6 +350,7 @@ func parseUpdateFarmOpts(opts []UpdateFarmOpts) map[string]any { return data } +// validateStellarAddress ensures that the address is valid stellar address func validateStellarAddress(stellarAddr string) error { stellarAddr = strings.TrimSpace(stellarAddr) if len(stellarAddr) != 56 { diff --git a/node-registrar/client/mnemonic.go b/node-registrar/client/mnemonic.go index f4cbb46..ea0ef5b 100644 --- a/node-registrar/client/mnemonic.go +++ b/node-registrar/client/mnemonic.go @@ -12,16 +12,18 @@ func (c *RegistrarClient) Mnemonic() string { return c.mnemonic } +// parseKeysFromMnemonicOrSeed drives keypair from mnemonic or seed func parseKeysFromMnemonicOrSeed(mnemonicOrSeed string) (keypair subkey.KeyPair, err error) { // otherwise drive key pair from seed keypair, err = subkey.DeriveKeyPair(subkeyEd25519.Scheme{}, mnemonicOrSeed) if err != nil { - return keypair, errors.Wrapf(err, "failed to derive key pair from seed %s", mnemonicOrSeed) + return keypair, errors.Wrapf(err, "failed to drive key pair from seed %s", mnemonicOrSeed) } return keypair, nil } +// generateNewMnemonic generates new mnemonic and keypair func generateNewMnemonic() (mnemonic string, keypair subkey.KeyPair, err error) { // Generate 128-bit entropy (12-word mnemonic) entropy, err := bip39.NewEntropy(128) diff --git a/node-registrar/client/node.go b/node-registrar/client/node.go index 5dc5404..b2ba5a8 100644 --- a/node-registrar/client/node.go +++ b/node-registrar/client/node.go @@ -14,6 +14,7 @@ import ( var ErrorNodeNotFound = fmt.Errorf("failed to get requested node from node registrar") +// RegisterNode register physical/virtual nodes with on TFGrid. func (c *RegistrarClient) RegisterNode( farmID uint64, twinID uint64, @@ -27,22 +28,27 @@ func (c *RegistrarClient) RegisterNode( return c.registerNode(farmID, twinID, interfaces, location, resources, serialNumber, secureBoot, virtualized) } +// UpdateNode update node configuration (farmID, interfaces, resources, location, secureBoot, virtualized). func (c *RegistrarClient) UpdateNode(opts ...UpdateNodeOpts) (err error) { return c.updateNode(opts) } +// ReportUptime update node Uptime. func (c *RegistrarClient) ReportUptime(report UptimeReport) (err error) { return c.reportUptime(report) } +// GetNode gets registered node details using nodeID func (c *RegistrarClient) GetNode(id uint64) (node Node, err error) { return c.getNode(id) } +// GetNodeByTwinID gets registered node details using twinID func (c *RegistrarClient) GetNodeByTwinID(id uint64) (node Node, err error) { return c.getNodeByTwinID(id) } +// ListNodes lists registered nodes details using (nodeID, twinID, farmID). func (c *RegistrarClient) ListNodes(opts ...ListNodeOpts) (nodes []Node, err error) { return c.listNodes(opts) } diff --git a/node-registrar/client/utils.go b/node-registrar/client/utils.go index 3d64cba..27652c6 100644 --- a/node-registrar/client/utils.go +++ b/node-registrar/client/utils.go @@ -9,6 +9,7 @@ import ( "github.com/pkg/errors" ) +// signRequest signs request with challenge with format timestamp:twinID func (c *RegistrarClient) signRequest(timestamp int64) (authHeader string, err error) { challenge := []byte(fmt.Sprintf("%d:%v", timestamp, c.twinID)) signature, err := c.keyPair.Sign(challenge) @@ -24,6 +25,7 @@ func (c *RegistrarClient) signRequest(timestamp int64) (authHeader string, err e return } +// parseResponseError parse json response error func parseResponseError(body io.Reader) (err error) { errResp := struct { Error string `json:"error"` diff --git a/node-registrar/client/zos_version.go b/node-registrar/client/zos_version.go index 05547fa..9ce848d 100644 --- a/node-registrar/client/zos_version.go +++ b/node-registrar/client/zos_version.go @@ -12,10 +12,12 @@ import ( "github.com/pkg/errors" ) +// GetZosVersion gets zos version for specific network func (c *RegistrarClient) GetZosVersion() (version ZosVersion, err error) { return c.getZosVersion() } +// SetZosVersion sets zos version for specific network only valid for network admin func (c *RegistrarClient) SetZosVersion(v string, safeToUpgrade bool) (err error) { return c.setZosVersion(v, safeToUpgrade) } From 55d9ee59d45b4f8209f70478d7aa04cc4ad834c2 Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Thu, 20 Mar 2025 14:05:42 +0200 Subject: [PATCH 27/27] use base64 encoding for signature in create account --- node-registrar/client/account.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node-registrar/client/account.go b/node-registrar/client/account.go index 124f24e..808fc52 100644 --- a/node-registrar/client/account.go +++ b/node-registrar/client/account.go @@ -93,8 +93,8 @@ func (c *RegistrarClient) createAccount(relays []string, rmbEncKey string) (acco } data := map[string]any{ - "public_key": c.keyPair.Public(), - "signature": signature, + "public_key": base64.StdEncoding.EncodeToString(c.keyPair.Public()), + "signature": base64.StdEncoding.EncodeToString(signature), "timestamp": timestamp, "rmb_enc_key": rmbEncKey, "relays": relays,