Skip to content

Commit

Permalink
Add registry client.
Browse files Browse the repository at this point in the history
  • Loading branch information
q-uint committed May 17, 2024
1 parent 6602f73 commit f79dc69
Show file tree
Hide file tree
Showing 15 changed files with 2,959 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/di-wu/parser v0.3.0
github.com/fxamacker/cbor/v2 v2.6.0
github.com/herumi/bls-go-binary v1.33.0
google.golang.org/protobuf v1.34.1
)

require github.com/x448/float16 v0.8.4 // indirect
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ github.com/di-wu/parser v0.3.0 h1:NMOvy5ifswgt4gsdhySVcKOQtvjC43cHZIfViWctqQY=
github.com/di-wu/parser v0.3.0/go.mod h1:SLp58pW6WamdmznrVRrw2NTyn4wAvT9rrEFynKX7nYo=
github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA=
github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/herumi/bls-go-binary v1.33.0 h1:OJwWkXTsxF7SLHT8cBLJfb6i97KHfrG4DkgejLcDm78=
github.com/herumi/bls-go-binary v1.33.0/go.mod h1:O4Vp1AfR4raRGwFeQpr9X/PQtncEicMoOe6BQt1oX0Y=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
3 changes: 3 additions & 0 deletions registry/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Registry Client in Go

