From 0bfb47894f83a9025558462e1250f16ea9dfa0fe Mon Sep 17 00:00:00 2001 From: Quint Daenen Date: Mon, 28 Feb 2022 18:38:08 +0100 Subject: [PATCH] Add call endpoint. --- agent.go | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++-- dfx_test.go | 32 +++++++++++++------- go.mod | 2 +- go.sum | 4 +-- responses.go | 2 +- 5 files changed, 105 insertions(+), 18 deletions(-) diff --git a/agent.go b/agent.go index 6282a47..eb01366 100644 --- a/agent.go +++ b/agent.go @@ -1,6 +1,7 @@ package agent import ( + "encoding/binary" "fmt" "net/url" "time" @@ -42,6 +43,32 @@ func New(cfg AgentConfig) Agent { } } +func (a Agent) Call(canisterID principal.Principal, methodName string, args []byte) (string, error) { + types, values, err := a.CallCandid(canisterID, methodName, args) + if err != nil { + return "", err + } + return candid.DecodeValues(types, values) +} + +func (a Agent) CallCandid(canisterID principal.Principal, methodName string, args []byte) ([]idl.Type, []interface{}, error) { + requestID, data, err := a.sign(Request{ + Type: RequestTypeCall, + Sender: a.Sender(), + CanisterID: canisterID, + MethodName: methodName, + Arguments: args, + IngressExpiry: a.expiryDate(), + }) + if err != nil { + return nil, nil, err + } + if _, err := a.call(canisterID, data); err != nil { + return nil, nil, err + } + return a.poll(canisterID, *requestID, time.Second, time.Second*10) +} + func (a Agent) GetCanisterControllers(canisterID principal.Principal) ([]principal.Principal, error) { resp, err := a.GetCanisterInfo(canisterID, "controllers") if err != nil { @@ -109,21 +136,71 @@ func (a Agent) QueryCandid(canisterID principal.Principal, methodName string, ar } } +func (agent *Agent) RequestStatus(canisterID principal.Principal, requestID RequestID) ([]byte, cert.Node, error) { + path := [][]byte{[]byte("request_status"), requestID[:]} + c, err := agent.readStateCertificate(canisterID, [][][]byte{path}) + if err != nil { + return nil, nil, err + } + var state map[string]interface{} + if err := cbor.Unmarshal(c, &state); err != nil { + return nil, nil, err + } + node, err := cert.DeserializeNode(state["tree"].([]interface{})) + if err != nil { + return nil, nil, err + } + return cert.Lookup(append(path, []byte("status")), node), node, nil +} + func (a Agent) Sender() principal.Principal { return a.identity.Sender() } +func (agent *Agent) call(canisterID principal.Principal, data []byte) ([]byte, error) { + return agent.client.call(canisterID, data) +} + func (a Agent) expiryDate() uint64 { return uint64(time.Now().Add(a.ingressExpiry).UnixNano()) } -func (a Agent) query(canisterID principal.Principal, data []byte) (*QueryResponse, error) { +func (a Agent) poll(canisterID principal.Principal, requestID RequestID, delay, timeout time.Duration) ([]idl.Type, []interface{}, error) { + ticker := time.NewTicker(delay) + timer := time.NewTimer(timeout) + for { + select { + case <-ticker.C: + data, node, err := a.RequestStatus(canisterID, requestID) + if err != nil { + return nil, nil, err + } + if len(data) != 0 { + path := [][]byte{[]byte("request_status"), requestID[:]} + switch string(data) { + case "rejected": + code := cert.Lookup(append(path, []byte("reject_code")), node) + reject_message := cert.Lookup(append(path, []byte("reject_message")), node) + return nil, nil, fmt.Errorf("(%d) %s", binary.BigEndian.Uint64(code), string(reject_message)) + case "replied": + path := [][]byte{[]byte("request_status"), requestID[:]} + reply := cert.Lookup(append(path, []byte("reply")), node) + return idl.Decode(reply) + } + } + case <-timer.C: + return nil, nil, fmt.Errorf("out of time... waited %d seconds", timeout/time.Second) + } + } +} + +func (a Agent) query(canisterID principal.Principal, data []byte) (*Response, error) { resp, err := a.client.query(canisterID, data) if err != nil { return nil, err } - queryReponse := new(QueryResponse) - return queryReponse, cbor.Unmarshal(resp, &queryReponse) + queryReponse := new(Response) + return queryReponse, cbor.Unmarshal(resp, queryReponse) } func (a Agent) readState(canisterID principal.Principal, data []byte) (map[string][]byte, error) { diff --git a/dfx_test.go b/dfx_test.go index 254d9ff..8311da3 100644 --- a/dfx_test.go +++ b/dfx_test.go @@ -16,7 +16,7 @@ import ( func TestLocalReplica(t *testing.T) { if _, err := exec.LookPath("dfx"); err != nil { - t.Skip() + t.Skip("DFX not installed.") return } @@ -35,16 +35,6 @@ func TestLocalReplica(t *testing.T) { Host: ic0, }, }) - { - args, _ := candid.EncodeValue("()") - resp, err := agent.Query(canister, "get", args) - if err != nil { - t.Fatal(err) - } - if resp != "(0 : nat)" { - t.Error(resp) - } - } { ps, err := agent.GetCanisterControllers(canister) if err != nil { @@ -66,6 +56,26 @@ func TestLocalReplica(t *testing.T) { t.Error(h) } } + { + args, _ := candid.EncodeValue("()") + resp, err := agent.Query(canister, "get", args) + if err != nil { + t.Fatal(err) + } + if resp != "(0 : nat)" { + t.Error(resp) + } + } + { + args, _ := candid.EncodeValue("( 1 : nat )") + resp, err := agent.Call(canister, "add", args) + if err != nil { + t.Fatal(err) + } + if resp != "(1 : nat)" { + t.Error(resp) + } + } } func startDFX(t *testing.T) *exec.Cmd { diff --git a/go.mod b/go.mod index 55e7849..ba5f311 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.17 require ( github.com/aviate-labs/candid-go v0.0.0-20220207081439-95cecca37dcd - github.com/aviate-labs/certificate-go v0.0.2-0.20220227153822-3ce38b25fec0 + github.com/aviate-labs/certificate-go v0.0.2 github.com/aviate-labs/leb128 v0.3.0 github.com/aviate-labs/principal-go v0.2.0 github.com/fxamacker/cbor/v2 v2.4.0 diff --git a/go.sum b/go.sum index 60a10ae..33a952f 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/aviate-labs/candid-go v0.0.0-20220207081439-95cecca37dcd h1:7dyRw30pv6ob7Mldv5jHshl/G95TNTfGSO6ka0cFMtM= github.com/aviate-labs/candid-go v0.0.0-20220207081439-95cecca37dcd/go.mod h1:aSwDlnrwlxvHK7v/LfyAI5dCR4CHf5vHfwn/t9xQzgY= -github.com/aviate-labs/certificate-go v0.0.2-0.20220227153822-3ce38b25fec0 h1:eoCN/x6FLtgSc9RoYdXcobgnJYJR0cdQADfxAVIfMAk= -github.com/aviate-labs/certificate-go v0.0.2-0.20220227153822-3ce38b25fec0/go.mod h1:O4CwO5bBe+AFpMLGEz7ZgrMms6V3fJ69d3jb+fpe31g= +github.com/aviate-labs/certificate-go v0.0.2 h1:eohRSsYdANL9n/nykibVO9X0NfyAGl3e5wBcZXBYSqo= +github.com/aviate-labs/certificate-go v0.0.2/go.mod h1:O4CwO5bBe+AFpMLGEz7ZgrMms6V3fJ69d3jb+fpe31g= 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/principal-go v0.2.0 h1:Xp5V9L90dCmOLsUjTwL96Yhrv4czOSyzMV8fzJunmuM= diff --git a/responses.go b/responses.go index 7f880f6..433be69 100644 --- a/responses.go +++ b/responses.go @@ -1,6 +1,6 @@ package agent -type QueryResponse struct { +type Response struct { Status string `cbor:"status"` Reply map[string][]byte `cbor:"reply"` RejectCode uint64 `cbor:"reject_code"`