-
Notifications
You must be signed in to change notification settings - Fork 807
Introducing GenericNodeID #1989
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5394aef
0ed3974
8ce752b
4feb4e1
58be8e6
b128954
1ef12a3
b5bdc6a
4182792
c430ce9
85bb0a2
536e573
7f29f23
0c585f6
2af164e
3a4a6bd
a664d35
f2af3f5
ac4a517
aafd74d
9d7127f
87e850b
a828b8c
0296b44
f6dfdd6
c2bb72a
7a2f437
248c659
02df155
4186d05
be4365a
a0094bc
9a42c63
73fbcd3
0958003
95b7cde
43e4e47
e9dbe07
0f341fd
0d634e2
2afccb3
2fe7f1c
5b854cb
5168d70
e1352fa
7f455e9
3186ed3
765a716
eca8960
0e1e84c
b16831c
147cfd6
c9e167b
a0d4c66
0d2c1d3
53c4302
e62fd9f
f5347a0
edf2b45
07b6edc
ab7c0ac
eb668c2
2f6c082
879d8ff
e4fbea3
62f05d8
391596c
89a4282
5b93a39
c7d3db1
9856159
ed0f956
8721f2e
1cb0495
457b1da
0bbf29b
54ddc4b
43f1eb5
849f1ff
238312d
1350547
82a8769
09d2c08
431c72b
c679b02
8fe07d9
72ca6cb
7740122
8f6039c
31a3741
16671fb
c8d1857
a0252ed
7a7e357
8993e72
63eb8a1
fd29f04
ffc4908
bad7d13
feaebba
7bb4505
3efdd6e
76c2a25
8e05c51
78cabfa
358152a
4fffa3d
d05caec
1ae79d4
2a2f801
1acba39
85ac064
a667456
113eede
5b1dcdb
52a9768
3d76349
05d303d
ea7e361
bc15253
10dae4c
da1609f
7981dcb
31c56f1
5145b26
694ab1a
807bf28
6e3b435
8b315d8
43043d4
0ac8972
f27c18c
6f35ae0
c05c853
fc57aef
911ae39
eea5821
346088f
c65c623
6e6a5fe
ee7fd40
865765f
92e8edf
e2867e3
46597bf
eca0488
19c527c
e37b649
839678e
6a129e2
d196285
68b857f
94d252f
b1c77b4
4ee3b32
46fba82
a70b1a1
af13438
9c95f87
ab7db24
e5fd7fe
1a5bd9c
529aabc
8837b98
05e4fa9
8ea46cd
a32d343
0dcdcc5
092ce06
4b3619f
ff4fdb2
1f194fa
26a757a
a22fd65
4203d2e
22843b4
132bb3b
130b4df
7918c58
dcfe530
ab4ab97
b8c01ff
d8ca7fb
1620e46
e334182
77b6c15
2ea4b0e
0d2e8f2
f0b6705
176b5a4
95d228a
44bf435
892be8e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
package ids | ||
|
||
import ( | ||
"encoding/json" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func FuzzNodeIDMarshallUnmarshalInversion(f *testing.F) { | ||
f.Fuzz(func(t *testing.T, buf string) { | ||
var ( | ||
require = require.New(t) | ||
input = NodeID{buf: buf} | ||
output = new(NodeID) | ||
) | ||
|
||
// json package marshalling | ||
b, err := json.Marshal(input) | ||
require.NoError(err) | ||
|
||
require.NoError(json.Unmarshal(b, output)) | ||
require.Equal(input, *output) | ||
|
||
// MarshalJson/UnmarshalJson | ||
output = new(NodeID) | ||
b, err = input.MarshalJSON() | ||
require.NoError(err) | ||
|
||
require.NoError(output.UnmarshalJSON(b)) | ||
require.Equal(input, *output) | ||
}) | ||
} | ||
|
||
func FuzzShortNodeIDMarshallUnmarshalInversion(f *testing.F) { | ||
f.Fuzz(func(t *testing.T, buf []byte) { | ||
if len(buf) != ShortNodeIDLen { | ||
return | ||
} | ||
|
||
var ( | ||
require = require.New(t) | ||
input = ShortNodeID(buf) | ||
output = new(ShortNodeID) | ||
) | ||
|
||
// json package marshalling | ||
b, err := json.Marshal(input) | ||
require.NoError(err) | ||
|
||
require.NoError(json.Unmarshal(b, output)) | ||
require.Equal(input, *output) | ||
|
||
// MarshalJson/UnmarshalJson | ||
output = new(ShortNodeID) | ||
b, err = input.MarshalJSON() | ||
require.NoError(err) | ||
|
||
require.NoError(output.UnmarshalJSON(b)) | ||
require.Equal(input, *output) | ||
}) | ||
} | ||
|
||
func FuzzShortIDMarshallUnmarshalInversion(f *testing.F) { | ||
f.Fuzz(func(t *testing.T, buf []byte) { | ||
if len(buf) != ShortIDLen { | ||
return | ||
} | ||
|
||
var ( | ||
require = require.New(t) | ||
input = ShortID(buf) | ||
output = new(ShortID) | ||
) | ||
|
||
// json package marshalling | ||
b, err := json.Marshal(input) | ||
require.NoError(err) | ||
|
||
require.NoError(json.Unmarshal(b, output)) | ||
require.Equal(input, *output) | ||
|
||
// MarshalJson/UnmarshalJson | ||
output = new(ShortID) | ||
b, err = input.MarshalJSON() | ||
require.NoError(err) | ||
|
||
require.NoError(output.UnmarshalJSON(b)) | ||
require.Equal(input, *output) | ||
}) | ||
} | ||
|
||
func FuzzIDMarshallUnmarshalInversion(f *testing.F) { | ||
f.Fuzz(func(t *testing.T, buf []byte) { | ||
if len(buf) != IDLen { | ||
return | ||
} | ||
|
||
var ( | ||
require = require.New(t) | ||
input = ID(buf) | ||
output = new(ID) | ||
) | ||
|
||
// json package marshalling | ||
b, err := json.Marshal(input) | ||
require.NoError(err) | ||
|
||
require.NoError(json.Unmarshal(b, output)) | ||
require.Equal(input, *output) | ||
|
||
// MarshalJson/UnmarshalJson | ||
output = new(ID) | ||
b, err = input.MarshalJSON() | ||
require.NoError(err) | ||
|
||
require.NoError(output.UnmarshalJSON(b)) | ||
require.Equal(input, *output) | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,51 +4,60 @@ | |
package ids | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/ava-labs/avalanchego/staking" | ||
"github.com/ava-labs/avalanchego/utils" | ||
"github.com/ava-labs/avalanchego/utils/cb58" | ||
"github.com/ava-labs/avalanchego/utils/hashing" | ||
) | ||
|
||
const ( | ||
NodeIDPrefix = "NodeID-" | ||
NodeIDLen = ShortIDLen | ||
|
||
NodeIDLen = 32 | ||
) | ||
|
||
var ( | ||
EmptyNodeID = NodeID{} | ||
|
||
errShortNodeID = errors.New("insufficient NodeID length") | ||
ErrBadNodeIDLength = errors.New("bad nodeID length") | ||
|
||
_ utils.Sortable[NodeID] = NodeID{} | ||
) | ||
|
||
type NodeID ShortID | ||
|
||
func (id NodeID) String() string { | ||
return ShortID(id).PrefixedString(NodeIDPrefix) | ||
// NodeID embeds a string, rather than being a type alias for a string | ||
// to be able to use custom marshaller for json encoding. | ||
// See https://github.com/golang/go/blob/go1.20.8/src/encoding/json/encode.go#L1004-L1026 | ||
// which checks for the string type first, then checks to see if a custom marshaller exists, | ||
// then checks if any other of the primitive types are provided. | ||
type NodeID struct { | ||
buf string | ||
} | ||
Comment on lines
+36
to
38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like we should explicitly link or document why func (w *reflectWithString) resolve() error {
if w.k.Kind() == reflect.String {
w.ks = w.k.String()
return nil
}
if tm, ok := w.k.Interface().(encoding.TextMarshaler); ok {
if w.k.Kind() == reflect.Pointer && w.k.IsNil() {
return nil
}
buf, err := tm.MarshalText()
w.ks = string(buf)
return err
}
switch w.k.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
w.ks = strconv.FormatInt(w.k.Int(), 10)
return nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
w.ks = strconv.FormatUint(w.k.Uint(), 10)
return nil
}
panic("unexpected map key type")
} For some reason this function checks for the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a comment with the ref you found. |
||
|
||
func (id NodeID) Bytes() []byte { | ||
return id[:] | ||
func (n NodeID) Bytes() []byte { | ||
return []byte(n.buf) | ||
} | ||
|
||
func (id NodeID) MarshalJSON() ([]byte, error) { | ||
return []byte(`"` + id.String() + `"`), nil | ||
func (n NodeID) String() string { | ||
// We assume that the maximum size of a byte slice that | ||
// can be stringified is at least the length of an ID | ||
str, _ := cb58.Encode([]byte(n.buf)) | ||
return NodeIDPrefix + str | ||
} | ||
|
||
func (id NodeID) MarshalText() ([]byte, error) { | ||
return []byte(id.String()), nil | ||
func (n NodeID) MarshalJSON() ([]byte, error) { | ||
return []byte(`"` + n.String() + `"`), nil | ||
} | ||
|
||
func (id *NodeID) UnmarshalJSON(b []byte) error { | ||
func (n *NodeID) UnmarshalJSON(b []byte) error { | ||
str := string(b) | ||
if str == nullStr { // If "null", do nothing | ||
return nil | ||
} else if len(str) <= 2+len(NodeIDPrefix) { | ||
} | ||
if len(str) <= 2+len(NodeIDPrefix) { | ||
return fmt.Errorf("%w: expected to be > %d", errShortNodeID, 2+len(NodeIDPrefix)) | ||
} | ||
|
||
|
@@ -58,35 +67,66 @@ func (id *NodeID) UnmarshalJSON(b []byte) error { | |
} | ||
|
||
var err error | ||
*id, err = NodeIDFromString(str[1:lastIndex]) | ||
*n, err = NodeIDFromString(str[1:lastIndex]) | ||
return err | ||
} | ||
|
||
func (id *NodeID) UnmarshalText(text []byte) error { | ||
return id.UnmarshalJSON(text) | ||
func (n NodeID) MarshalText() ([]byte, error) { | ||
return []byte(n.String()), nil | ||
} | ||
|
||
func (n *NodeID) UnmarshalText(text []byte) error { | ||
return n.UnmarshalJSON(text) | ||
Comment on lines
+74
to
+79
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should explicitly document why these are not symmetric |
||
} | ||
|
||
func (id NodeID) Compare(other NodeID) int { | ||
return bytes.Compare(id[:], other[:]) | ||
func (n NodeID) Compare(other NodeID) int { | ||
return strings.Compare(n.buf, other.buf) | ||
} | ||
|
||
// ToNodeID attempt to convert a byte slice into a node id | ||
func ToNodeID(bytes []byte) (NodeID, error) { | ||
nodeID, err := ToShortID(bytes) | ||
return NodeID(nodeID), err | ||
func ToNodeID(src []byte) (NodeID, error) { | ||
switch { | ||
case len(src) == 0: | ||
return EmptyNodeID, nil | ||
|
||
Comment on lines
+88
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering if we can get away with not supporting this parsing here (treating the zero value of |
||
case len(src) == ShortIDLen || len(src) == NodeIDLen: | ||
return NodeID{ | ||
buf: string(src), | ||
}, nil | ||
|
||
default: | ||
return EmptyNodeID, fmt.Errorf("%w: expected %d or %d bytes but got %d", ErrBadNodeIDLength, ShortNodeIDLen, NodeIDLen, len(src)) | ||
} | ||
} | ||
|
||
func NodeIDFromShortNodeID(nodeID ShortNodeID) NodeID { | ||
if nodeID == EmptyShortNodeID { | ||
return EmptyNodeID | ||
} | ||
return NodeID{ | ||
buf: string(nodeID.Bytes()), | ||
} | ||
} | ||
|
||
func NodeIDFromCert(cert *staking.Certificate) NodeID { | ||
return hashing.ComputeHash160Array( | ||
hashing.ComputeHash256(cert.Raw), | ||
) | ||
return NodeID{ | ||
buf: string(hashing.ComputeHash160( | ||
hashing.ComputeHash256(cert.Raw), | ||
)), | ||
} | ||
} | ||
|
||
// NodeIDFromString is the inverse of NodeID.String() | ||
func NodeIDFromString(nodeIDStr string) (NodeID, error) { | ||
StephenButtolph marked this conversation as resolved.
Show resolved
Hide resolved
|
||
asShort, err := ShortFromPrefixedString(nodeIDStr, NodeIDPrefix) | ||
s, found := strings.CutPrefix(nodeIDStr, NodeIDPrefix) | ||
if !found { | ||
return EmptyNodeID, fmt.Errorf("ID: %s is missing the prefix: %s", nodeIDStr, NodeIDPrefix) | ||
} | ||
|
||
bytes, err := cb58.Decode(s) | ||
if err != nil { | ||
return NodeID{}, err | ||
return EmptyNodeID, err | ||
} | ||
return NodeID(asShort), nil | ||
return NodeID{ | ||
buf: string(bytes), | ||
}, nil | ||
} |
Uh oh!
There was an error while loading. Please reload this page.