Skip to content
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

Improved Lookup Errors #16

Merged
merged 5 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package agent
import (
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"net/url"
"reflect"
Expand All @@ -28,11 +29,7 @@ var icp0, _ = url.Parse("https://icp0.io/")

func effectiveCanisterID(canisterId principal.Principal, args []any) principal.Principal {
// If the canisterId is not aaaaa-aa (encoded as empty byte array), return it.
if len(canisterId.Raw) > 0 {
return canisterId
}

if(len(args) < 1) {
if 0 < len(canisterId.Raw) || len(args) < 1 {
return canisterId
}

Expand Down Expand Up @@ -140,9 +137,8 @@ func (a Agent) Call(canisterID principal.Principal, methodName string, args []an
return err
}
ecID := effectiveCanisterID(canisterID, args)
a.logger.Printf("[AGENT] CALL %s (ecid: %s) %x", canisterID, ecID, a.identity.PublicKey(), methodName)

if _, err := a.call(ecID, data); err != nil {
a.logger.Printf("[AGENT] CALL %s %s (%x)", canisterID, methodName, *requestID)
if _, err := a.call(canisterID, data); err != nil {
return err
}

Expand Down Expand Up @@ -262,7 +258,7 @@ func (a Agent) Query(canisterID principal.Principal, methodName string, args []a

// RequestStatus returns the status of the request with the given ID.
func (a Agent) RequestStatus(ecID principal.Principal, requestID RequestID) ([]byte, hashtree.Node, error) {
a.logger.Printf("[AGENT] REQUEST STATUS %x", requestID[:]);
a.logger.Printf("[AGENT] REQUEST STATUS %s %x", ecID, requestID)
path := []hashtree.Label{hashtree.Label("request_status"), requestID[:]}
c, err := a.readStateCertificate(ecID, [][]hashtree.Label{path})
if err != nil {
Expand All @@ -284,6 +280,11 @@ func (a Agent) RequestStatus(ecID principal.Principal, requestID RequestID) ([]b
return nil, nil, err
}
status, err := hashtree.NewHashTree(node).Lookup(append(path, hashtree.Label("status"))...)
var lookupError hashtree.LookupError
if errors.As(err, &lookupError) && lookupError.Type == hashtree.LookupResultAbsent {
// The status might not be available immediately, since the request is still being processed.
return nil, nil, nil
}
if err != nil {
return nil, nil, err
}
Expand All @@ -309,7 +310,7 @@ func (a Agent) poll(ecID principal.Principal, requestID RequestID, delay, timeou
for {
select {
case <-ticker.C:
a.logger.Printf("[AGENT] POLL (reqID: %x) ", requestID[:])
a.logger.Printf("[AGENT] POLL %s %x", ecID, requestID)
data, node, err := a.RequestStatus(ecID, requestID)
if err != nil {
return nil, err
Expand All @@ -329,7 +330,6 @@ func (a Agent) poll(ecID principal.Principal, requestID RequestID, delay, timeou
}
return nil, fmt.Errorf("(%d) %s", uint64FromBytes(code), string(message))
case "replied":
fmt.Println(node)
replied, err := hashtree.NewHashTree(node).Lookup(append(path, hashtree.Label("reply"))...)
if err != nil {
return nil, fmt.Errorf("no reply found")
Expand Down
2 changes: 1 addition & 1 deletion agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,6 @@ func TestICPLedger_queryBlocks(t *testing.T) {

type testLogger struct{}

func (t testLogger) Printf(format string, v ...interface{}) {
func (t testLogger) Printf(format string, v ...any) {
fmt.Printf("[TEST]"+format+"\n", v...)
}
30 changes: 19 additions & 11 deletions certification/hashtree/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,45 @@ func pathToString(path []Label) string {
return sb.String()
}

// LookupError is an error that occurs during a lookup.
type LookupError struct {
// Type is the type of the lookup result.
Type LookupResultType
Path string
// Path is the path that was looked up.
Path []Label
// Index is the index in the path where the error occurred.
Index int
}

// NewLookupAbsentError returns a new LookupError with type LookupResultAbsent.
func NewLookupAbsentError(path ...Label) LookupError {
func NewLookupAbsentError(path []Label, index int) LookupError {
return LookupError{
Type: LookupResultAbsent,
Path: pathToString(path),
Type: LookupResultAbsent,
Path: path,
Index: index,
}
}

// NewLookupError returns a new LookupError with type LookupResultError.
func NewLookupError(path ...Label) LookupError {
func NewLookupError(path []Label, index int) LookupError {
return LookupError{
Type: LookupResultError,
Path: pathToString(path),
Type: LookupResultError,
Path: path,
Index: index,
}
}

// NewLookupUnknownError returns a new LookupError with type LookupResultUnknown.
func NewLookupUnknownError(path ...Label) LookupError {
func NewLookupUnknownError(path []Label, index int) LookupError {
return LookupError{
Type: LookupResultUnknown,
Path: pathToString(path),
Type: LookupResultUnknown,
Path: path,
Index: index,
}
}

func (l LookupError) Error() string {
return fmt.Sprintf("lookup error (path: %q): %s", l.Path, l.error())
return fmt.Sprintf("lookup error (path: %q) at %q: %s", pathToString(l.Path), l.Path[l.Index], l.error())
}

func (l LookupError) error() string {
Expand Down
4 changes: 2 additions & 2 deletions certification/hashtree/hashtree.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ func (t HashTree) Digest() [32]byte {

// Lookup looks up a path in the hash tree.
func (t HashTree) Lookup(path ...Label) ([]byte, error) {
return lookupPath(t.Root, path...)
return lookupPath(t.Root, path, 0)
}

// LookupSubTree looks up a path in the hash tree and returns the sub-tree.
func (t HashTree) LookupSubTree(path ...Label) (Node, error) {
return lookupSubTree(t.Root, path...)
return lookupSubTree(t.Root, path, 0)
}

// MarshalCBOR marshals a hash tree.
Expand Down
12 changes: 12 additions & 0 deletions certification/hashtree/hashtree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,14 @@ func TestHashTree_Lookup(t *testing.T) {
for _, i := range []int{0, 1} {
if _, err := tree.Lookup(Label(fmt.Sprintf("label %d", i))); !errors.As(err, &lookupError) || lookupError.Type != LookupResultAbsent {
t.Fatalf("unexpected lookup result")
} else if e := lookupError.Error(); e != fmt.Sprintf(`lookup error (path: "label %d") at "label %d": not found, not present in the tree`, i, i) {
t.Fatalf("unexpected error message: %s", e)
}
}
if _, err := tree.Lookup(Label("label 2")); !errors.As(err, &lookupError) || lookupError.Type != LookupResultUnknown {
t.Fatalf("unexpected lookup result")
} else if e := lookupError.Error(); e != `lookup error (path: "label 2") at "label 2": not found, could be pruned` {
t.Fatalf("unexpected error message: %s", e)
}
if v, err := tree.Lookup(Label("label 3")); err != nil {
t.Fatalf("unexpected lookup result")
Expand All @@ -49,6 +53,8 @@ func TestHashTree_Lookup(t *testing.T) {
for _, i := range []int{4, 5, 6} {
if _, err := tree.Lookup(Label(fmt.Sprintf("label %d", i))); !errors.As(err, &lookupError) || lookupError.Type != LookupResultAbsent {
t.Fatalf("unexpected lookup result")
} else if e := lookupError.Error(); e != fmt.Sprintf(`lookup error (path: "label %d") at "label %d": not found, not present in the tree`, i, i) {
t.Fatalf("unexpected error message: %s", e)
}
}
})
Expand Down Expand Up @@ -84,6 +90,8 @@ func TestHashTree_Lookup(t *testing.T) {
for _, i := range []int{0, 1, 2} {
if _, err := tree.Lookup(Label(fmt.Sprintf("label %d", i))); !errors.As(err, &lookupError) || lookupError.Type != LookupResultAbsent {
t.Fatalf("unexpected lookup result")
} else if e := lookupError.Error(); e != fmt.Sprintf(`lookup error (path: "label %d") at "label %d": not found, not present in the tree`, i, i) {
t.Fatalf("unexpected error message: %s", e)
}
}
if v, err := tree.Lookup(Label("label 3")); err != nil {
Expand All @@ -96,10 +104,14 @@ func TestHashTree_Lookup(t *testing.T) {
for _, i := range []int{4, 5} {
if _, err := tree.Lookup(Label(fmt.Sprintf("label %d", i))); !errors.As(err, &lookupError) || lookupError.Type != LookupResultAbsent {
t.Fatalf("unexpected lookup result")
} else if e := lookupError.Error(); e != fmt.Sprintf(`lookup error (path: "label %d") at "label %d": not found, not present in the tree`, i, i) {
t.Fatalf("unexpected error message: %s", e)
}
}
if _, err := tree.Lookup(Label("label 6")); !errors.As(err, &lookupError) || lookupError.Type != LookupResultUnknown {
t.Fatalf("unexpected lookup result")
} else if e := lookupError.Error(); e != `lookup error (path: "label 6") at "label 6": not found, could be pruned` {
t.Fatalf("unexpected error message: %s", e)
}
})
}
Expand Down
30 changes: 15 additions & 15 deletions certification/hashtree/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,44 @@ import (
"bytes"
)

func lookupPath(n Node, path ...Label) ([]byte, error) {
func lookupPath(n Node, path []Label, idx int) ([]byte, error) {
switch {
case len(path) == 0:
case len(path) == 0 || len(path) == idx:
switch n := n.(type) {
case Leaf:
return n, nil
case nil, Empty:
return nil, NewLookupAbsentError()
return nil, NewLookupAbsentError(path, idx-1)
case Pruned:
return nil, NewLookupUnknownError()
return nil, NewLookupUnknownError(path, idx-1)
default:
// Labeled, Fork
return nil, NewLookupError()
return nil, NewLookupError(path, idx-1)
}
default:
switch l := lookupLabel(n, path[0]); l.Type {
switch l := lookupLabel(n, path[idx]); l.Type {
case lookupLabelResultFound:
return lookupPath(l.Node, path[1:]...)
return lookupPath(l.Node, path, idx+1)
case lookupLabelResultUnknown:
return nil, NewLookupUnknownError(path...)
return nil, NewLookupUnknownError(path, idx)
default:
return nil, NewLookupAbsentError(path...)
return nil, NewLookupAbsentError(path, idx)
}
}
}

func lookupSubTree(n Node, path ...Label) (Node, error) {
func lookupSubTree(n Node, path []Label, idx int) (Node, error) {
switch {
case len(path) == 0:
case len(path) == 0 || len(path) == idx:
return n, nil
default:
switch l := lookupLabel(n, path[0]); l.Type {
switch l := lookupLabel(n, path[idx]); l.Type {
case lookupLabelResultFound:
return lookupSubTree(l.Node, path[1:]...)
return lookupSubTree(l.Node, path, idx+1)
case lookupLabelResultUnknown:
return nil, NewLookupUnknownError(path...)
return nil, NewLookupUnknownError(path, idx)
default:
return nil, NewLookupAbsentError(path...)
return nil, NewLookupAbsentError(path, idx)
}
}
}
Expand Down
44 changes: 44 additions & 0 deletions certification/hashtree/lookup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package hashtree_test

import (
"bytes"
"errors"
"github.com/aviate-labs/agent-go/certification/hashtree"
"testing"
)

func TestHashTree_Lookup_absent(t *testing.T) {
tree := hashtree.NewHashTree(hashtree.Labeled{
Label: hashtree.Label("a"),
Tree: hashtree.Labeled{
Label: hashtree.Label("b"),
Tree: hashtree.Labeled{
Label: hashtree.Label("c"),
Tree: hashtree.Fork{
LeftTree: hashtree.Labeled{
Label: hashtree.Label("d0"),
},
RightTree: hashtree.Labeled{
Label: hashtree.Label("d1"),
Tree: hashtree.Leaf("d"),
},
},
},
},
})
var lookupError hashtree.LookupError
if _, err := tree.Lookup(hashtree.Label("a"), hashtree.Label("b"), hashtree.Label("c0"), hashtree.Label("d0")); !errors.As(err, &lookupError) || lookupError.Type != hashtree.LookupResultAbsent {
t.Fatalf("unexpected lookup result")
}
if _, err := tree.Lookup(hashtree.Label("a"), hashtree.Label("b"), hashtree.Label("c"), hashtree.Label("d0")); !errors.As(err, &lookupError) || lookupError.Type != hashtree.LookupResultAbsent {
t.Fatalf("unexpected lookup result")
}

v, err := tree.Lookup(hashtree.Label("a"), hashtree.Label("b"), hashtree.Label("c"), hashtree.Label("d1"))
if err != nil {
t.Fatalf("unexpected lookup result")
}
if !bytes.Equal(v, hashtree.Label("d")) {
t.Fatalf("unexpected node value")
}
}
4 changes: 4 additions & 0 deletions ic/dfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
"assetstorage": {
"type": "motoko",
"main": "assetstorage/actor.mo"
},
"ic0": {
"type": "motoko",
"main": "ic/actor.mo"
}
}
}
Loading
Loading