From 248c1f903c484f253aaf452c02910a395b888da2 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 24 Jan 2025 10:50:19 +0300 Subject: [PATCH] netmap: expose Candidate structure to RPC bindings Unfortunately, we don't have any more elegant way to do this now. Signed-off-by: Roman Khimov --- contracts/netmap/config.yml | 2 +- contracts/netmap/contract.go | 7 ++ contracts/netmap/contract.nef | Bin 5497 -> 5505 bytes contracts/netmap/manifest.json | 2 +- rpc/netmap/rpcbinding.go | 145 +++++++++++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 2 deletions(-) diff --git a/contracts/netmap/config.yml b/contracts/netmap/config.yml index c9c90b41..676d8587 100644 --- a/contracts/netmap/config.yml +++ b/contracts/netmap/config.yml @@ -1,5 +1,5 @@ name: "NeoFS Netmap" -safemethods: ["innerRingList", "epoch", "netmap", "netmapCandidates", "snapshot", "snapshotByEpoch", "config", "listConfig", "version", "listNodes", "listCandidates", "cleanupThreshold"] +safemethods: ["innerRingList", "epoch", "netmap", "netmapCandidates", "snapshot", "snapshotByEpoch", "config", "listConfig", "version", "listNodes", "listCandidates", "cleanupThreshold", "unusedCandidate"] permissions: - methods: ["update", "newEpoch"] events: diff --git a/contracts/netmap/contract.go b/contracts/netmap/contract.go index 08216314..b1b4ce3f 100644 --- a/contracts/netmap/contract.go +++ b/contracts/netmap/contract.go @@ -775,6 +775,13 @@ func CleanupThreshold() int { return storage.Get(storage.GetReadOnlyContext(), []byte(cleanupThresholdKey)).(int) } +// UnusedCandidate does nothing except marking Candidate structure as used one +// thereby making RPC binding generator produce code for it. It is a temporary +// solution until we have proper iterator types. Never use it. +func UnusedCandidate() Candidate { + return Candidate{} +} + // serializes and stores the given Node by its public key in the contract storage, // and throws AddPeerSuccess notification after this. // diff --git a/contracts/netmap/contract.nef b/contracts/netmap/contract.nef index 8d811baa17ed8729583680c270e9af1ef340ddeb..de0c42ba88bba61121665d9a2692d1bcca2e8101 100755 GIT binary patch delta 207 zcmeyV)u_E8pOKMga{=Q?CdTWNtyq#7izm-xsb&1SS%!5bGh^H2V;s7S5tF}g{9~Lk z`7@_8WBX=vE>1>9?a7YZLEKs#3=A2jqD%}7C6f*K1>9<;jlRLEK6l3=A2jqD%}7C6f*KEyAR>|x*`&;_C z0#o(x|Nm!HRt1}~Ze|g%W18$G^qKMBWIN#|#`MVtg>xAXY}Oa?V`TB4(aaA3vCKgD diff --git a/contracts/netmap/manifest.json b/contracts/netmap/manifest.json index 6a3e9fa0..f2a6fbb6 100755 --- a/contracts/netmap/manifest.json +++ b/contracts/netmap/manifest.json @@ -1 +1 @@ -{"name":"NeoFS Netmap","abi":{"methods":[{"name":"_initialize","offset":0,"parameters":[],"returntype":"Void","safe":false},{"name":"_deploy","offset":93,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"addNode","offset":1927,"parameters":[{"name":"n","type":"Array"}],"returntype":"Void","safe":false},{"name":"addPeer","offset":1881,"parameters":[{"name":"nodeInfo","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"addPeerIR","offset":1841,"parameters":[{"name":"nodeInfo","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"cleanupThreshold","offset":4269,"parameters":[],"returntype":"Integer","safe":true},{"name":"config","offset":3693,"parameters":[{"name":"key","type":"ByteArray"}],"returntype":"Any","safe":true},{"name":"deleteNode","offset":2108,"parameters":[{"name":"pkey","type":"PublicKey"}],"returntype":"Void","safe":false},{"name":"epoch","offset":2753,"parameters":[],"returntype":"Integer","safe":true},{"name":"innerRingList","offset":1796,"parameters":[],"returntype":"Array","safe":true},{"name":"lastEpochBlock","offset":2792,"parameters":[],"returntype":"Integer","safe":false},{"name":"listCandidates","offset":2981,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"listConfig","offset":3772,"parameters":[],"returntype":"Array","safe":true},{"name":"listNodes","offset":2929,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"listNodes","offset":2937,"parameters":[{"name":"epoch","type":"Integer"}],"returntype":"InteropInterface","safe":true},{"name":"netmap","offset":2831,"parameters":[],"returntype":"Array","safe":true},{"name":"netmapCandidates","offset":2913,"parameters":[],"returntype":"Array","safe":true},{"name":"newEpoch","offset":2445,"parameters":[{"name":"epochNum","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setCleanupThreshold","offset":4213,"parameters":[{"name":"val","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setConfig","offset":3711,"parameters":[{"name":"id","type":"ByteArray"},{"name":"key","type":"ByteArray"},{"name":"val","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"snapshot","offset":3011,"parameters":[{"name":"diff","type":"Integer"}],"returntype":"Array","safe":true},{"name":"snapshotByEpoch","offset":3645,"parameters":[{"name":"epoch","type":"Integer"}],"returntype":"Array","safe":true},{"name":"subscribeForNewEpoch","offset":3828,"parameters":[{"name":"contract","type":"Hash160"}],"returntype":"Void","safe":false},{"name":"update","offset":1666,"parameters":[{"name":"script","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"updateSnapshotCount","offset":3165,"parameters":[{"name":"count","type":"Integer"}],"returntype":"Void","safe":false},{"name":"updateState","offset":2354,"parameters":[{"name":"state","type":"Integer"},{"name":"publicKey","type":"PublicKey"}],"returntype":"Void","safe":false},{"name":"updateStateIR","offset":2421,"parameters":[{"name":"state","type":"Integer"},{"name":"publicKey","type":"PublicKey"}],"returntype":"Void","safe":false},{"name":"version","offset":4209,"parameters":[],"returntype":"Integer","safe":true}],"events":[{"name":"AddNode","parameters":[{"name":"publicKey","type":"PublicKey"},{"name":"addresses","type":"Array"},{"name":"attributes","type":"Map"}]},{"name":"AddPeerSuccess","parameters":[{"name":"publicKey","type":"PublicKey"}]},{"name":"UpdateStateSuccess","parameters":[{"name":"publicKey","type":"PublicKey"},{"name":"state","type":"Integer"}]},{"name":"NewEpoch","parameters":[{"name":"epoch","type":"Integer"}]},{"name":"NewEpochSubscription","parameters":[{"name":"contract","type":"Hash160"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":["update","newEpoch"]}],"supportedstandards":[],"trusts":[],"extra":null} \ No newline at end of file +{"name":"NeoFS Netmap","abi":{"methods":[{"name":"_initialize","offset":0,"parameters":[],"returntype":"Void","safe":false},{"name":"_deploy","offset":93,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"addNode","offset":1927,"parameters":[{"name":"n","type":"Array"}],"returntype":"Void","safe":false},{"name":"addPeer","offset":1881,"parameters":[{"name":"nodeInfo","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"addPeerIR","offset":1841,"parameters":[{"name":"nodeInfo","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"cleanupThreshold","offset":4269,"parameters":[],"returntype":"Integer","safe":true},{"name":"config","offset":3693,"parameters":[{"name":"key","type":"ByteArray"}],"returntype":"Any","safe":true},{"name":"deleteNode","offset":2108,"parameters":[{"name":"pkey","type":"PublicKey"}],"returntype":"Void","safe":false},{"name":"epoch","offset":2753,"parameters":[],"returntype":"Integer","safe":true},{"name":"innerRingList","offset":1796,"parameters":[],"returntype":"Array","safe":true},{"name":"lastEpochBlock","offset":2792,"parameters":[],"returntype":"Integer","safe":false},{"name":"listCandidates","offset":2981,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"listConfig","offset":3772,"parameters":[],"returntype":"Array","safe":true},{"name":"listNodes","offset":2929,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"listNodes","offset":2937,"parameters":[{"name":"epoch","type":"Integer"}],"returntype":"InteropInterface","safe":true},{"name":"netmap","offset":2831,"parameters":[],"returntype":"Array","safe":true},{"name":"netmapCandidates","offset":2913,"parameters":[],"returntype":"Array","safe":true},{"name":"newEpoch","offset":2445,"parameters":[{"name":"epochNum","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setCleanupThreshold","offset":4213,"parameters":[{"name":"val","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setConfig","offset":3711,"parameters":[{"name":"id","type":"ByteArray"},{"name":"key","type":"ByteArray"},{"name":"val","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"snapshot","offset":3011,"parameters":[{"name":"diff","type":"Integer"}],"returntype":"Array","safe":true},{"name":"snapshotByEpoch","offset":3645,"parameters":[{"name":"epoch","type":"Integer"}],"returntype":"Array","safe":true},{"name":"subscribeForNewEpoch","offset":3828,"parameters":[{"name":"contract","type":"Hash160"}],"returntype":"Void","safe":false},{"name":"unusedCandidate","offset":4305,"parameters":[],"returntype":"Array","safe":true},{"name":"update","offset":1666,"parameters":[{"name":"script","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"updateSnapshotCount","offset":3165,"parameters":[{"name":"count","type":"Integer"}],"returntype":"Void","safe":false},{"name":"updateState","offset":2354,"parameters":[{"name":"state","type":"Integer"},{"name":"publicKey","type":"PublicKey"}],"returntype":"Void","safe":false},{"name":"updateStateIR","offset":2421,"parameters":[{"name":"state","type":"Integer"},{"name":"publicKey","type":"PublicKey"}],"returntype":"Void","safe":false},{"name":"version","offset":4209,"parameters":[],"returntype":"Integer","safe":true}],"events":[{"name":"AddNode","parameters":[{"name":"publicKey","type":"PublicKey"},{"name":"addresses","type":"Array"},{"name":"attributes","type":"Map"}]},{"name":"AddPeerSuccess","parameters":[{"name":"publicKey","type":"PublicKey"}]},{"name":"UpdateStateSuccess","parameters":[{"name":"publicKey","type":"PublicKey"},{"name":"state","type":"Integer"}]},{"name":"NewEpoch","parameters":[{"name":"epoch","type":"Integer"}]},{"name":"NewEpochSubscription","parameters":[{"name":"contract","type":"Hash160"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":["update","newEpoch"]}],"supportedstandards":[],"trusts":[],"extra":null} \ No newline at end of file diff --git a/rpc/netmap/rpcbinding.go b/rpc/netmap/rpcbinding.go index f81b10c0..4508e207 100644 --- a/rpc/netmap/rpcbinding.go +++ b/rpc/netmap/rpcbinding.go @@ -30,6 +30,15 @@ type CommonIRNode struct { PublicKey *keys.PublicKey } +// NetmapCandidate is a contract-specific netmap.Candidate type used by its methods. +type NetmapCandidate struct { + Addresses []string + Attributes map[string]string + Key *keys.PublicKey + State *big.Int + LastActiveEpoch *big.Int +} + // NetmapConfigRecord is a contract-specific netmap.ConfigRecord type used by its methods. type NetmapConfigRecord struct { Key []byte @@ -321,6 +330,11 @@ func (c *ContractReader) SnapshotByEpoch(epoch *big.Int) ([]*NetmapNode, error) }(unwrap.Item(c.invoker.Call(c.hash, "snapshotByEpoch", epoch))) } +// UnusedCandidate invokes `unusedCandidate` method of contract. +func (c *ContractReader) UnusedCandidate() (*NetmapCandidate, error) { + return itemToNetmapCandidate(unwrap.Item(c.invoker.Call(c.hash, "unusedCandidate"))) +} + // Version invokes `version` method of contract. func (c *ContractReader) Version() (*big.Int, error) { return unwrap.BigInt(c.invoker.Call(c.hash, "version")) @@ -735,6 +749,137 @@ func (res *CommonIRNode) FromStackItem(item stackitem.Item) error { return nil } +// itemToNetmapCandidate converts stack item into *NetmapCandidate. +// NULL item is returned as nil pointer without error. +func itemToNetmapCandidate(item stackitem.Item, err error) (*NetmapCandidate, error) { + if err != nil { + return nil, err + } + _, null := item.(stackitem.Null) + if null { + return nil, nil + } + var res = new(NetmapCandidate) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of NetmapCandidate from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *NetmapCandidate) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 5 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Addresses, err = func(item stackitem.Item) ([]string, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]string, len(arr)) + for i := range res { + res[i], err = func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Addresses: %w", err) + } + + index++ + res.Attributes, err = func(item stackitem.Item) (map[string]string, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[string]string) + for i := range m { + k, err := func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(m[i].Key) + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := func(item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + }(m[i].Value) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Attributes: %w", err) + } + + index++ + res.Key, err = func(item stackitem.Item) (*keys.PublicKey, error) { + b, err := item.TryBytes() + if err != nil { + return nil, err + } + k, err := keys.NewPublicKeyFromBytes(b, elliptic.P256()) + if err != nil { + return nil, err + } + return k, nil + }(arr[index]) + if err != nil { + return fmt.Errorf("field Key: %w", err) + } + + index++ + res.State, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field State: %w", err) + } + + index++ + res.LastActiveEpoch, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field LastActiveEpoch: %w", err) + } + + return nil +} + // itemToNetmapConfigRecord converts stack item into *NetmapConfigRecord. // NULL item is returned as nil pointer without error. func itemToNetmapConfigRecord(item stackitem.Item, err error) (*NetmapConfigRecord, error) {