Based on the [Rust Registry Client](https://github.com/dfinity/ic/tree/master/rs/registry)
116 changes: 116 additions & 0 deletions registry/dataprovider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package registry

import (
"fmt"
"sort"
"sync"
"time"

"github.com/aviate-labs/agent-go"
"github.com/aviate-labs/agent-go/ic"
"github.com/aviate-labs/agent-go/identity"
"github.com/aviate-labs/agent-go/principal"
"github.com/aviate-labs/agent-go/registry/proto/v1"
"github.com/fxamacker/cbor/v2"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/wrapperspb"
)

type DataProvider struct {
sync.RWMutex
Records []v1.ProtoRegistryRecord
}

func (d *DataProvider) Add(
key string,
version uint64,
value []byte,
) error {
if version < 1 {
return fmt.Errorf("version must be greater than 0")
}

d.Lock()
defer d.Unlock()

var ok bool
idx := sort.Search(len(d.Records), func(i int) bool {
if d.Records[i].Key == key {
if d.Records[i].Version == version {
ok = true // Record already exists.
}
return d.Records[i].Version >= version
}
return d.Records[i].Key >= key
})
if ok {
// Key and version already exist.
return fmt.Errorf("record already exists: %s@%d", key, version)
}
d.Records = append(d.Records, v1.ProtoRegistryRecord{})
copy(d.Records[idx+1:], d.Records[idx:]) // Shift right.
d.Records[idx] = v1.ProtoRegistryRecord{
Key: key,
Version: version,
Value: wrapperspb.Bytes(value),
}
return nil
}

func (d *DataProvider) GetChangesSince(version uint64) ([]*v1.RegistryDelta, uint64, error) {
a, err := agent.New(agent.DefaultConfig)
if err != nil {
return nil, 0, err
}
payload, err := proto.Marshal(&v1.RegistryGetChangesSinceRequest{
Version: version,
})
if err != nil {
return nil, 0, err
}
request := agent.Request{
Type: agent.RequestTypeQuery,
Sender: principal.AnonymousID,
IngressExpiry: uint64(time.Now().Add(5 * time.Minute).UnixNano()),
CanisterID: ic.REGISTRY_PRINCIPAL,
MethodName: "get_changes_since",
Arguments: payload,
}
requestID := agent.NewRequestID(request)
id := new(identity.AnonymousIdentity)
data, err := cbor.Marshal(agent.Envelope{
Content: request,
SenderPubKey: id.PublicKey(),
SenderSig: requestID.Sign(id),
})
if err != nil {
return nil, 0, err
}
resp, err := a.Client().Query(ic.REGISTRY_PRINCIPAL, data)
if err != nil {
return nil, 0, err
}
var response agent.Response
if err := cbor.Unmarshal(resp, &response); err != nil {
return nil, 0, err
}
if response.Status != "replied" {
return nil, 0, fmt.Errorf("status: %s", response.Status)
}

changesResponse := new(v1.RegistryGetChangesSinceResponse)
if err := proto.Unmarshal(response.Reply["arg"], changesResponse); err != nil {
return nil, 0, err
}
if changesResponse.Error != nil {
return nil, 0, fmt.Errorf("error: %s", changesResponse.Error.String())
}
return changesResponse.Deltas, changesResponse.Version, nil
}

func (d *DataProvider) IsEmpty() bool {
d.RLock()
defer d.RUnlock()

return len(d.Records) == 0
}
11 changes: 11 additions & 0 deletions registry/dataprovider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package registry

import (
"testing"
)

func TestDataProvider_GetChangesSince(t *testing.T) {
if _, _, err := new(DataProvider).GetChangesSince(0); err != nil {
t.Fatal(err)
}
}
83 changes: 83 additions & 0 deletions registry/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package registry

import "fmt"

type ClientDataProviderQueryFailedError struct {
Source DataProviderError
}

func (e ClientDataProviderQueryFailedError) Error() string {
return fmt.Sprintf("failed to query data provider: %s", e.Source)
}

func (ClientDataProviderQueryFailedError) registryClientError() {}

type ClientDecodeError struct {
Err string
}

func (e ClientDecodeError) Error() string {
return fmt.Sprintf("failed to decode registry contents: %s", e.Err)
}

func (ClientDecodeError) registryClientError() {}

type ClientError interface {
registryClientError()
error
}

type ClientPollLockFailedError struct {
Err string
}

func (e ClientPollLockFailedError) Error() string {
return fmt.Sprintf("failed to acquire poll lock: %s", e.Err)
}

func (ClientPollLockFailedError) registryClientError() {}

type ClientPollingLatestVersionFailedError struct {
Retries uint
}

func (e ClientPollingLatestVersionFailedError) Error() string {
return fmt.Sprintf("failed to report the same version twice after %d times", e.Retries)
}

func (ClientPollingLatestVersionFailedError) registryClientError() {}

type ClientVersionNotAvailableError struct {
Version Version
}

func (e ClientVersionNotAvailableError) Error() string {
return fmt.Sprintf("the requested version is not available locally: %d", e.Version)
}

func (ClientVersionNotAvailableError) registryClientError() {}

type DataProviderError interface {
dataProviderError()
error
}

// DataProviderTimeoutError occurs when the registry transport client times out.
type DataProviderTimeoutError struct{}

func (DataProviderTimeoutError) Error() string {
return "registry transport client timed out"
}

func (DataProviderTimeoutError) dataProviderError() {}

// DataProviderTransferError occurs when using registry transfer.
type DataProviderTransferError struct {
Source string
}

func (e DataProviderTransferError) Error() string {
return fmt.Sprintf("registry transport client failed to fetch registry update from registry canister: %s", e.Source)
}

func (DataProviderTransferError) dataProviderError() {}
20 changes: 20 additions & 0 deletions registry/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
github.com/aviate-labs/agent-go v0.4.3 h1:XvUv23dw/77I5Rt6S89fHOuI9ci9ARiyLw8Lh4C2FTU=
github.com/aviate-labs/agent-go v0.4.3/go.mod h1:uodw1LlLrP5A5XxXaNOE39iHYb6IGDqnVXEvwiG+YiM=
github.com/aviate-labs/agent-go v0.4.4-0.20240515111542-ef8ce13f44f2 h1:4WA9qGa4z22TSJ6LApbRXtb/ZpeLkxgaKBOvu72srno=
github.com/aviate-labs/agent-go v0.4.4-0.20240515111542-ef8ce13f44f2/go.mod h1:uodw1LlLrP5A5XxXaNOE39iHYb6IGDqnVXEvwiG+YiM=
github.com/aviate-labs/leb128 v0.3.0 h1:s9htRv3OYk8nuHqJu9PiVFJxv1jIUTIcpEeiURa91uQ=
github.com/aviate-labs/leb128 v0.3.0/go.mod h1:GclhBOjhIKmcDlgHKhj0AEZollzERfZUbcRUKiQVqgY=
github.com/aviate-labs/secp256k1 v0.0.0-5e6736a h1:aQkG/D+l8Y7tr809l8pN+KebH2jzacWReSFQmeEKFgM=
github.com/aviate-labs/secp256k1 v0.0.0-5e6736a/go.mod h1:C/lr3F9TimrVkdZckG5mz+VU0TrmpeyVKUjzv2YyGwA=
github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA=
github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/herumi/bls-go-binary v1.33.0 h1:OJwWkXTsxF7SLHT8cBLJfb6i97KHfrG4DkgejLcDm78=
github.com/herumi/bls-go-binary v1.33.0/go.mod h1:O4Vp1AfR4raRGwFeQpr9X/PQtncEicMoOe6BQt1oX0Y=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
53 changes: 53 additions & 0 deletions registry/interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package registry

import "time"

func GetValue(c RegistryClient, key string, version Version) (*[]byte, error) {
vv, err := c.GetVersionedValue(key, version)
if err != nil {
return nil, err
}
return vv.Value, nil
}

type RegistryClient interface {
GetVersionedValue(key string, version Version) (VersionedRecord[[]byte], error)
GetKeyFamily(keyPrefix string, version Version) ([]string, error)
GetLatestVersion() (Version, error)
GetVersionTimestamp(version Version) (*int64, error)
}

type RegistryDataProvider interface {
GetUpdatesSince(version Version) ([]TransportRecord, error)
}

type TransportRecord = VersionedRecord[[]byte]

func EmptyZeroRecord(key string) TransportRecord {
return TransportRecord{
Key: key,
Version: ZeroRegistryVersion,
Value: nil,
}
}

type Version uint64

// Reference: https://github.com/dfinity/ic/blob/master/rs/interfaces/registry/src/lib.rs

const (
// ZeroRegistryVersion is the version number of the empty registry.
ZeroRegistryVersion Version = 0
// PollingPeriod is the period at which the local store is polled for updates.
PollingPeriod = 5 * time.Second
)

// VersionedRecord is a key-value pair with a version.
type VersionedRecord[T any] struct {
// Key of the record.
Key string
// Version at which this record was created.
Version Version
// Value of the record. If the record was deleted in this version, this field is nil.
Value *T
}
6 changes: 6 additions & 0 deletions registry/proto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package registry

//go:generate go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
//go:generate protoc -I=testdata --go_out=. testdata/registry.proto
//go:generate protoc -I=testdata --go_out=. testdata/local.proto
//go:generate protoc -I=testdata --go_out=. testdata/transport.proto
Loading

0 comments on commit f79dc69

Please sign in to comment.