From 0a688e688c97b1524a557cc32cd8a88dc22ab33f Mon Sep 17 00:00:00 2001 From: deelawn Date: Sun, 17 Dec 2023 10:13:01 +0100 Subject: [PATCH 01/48] agent wip --- agent/devmock/avl/node.go | 463 +++++++++++++++++++++ agent/devmock/avl/tree.go | 82 ++++ agent/devmock/go.mod | 1 + agent/devmock/std/std.go | 7 + agent/p/agent/pricefeed/task/definition.go | 6 + agent/p/agent/random/task/aggregator.go | 31 ++ agent/p/agent/random/task/definition.go | 26 ++ agent/p/agent/task/agent.go | 55 +++ agent/p/agent/task/aggregator.go | 8 + agent/p/agent/task/definition.go | 5 + agent/p/agent/task/history.go | 16 + agent/p/agent/task/instance.go | 42 ++ agent/p/agent/task/registry.go | 28 ++ agent/p/agent/task/result.go | 8 + agent/p/agent/task/type.go | 7 + go.mod | 5 + 16 files changed, 790 insertions(+) create mode 100644 agent/devmock/avl/node.go create mode 100644 agent/devmock/avl/tree.go create mode 100644 agent/devmock/go.mod create mode 100644 agent/devmock/std/std.go create mode 100644 agent/p/agent/pricefeed/task/definition.go create mode 100644 agent/p/agent/random/task/aggregator.go create mode 100644 agent/p/agent/random/task/definition.go create mode 100644 agent/p/agent/task/agent.go create mode 100644 agent/p/agent/task/aggregator.go create mode 100644 agent/p/agent/task/definition.go create mode 100644 agent/p/agent/task/history.go create mode 100644 agent/p/agent/task/instance.go create mode 100644 agent/p/agent/task/registry.go create mode 100644 agent/p/agent/task/result.go create mode 100644 agent/p/agent/task/type.go diff --git a/agent/devmock/avl/node.go b/agent/devmock/avl/node.go new file mode 100644 index 00000000000..a49dba00ae1 --- /dev/null +++ b/agent/devmock/avl/node.go @@ -0,0 +1,463 @@ +package avl + +//---------------------------------------- +// Node + +type Node struct { + key string + value interface{} + height int8 + size int + leftNode *Node + rightNode *Node +} + +func NewNode(key string, value interface{}) *Node { + return &Node{ + key: key, + value: value, + height: 0, + size: 1, + } +} + +func (node *Node) Size() int { + if node == nil { + return 0 + } + return node.size +} + +func (node *Node) IsLeaf() bool { + return node.height == 0 +} + +func (node *Node) Key() string { + return node.key +} + +func (node *Node) Value() interface{} { + return node.value +} + +func (node *Node) _copy() *Node { + if node.height == 0 { + panic("Why are you copying a value node?") + } + return &Node{ + key: node.key, + height: node.height, + size: node.size, + leftNode: node.leftNode, + rightNode: node.rightNode, + } +} + +func (node *Node) Has(key string) (has bool) { + if node == nil { + return false + } + if node.key == key { + return true + } + if node.height == 0 { + return false + } else { + if key < node.key { + return node.getLeftNode().Has(key) + } else { + return node.getRightNode().Has(key) + } + } +} + +func (node *Node) Get(key string) (index int, value interface{}, exists bool) { + if node == nil { + return 0, nil, false + } + if node.height == 0 { + if node.key == key { + return 0, node.value, true + } else if node.key < key { + return 1, nil, false + } else { + return 0, nil, false + } + } else { + if key < node.key { + return node.getLeftNode().Get(key) + } else { + rightNode := node.getRightNode() + index, value, exists = rightNode.Get(key) + index += node.size - rightNode.size + return index, value, exists + } + } +} + +func (node *Node) GetByIndex(index int) (key string, value interface{}) { + if node.height == 0 { + if index == 0 { + return node.key, node.value + } else { + panic("GetByIndex asked for invalid index") + return "", nil + } + } else { + // TODO: could improve this by storing the sizes + leftNode := node.getLeftNode() + if index < leftNode.size { + return leftNode.GetByIndex(index) + } else { + return node.getRightNode().GetByIndex(index - leftNode.size) + } + } +} + +// XXX consider a better way to do this... perhaps split Node from Node. +func (node *Node) Set(key string, value interface{}) (newSelf *Node, updated bool) { + if node == nil { + return NewNode(key, value), false + } + if node.height == 0 { + if key < node.key { + return &Node{ + key: node.key, + height: 1, + size: 2, + leftNode: NewNode(key, value), + rightNode: node, + }, false + } else if key == node.key { + return NewNode(key, value), true + } else { + return &Node{ + key: key, + height: 1, + size: 2, + leftNode: node, + rightNode: NewNode(key, value), + }, false + } + } else { + node = node._copy() + if key < node.key { + node.leftNode, updated = node.getLeftNode().Set(key, value) + } else { + node.rightNode, updated = node.getRightNode().Set(key, value) + } + if updated { + return node, updated + } else { + node.calcHeightAndSize() + return node.balance(), updated + } + } +} + +// newNode: The new node to replace node after remove. +// newKey: new leftmost leaf key for node after successfully removing 'key' if changed. +// value: removed value. +func (node *Node) Remove(key string) ( + newNode *Node, newKey string, value interface{}, removed bool, +) { + if node == nil { + return nil, "", nil, false + } + if node.height == 0 { + if key == node.key { + return nil, "", node.value, true + } else { + return node, "", nil, false + } + } else { + if key < node.key { + var newLeftNode *Node + newLeftNode, newKey, value, removed = node.getLeftNode().Remove(key) + if !removed { + return node, "", value, false + } else if newLeftNode == nil { // left node held value, was removed + return node.rightNode, node.key, value, true + } + node = node._copy() + node.leftNode = newLeftNode + node.calcHeightAndSize() + node = node.balance() + return node, newKey, value, true + } else { + var newRightNode *Node + newRightNode, newKey, value, removed = node.getRightNode().Remove(key) + if !removed { + return node, "", value, false + } else if newRightNode == nil { // right node held value, was removed + return node.leftNode, "", value, true + } + node = node._copy() + node.rightNode = newRightNode + if newKey != "" { + node.key = newKey + } + node.calcHeightAndSize() + node = node.balance() + return node, "", value, true + } + } +} + +func (node *Node) getLeftNode() *Node { + return node.leftNode +} + +func (node *Node) getRightNode() *Node { + return node.rightNode +} + +// NOTE: overwrites node +// TODO: optimize balance & rotate +func (node *Node) rotateRight() *Node { + node = node._copy() + l := node.getLeftNode() + _l := l._copy() + + _lrCached := _l.rightNode + _l.rightNode = node + node.leftNode = _lrCached + + node.calcHeightAndSize() + _l.calcHeightAndSize() + + return _l +} + +// NOTE: overwrites node +// TODO: optimize balance & rotate +func (node *Node) rotateLeft() *Node { + node = node._copy() + r := node.getRightNode() + _r := r._copy() + + _rlCached := _r.leftNode + _r.leftNode = node + node.rightNode = _rlCached + + node.calcHeightAndSize() + _r.calcHeightAndSize() + + return _r +} + +// NOTE: mutates height and size +func (node *Node) calcHeightAndSize() { + node.height = maxInt8(node.getLeftNode().height, node.getRightNode().height) + 1 + node.size = node.getLeftNode().size + node.getRightNode().size +} + +func (node *Node) calcBalance() int { + return int(node.getLeftNode().height) - int(node.getRightNode().height) +} + +// NOTE: assumes that node can be modified +// TODO: optimize balance & rotate +func (node *Node) balance() (newSelf *Node) { + balance := node.calcBalance() + if balance > 1 { + if node.getLeftNode().calcBalance() >= 0 { + // Left Left Case + return node.rotateRight() + } else { + // Left Right Case + // node = node._copy() + left := node.getLeftNode() + node.leftNode = left.rotateLeft() + // node.calcHeightAndSize() + return node.rotateRight() + } + } + if balance < -1 { + if node.getRightNode().calcBalance() <= 0 { + // Right Right Case + return node.rotateLeft() + } else { + // Right Left Case + // node = node._copy() + right := node.getRightNode() + node.rightNode = right.rotateRight() + // node.calcHeightAndSize() + return node.rotateLeft() + } + } + // Nothing changed + return node +} + +// Shortcut for TraverseInRange. +func (node *Node) Iterate(start, end string, cb func(*Node) bool) bool { + return node.TraverseInRange(start, end, true, true, cb) +} + +// Shortcut for TraverseInRange. +func (node *Node) ReverseIterate(start, end string, cb func(*Node) bool) bool { + return node.TraverseInRange(start, end, false, true, cb) +} + +// TraverseInRange traverses all nodes, including inner nodes. +// Start is inclusive and end is exclusive when ascending, +// Start and end are inclusive when descending. +// Empty start and empty end denote no start and no end. +// If leavesOnly is true, only visit leaf nodes. +// NOTE: To simulate an exclusive reverse traversal, +// just append 0x00 to start. +func (node *Node) TraverseInRange(start, end string, ascending bool, leavesOnly bool, cb func(*Node) bool) bool { + if node == nil { + return false + } + afterStart := (start == "" || start < node.key) + startOrAfter := (start == "" || start <= node.key) + beforeEnd := false + if ascending { + beforeEnd = (end == "" || node.key < end) + } else { + beforeEnd = (end == "" || node.key <= end) + } + + // Run callback per inner/leaf node. + stop := false + if (!node.IsLeaf() && !leavesOnly) || + (node.IsLeaf() && startOrAfter && beforeEnd) { + stop = cb(node) + if stop { + return stop + } + } + if node.IsLeaf() { + return stop + } + + if ascending { + // check lower nodes, then higher + if afterStart { + stop = node.getLeftNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + if stop { + return stop + } + if beforeEnd { + stop = node.getRightNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + } else { + // check the higher nodes first + if beforeEnd { + stop = node.getRightNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + if stop { + return stop + } + if afterStart { + stop = node.getLeftNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + } + + return stop +} + +// TraverseByOffset traverses all nodes, including inner nodes. +// A limit of math.MaxInt means no limit. +func (node *Node) TraverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool { + if node == nil { + return false + } + + // fast paths. these happen only if TraverseByOffset is called directly on a leaf. + if limit <= 0 || offset >= node.size { + return false + } + if node.IsLeaf() { + if offset > 0 { + return false + } + return cb(node) + } + + // go to the actual recursive function. + return node.traverseByOffset(offset, limit, descending, leavesOnly, cb) +} + +func (node *Node) traverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool { + // caller guarantees: offset < node.size; limit > 0. + + if !leavesOnly { + if cb(node) { + return true + } + } + first, second := node.getLeftNode(), node.getRightNode() + if descending { + first, second = second, first + } + if first.IsLeaf() { + // either run or skip, based on offset + if offset > 0 { + offset-- + } else { + cb(first) + limit-- + if limit <= 0 { + return false + } + } + } else { + // possible cases: + // 1 the offset given skips the first node entirely + // 2 the offset skips none or part of the first node, but the limit requires some of the second node. + // 3 the offset skips none or part of the first node, and the limit stops our search on the first node. + if offset >= first.size { + offset -= first.size // 1 + } else { + if first.traverseByOffset(offset, limit, descending, leavesOnly, cb) { + return true + } + // number of leaves which could actually be called from inside + delta := first.size - offset + offset = 0 + if delta >= limit { + return true // 3 + } + limit -= delta // 2 + } + } + + // because of the caller guarantees and the way we handle the first node, + // at this point we know that limit > 0 and there must be some values in + // this second node that we include. + + // => if the second node is a leaf, it has to be included. + if second.IsLeaf() { + return cb(second) + } + // => if it is not a leaf, it will still be enough to recursively call this + // function with the updated offset and limit + return second.traverseByOffset(offset, limit, descending, leavesOnly, cb) +} + +// Only used in testing... +func (node *Node) lmd() *Node { + if node.height == 0 { + return node + } + return node.getLeftNode().lmd() +} + +// Only used in testing... +func (node *Node) rmd() *Node { + if node.height == 0 { + return node + } + return node.getRightNode().rmd() +} + +func maxInt8(a, b int8) int8 { + if a > b { + return a + } + return b +} diff --git a/agent/devmock/avl/tree.go b/agent/devmock/avl/tree.go new file mode 100644 index 00000000000..7b33d28fbe3 --- /dev/null +++ b/agent/devmock/avl/tree.go @@ -0,0 +1,82 @@ +package avl + +type IterCbFn func(key string, value interface{}) bool + +//---------------------------------------- +// Tree + +// The zero struct can be used as an empty tree. +type Tree struct { + node *Node +} + +func NewTree() *Tree { + return &Tree{ + node: nil, + } +} + +func (tree *Tree) Size() int { + return tree.node.Size() +} + +func (tree *Tree) Has(key string) (has bool) { + return tree.node.Has(key) +} + +func (tree *Tree) Get(key string) (value interface{}, exists bool) { + _, value, exists = tree.node.Get(key) + return +} + +func (tree *Tree) GetByIndex(index int) (key string, value interface{}) { + return tree.node.GetByIndex(index) +} + +func (tree *Tree) Set(key string, value interface{}) (updated bool) { + newnode, updated := tree.node.Set(key, value) + tree.node = newnode + return updated +} + +func (tree *Tree) Remove(key string) (value interface{}, removed bool) { + newnode, _, value, removed := tree.node.Remove(key) + tree.node = newnode + return value, removed +} + +// Shortcut for TraverseInRange. +func (tree *Tree) Iterate(start, end string, cb IterCbFn) bool { + return tree.node.TraverseInRange(start, end, true, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} + +// Shortcut for TraverseInRange. +func (tree *Tree) ReverseIterate(start, end string, cb IterCbFn) bool { + return tree.node.TraverseInRange(start, end, false, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} + +// Shortcut for TraverseByOffset. +func (tree *Tree) IterateByOffset(offset int, count int, cb IterCbFn) bool { + return tree.node.TraverseByOffset(offset, count, true, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} + +// Shortcut for TraverseByOffset. +func (tree *Tree) ReverseIterateByOffset(offset int, count int, cb IterCbFn) bool { + return tree.node.TraverseByOffset(offset, count, false, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} diff --git a/agent/devmock/go.mod b/agent/devmock/go.mod new file mode 100644 index 00000000000..6c7ce04c7da --- /dev/null +++ b/agent/devmock/go.mod @@ -0,0 +1 @@ +package devmock \ No newline at end of file diff --git a/agent/devmock/std/std.go b/agent/devmock/std/std.go new file mode 100644 index 00000000000..06cdd59952f --- /dev/null +++ b/agent/devmock/std/std.go @@ -0,0 +1,7 @@ +package std + +type Address string + +func GetOrigCaller() Address { + return "" +} diff --git a/agent/p/agent/pricefeed/task/definition.go b/agent/p/agent/pricefeed/task/definition.go new file mode 100644 index 00000000000..8aef6ee02a5 --- /dev/null +++ b/agent/p/agent/pricefeed/task/definition.go @@ -0,0 +1,6 @@ +package task + +type Definition struct { + Pair string // USD/CAD + AcceptedDeviation float64 +} diff --git a/agent/p/agent/random/task/aggregator.go b/agent/p/agent/random/task/aggregator.go new file mode 100644 index 00000000000..111f0e14a52 --- /dev/null +++ b/agent/p/agent/random/task/aggregator.go @@ -0,0 +1,31 @@ +package task + +import ( + "math" + "strconv" +) + +type Aggregator struct { + sum uint64 + taskDefinition *Definition +} + +func (a *Aggregator) Aggregate() string { + randomValue := a.taskDefinition.RangeStart + a.sum%a.taskDefinition.RangeEnd + a.sum = 0 + return strconv.FormatUint(randomValue, 10) +} + +func (a *Aggregator) AddValue(value string) { + intValue, err := strconv.ParseUint(value, 10, 64) + if err != nil { + panic("value needs to be type uint64: " + err.Error()) + } + + // Account for overflow. + if diff := math.MaxUint64 - a.sum; diff < intValue { + a.sum = intValue - diff + } else { + a.sum += intValue + } +} diff --git a/agent/p/agent/random/task/definition.go b/agent/p/agent/random/task/definition.go new file mode 100644 index 00000000000..833a855e68c --- /dev/null +++ b/agent/p/agent/random/task/definition.go @@ -0,0 +1,26 @@ +package task + +import ( + "bufio" + "bytes" + "strconv" +) + +// Input in this range. +type Definition struct { + RangeStart uint64 + RangeEnd uint64 +} + +func (d Definition) MarshalJSON() ([]byte, error) { + buf := new(bytes.Buffer) + w := bufio.NewWriter(buf) + + w.WriteString( + `{"start":` + strconv.FormatUint(d.RangeStart, 10) + + `,"end":` + strconv.FormatUint(d.RangeEnd, 10) + `}`, + ) + + w.Flush() + return buf.Bytes(), nil +} diff --git a/agent/p/agent/task/agent.go b/agent/p/agent/task/agent.go new file mode 100644 index 00000000000..c604c03c5b2 --- /dev/null +++ b/agent/p/agent/task/agent.go @@ -0,0 +1,55 @@ +package task + +import ( + "strconv" + "time" + + "gno.land/p/demo/avl" + // TODO: replace with std + "gno.land/p/demo/std" +) + +var ( + taskRegistry Registry +) + +func TransitionEra(id int) (nextEra int, nextDueTime int64) { + task := taskRegistry.Task(id) + now := time.Now() + if now.Before(task.NextDue) { + panic("era can not be transitioned until " + task.NextDue.String()) + } + + // Handle the task state transitions. + task.Era++ + task.NextDue = now.Add(task.Interval) + task.Respondents = avl.NewTree() + + resultValue := task.Aggregator.Aggregate() + taskRegistry.TaskHistory(id).AddResult(Result{Value: resultValue, Time: now}) + + return task.Era, task.NextDue.Unix() +} + +func SubmitTaskValue(id int, era int, value string) { + task := taskRegistry.Task(id) + + // Check that the era is for the next expected result. + if era != task.Era { + panic("expected era of " + strconv.Itoa(task.Era) + ", got " + strconv.Itoa(era)) + } + + // Check that the window to write has not ended. + if time.Now().After(task.NextDue) { + panic("era " + strconv.Itoa(era) + " has ended; call TODO to transition to the next era") + } + + // Each agent can only respond once during each era. + var origCaller string + if origCaller = string(std.GetOrigCaller()); task.Respondents.Has(origCaller) { + panic("response already sent for this era") + } + + task.Aggregator.AddValue(value) + task.Respondents.Set(origCaller, struct{}{}) +} diff --git a/agent/p/agent/task/aggregator.go b/agent/p/agent/task/aggregator.go new file mode 100644 index 00000000000..35d8013d284 --- /dev/null +++ b/agent/p/agent/task/aggregator.go @@ -0,0 +1,8 @@ +package task + +type Aggregator interface { + // Aggregate runs an aggregation on all values written using the AddValue method. + // It returns the aggregated value as a string. + Aggregate() string + AddValue(value string) +} diff --git a/agent/p/agent/task/definition.go b/agent/p/agent/task/definition.go new file mode 100644 index 00000000000..2a53173c289 --- /dev/null +++ b/agent/p/agent/task/definition.go @@ -0,0 +1,5 @@ +package task + +type Definition interface { + MarshalJSON() ([]byte, error) +} diff --git a/agent/p/agent/task/history.go b/agent/p/agent/task/history.go new file mode 100644 index 00000000000..02ec63b5754 --- /dev/null +++ b/agent/p/agent/task/history.go @@ -0,0 +1,16 @@ +package task + +type History struct { + Size int + NumRemoved int + Results []Result +} + +func (h *History) AddResult(result Result) { + h.Results = append(h.Results, result) + if numResults := len(h.Results); numResults > h.Size { + numToRemove := numResults - h.Size + h.Results = h.Results[numToRemove:] + h.NumRemoved += numToRemove + } +} diff --git a/agent/p/agent/task/instance.go b/agent/p/agent/task/instance.go new file mode 100644 index 00000000000..1ed42f57ad0 --- /dev/null +++ b/agent/p/agent/task/instance.go @@ -0,0 +1,42 @@ +package task + +import ( + "bufio" + "bytes" + "strconv" + "time" + + "gno.land/p/demo/avl" +) + +type Instance struct { + ID int + Type uint8 + Era int + NextDue time.Time + Interval time.Duration + Aggregator Aggregator + Definition Definition + Respondents *avl.Tree +} + +func (i Instance) MarshalJSON() ([]byte, error) { + buf := new(bytes.Buffer) + w := bufio.NewWriter(buf) + w.WriteString( + `{"id":"` + strconv.Itoa(i.ID) + + `","type":` + strconv.FormatUint(uint64(i.Type), 10) + + `,"era":` + strconv.Itoa(i.Era) + + `,"next_due":` + strconv.FormatInt(i.NextDue.Unix(), 10) + + `,"interval":` + strconv.FormatInt(int64(i.Interval/time.Second), 10) + + `,"definition":`, + ) + taskDefinitionBytes, err := i.Definition.MarshalJSON() + if err != nil { + return nil, err + } + + w.WriteString(string(taskDefinitionBytes) + "}") + w.Flush() + return buf.Bytes(), nil +} diff --git a/agent/p/agent/task/registry.go b/agent/p/agent/task/registry.go new file mode 100644 index 00000000000..716011eb782 --- /dev/null +++ b/agent/p/agent/task/registry.go @@ -0,0 +1,28 @@ +package task + +const panicTaskIndex string = "task index out of bounds" + +type Registry struct { + taskHistory []*History + tasks []*Instance +} + +func (r *Registry) Register(id int, task *Instance) { + r.tasks = append(r.tasks, task) + r.taskHistory = append(r.taskHistory, &History{}) +} + +func (r *Registry) Task(id int) *Instance { + if id < 0 || id > len(r.tasks)-1 { + panic(panicTaskIndex) + } + + return r.tasks[id] +} + +func (r *Registry) TaskHistory(id int) *History { + if id < 0 || id > len(r.taskHistory)-1 { + panic(panicTaskIndex) + } + return r.taskHistory[id] +} diff --git a/agent/p/agent/task/result.go b/agent/p/agent/task/result.go new file mode 100644 index 00000000000..8816870104a --- /dev/null +++ b/agent/p/agent/task/result.go @@ -0,0 +1,8 @@ +package task + +import "time" + +type Result struct { + Value string + Time time.Time +} diff --git a/agent/p/agent/task/type.go b/agent/p/agent/task/type.go new file mode 100644 index 00000000000..0b6fb8ab981 --- /dev/null +++ b/agent/p/agent/task/type.go @@ -0,0 +1,7 @@ +package task + +type Type uint + +const ( + TypeRandom Type = iota +) diff --git a/go.mod b/go.mod index 599396eee99..3719fb3b361 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( github.com/rs/cors v1.10.1 github.com/stretchr/testify v1.8.4 github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c + gno.land/p/demo v0.0.0-00010101000000-000000000000 go.etcd.io/bbolt v1.3.8 go.uber.org/multierr v1.9.0 golang.org/x/crypto v0.15.0 @@ -70,3 +71,7 @@ require ( golang.org/x/text v0.14.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) + +replace ( + gno.land/p/demo => ./agent/devmock +) From 9523aa44e962815c580d701629dffc89ff4967ce Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 9 Jan 2024 10:51:28 -0800 Subject: [PATCH 02/48] reworked task agent --- agent/p/agent/agent.go | 95 ++++++++++++++ .../github/registration/task/definition.go | 24 ++++ agent/p/agent/task.go | 11 ++ agent/p/agent/task/agent.go | 55 -------- agent/p/agent/task/instance.go | 42 ------ agent/p/agent/task/registry.go | 28 ---- agent/p/agent/tasks/continuous/task.go | 120 ++++++++++++++++++ 7 files changed, 250 insertions(+), 125 deletions(-) create mode 100644 agent/p/agent/agent.go create mode 100644 agent/p/agent/github/registration/task/definition.go create mode 100644 agent/p/agent/task.go delete mode 100644 agent/p/agent/task/agent.go delete mode 100644 agent/p/agent/task/instance.go delete mode 100644 agent/p/agent/task/registry.go create mode 100644 agent/p/agent/tasks/continuous/task.go diff --git a/agent/p/agent/agent.go b/agent/p/agent/agent.go new file mode 100644 index 00000000000..e84b4901383 --- /dev/null +++ b/agent/p/agent/agent.go @@ -0,0 +1,95 @@ +package agent + +import ( + // TODO: replace with std + + "strings" + + "gno.land/p/demo/avl" + "gno.land/p/demo/std" +) + +var ( + tasks *avl.Tree + adminAddress string +) + +func HandleRequest(payload string) string { + payloadParts := strings.SplitN(payload, ",", 1) + if len(payloadParts) != 2 { + panic("invalid agent payload") + } + + switch function := payloadParts[0]; function { + case "finish": + FinishTask(payloadParts[1]) + case "request": + return RequestTasks() + case "submit": + submitArgs := strings.SplitN(payloadParts[1], ",", 1) + if len(submitArgs) != 2 { + panic("invalid agent submission payload") + } + + SubmitTaskValue(submitArgs[0], submitArgs[1]) + default: + panic("unknown function " + function) + } + + return "" +} + +func FinishTask(id string) { + task, ok := tasks.Get(id) + if !ok { + panic("task not found") + } + + task.(Task).Finish(string(std.GetOrigCaller())) +} + +func RequestTasks() string { + buf := new(strings.Builder) + buf.WriteString("[") + first := true + tasks.Iterate("", "", func(_ string, value interface{}) bool { + if !first { + buf.WriteString(",") + } + + first = false + task := value.(Task) + taskBytes, err := task.MarshalJSON() + if err != nil { + panic(err) + } + + buf.Write(taskBytes) + return true + }) + buf.WriteString("]") + return buf.String() +} + +func SubmitTaskValue(id, value string) { + task, ok := tasks.Get(id) + if !ok { + panic("task not found") + } + + task.(Task).SubmitResult(string(std.GetOrigCaller()), value) +} + +func Init(admin std.Address, newTasks ...Task) { + if tasks != nil { + panic("already initialized") + } + + adminAddress = string(admin) + tasks = avl.NewTree() + for _, task := range newTasks { + if updated := tasks.Set(task.ID(), task); updated { + panic("task id " + task.ID() + " already exists") + } + } +} diff --git a/agent/p/agent/github/registration/task/definition.go b/agent/p/agent/github/registration/task/definition.go new file mode 100644 index 00000000000..836c208a83c --- /dev/null +++ b/agent/p/agent/github/registration/task/definition.go @@ -0,0 +1,24 @@ +package task + +import ( + "bufio" + "bytes" +) + +type Definition struct { + Handle string + Signature string +} + +func (d Definition) MarshalJSON() ([]byte, error) { + buf := new(bytes.Buffer) + w := bufio.NewWriter(buf) + + w.WriteString( + `{"handle":"` + d.Handle + + `","signature":"` + d.Signature + `"}`, + ) + + w.Flush() + return buf.Bytes(), nil +} diff --git a/agent/p/agent/task.go b/agent/p/agent/task.go new file mode 100644 index 00000000000..a9893546069 --- /dev/null +++ b/agent/p/agent/task.go @@ -0,0 +1,11 @@ +package agent + +import "github.com/gnolang/gno/agent/p/agent/task" + +type Task interface { + Finish(origCaller string) + GetResult() (result task.Result, hasResult bool) + ID() string + MarshalJSON() ([]byte, error) + SubmitResult(origCaller, value string) +} diff --git a/agent/p/agent/task/agent.go b/agent/p/agent/task/agent.go deleted file mode 100644 index c604c03c5b2..00000000000 --- a/agent/p/agent/task/agent.go +++ /dev/null @@ -1,55 +0,0 @@ -package task - -import ( - "strconv" - "time" - - "gno.land/p/demo/avl" - // TODO: replace with std - "gno.land/p/demo/std" -) - -var ( - taskRegistry Registry -) - -func TransitionEra(id int) (nextEra int, nextDueTime int64) { - task := taskRegistry.Task(id) - now := time.Now() - if now.Before(task.NextDue) { - panic("era can not be transitioned until " + task.NextDue.String()) - } - - // Handle the task state transitions. - task.Era++ - task.NextDue = now.Add(task.Interval) - task.Respondents = avl.NewTree() - - resultValue := task.Aggregator.Aggregate() - taskRegistry.TaskHistory(id).AddResult(Result{Value: resultValue, Time: now}) - - return task.Era, task.NextDue.Unix() -} - -func SubmitTaskValue(id int, era int, value string) { - task := taskRegistry.Task(id) - - // Check that the era is for the next expected result. - if era != task.Era { - panic("expected era of " + strconv.Itoa(task.Era) + ", got " + strconv.Itoa(era)) - } - - // Check that the window to write has not ended. - if time.Now().After(task.NextDue) { - panic("era " + strconv.Itoa(era) + " has ended; call TODO to transition to the next era") - } - - // Each agent can only respond once during each era. - var origCaller string - if origCaller = string(std.GetOrigCaller()); task.Respondents.Has(origCaller) { - panic("response already sent for this era") - } - - task.Aggregator.AddValue(value) - task.Respondents.Set(origCaller, struct{}{}) -} diff --git a/agent/p/agent/task/instance.go b/agent/p/agent/task/instance.go deleted file mode 100644 index 1ed42f57ad0..00000000000 --- a/agent/p/agent/task/instance.go +++ /dev/null @@ -1,42 +0,0 @@ -package task - -import ( - "bufio" - "bytes" - "strconv" - "time" - - "gno.land/p/demo/avl" -) - -type Instance struct { - ID int - Type uint8 - Era int - NextDue time.Time - Interval time.Duration - Aggregator Aggregator - Definition Definition - Respondents *avl.Tree -} - -func (i Instance) MarshalJSON() ([]byte, error) { - buf := new(bytes.Buffer) - w := bufio.NewWriter(buf) - w.WriteString( - `{"id":"` + strconv.Itoa(i.ID) + - `","type":` + strconv.FormatUint(uint64(i.Type), 10) + - `,"era":` + strconv.Itoa(i.Era) + - `,"next_due":` + strconv.FormatInt(i.NextDue.Unix(), 10) + - `,"interval":` + strconv.FormatInt(int64(i.Interval/time.Second), 10) + - `,"definition":`, - ) - taskDefinitionBytes, err := i.Definition.MarshalJSON() - if err != nil { - return nil, err - } - - w.WriteString(string(taskDefinitionBytes) + "}") - w.Flush() - return buf.Bytes(), nil -} diff --git a/agent/p/agent/task/registry.go b/agent/p/agent/task/registry.go deleted file mode 100644 index 716011eb782..00000000000 --- a/agent/p/agent/task/registry.go +++ /dev/null @@ -1,28 +0,0 @@ -package task - -const panicTaskIndex string = "task index out of bounds" - -type Registry struct { - taskHistory []*History - tasks []*Instance -} - -func (r *Registry) Register(id int, task *Instance) { - r.tasks = append(r.tasks, task) - r.taskHistory = append(r.taskHistory, &History{}) -} - -func (r *Registry) Task(id int) *Instance { - if id < 0 || id > len(r.tasks)-1 { - panic(panicTaskIndex) - } - - return r.tasks[id] -} - -func (r *Registry) TaskHistory(id int) *History { - if id < 0 || id > len(r.taskHistory)-1 { - panic(panicTaskIndex) - } - return r.taskHistory[id] -} diff --git a/agent/p/agent/tasks/continuous/task.go b/agent/p/agent/tasks/continuous/task.go new file mode 100644 index 00000000000..21c939e55c2 --- /dev/null +++ b/agent/p/agent/tasks/continuous/task.go @@ -0,0 +1,120 @@ +package continuous + +import ( + "bufio" + "bytes" + "strconv" + "strings" + "time" + + "github.com/gnolang/gno/agent/p/agent/task" + "gno.land/p/demo/avl" +) + +type Task struct { + id string + Type uint8 + Era int + NextDue time.Time + Interval time.Duration + Aggregator task.Aggregator + Definition task.Definition + RespondentWhiteList *avl.Tree + Respondents *avl.Tree + History task.History +} + +func (t *Task) Finish(origCaller string) { + now := time.Now() + if now.Before(t.NextDue) { + panic("era can not be transitioned until " + t.NextDue.String()) + } + + if t.RespondentWhiteList != nil { + if !t.RespondentWhiteList.Has(origCaller) { + panic("caller not in whitelist") + } + } + + // Handle the task state transitions. + t.Era++ + t.NextDue = now.Add(t.Interval) + t.Respondents = avl.NewTree() + + resultValue := t.Aggregator.Aggregate() + t.History.AddResult(task.Result{Value: resultValue, Time: now}) + + return +} + +func (t Task) GetResult() (result task.Result, hasResult bool) { + if len(t.History.Results) == 0 { + return + } + + return t.History.Results[len(t.History.Results)-1], true +} + +func (t Task) ID() string { + return t.id +} + +func (t Task) MarshalJSON() ([]byte, error) { + buf := new(bytes.Buffer) + w := bufio.NewWriter(buf) + w.WriteString( + `{"id":"` + t.id + + `","type":` + strconv.FormatUint(uint64(t.Type), 10) + + `,"era":` + strconv.Itoa(t.Era) + + `,"next_due":` + strconv.FormatInt(t.NextDue.Unix(), 10) + + `,"interval":` + strconv.FormatInt(int64(t.Interval/time.Second), 10) + + `,"definition":`, + ) + taskDefinitionBytes, err := t.Definition.MarshalJSON() + if err != nil { + return nil, err + } + + w.WriteString(string(taskDefinitionBytes) + "}") + w.Flush() + return buf.Bytes(), nil +} + +func (t *Task) SubmitResult(origCaller, value string) { + var era int + valueParts := strings.SplitN(value, ",", 1) + if len(valueParts) != 2 { + panic("invalid result value; must be prefixed with era + ','") + } + + era, err := strconv.Atoi(valueParts[0]) + if err != nil { + panic(valueParts[0] + " is not a valid era") + } + + value = valueParts[1] + + // Check that the era is for the next expected result. + if era != t.Era { + panic("expected era of " + strconv.Itoa(t.Era) + ", got " + strconv.Itoa(era)) + } + + // Check that the window to write has not ended. + if time.Now().After(t.NextDue) { + panic("era " + strconv.Itoa(t.Era) + " has ended; call Finish to transition to the next era") + } + + if t.RespondentWhiteList != nil { + if !t.RespondentWhiteList.Has(origCaller) { + panic("caller not in whitelist") + } + } + + // Each agent can only respond once during each era. + if t.Respondents.Has(origCaller) { + panic("response already sent for this era") + } + + t.Aggregator.AddValue(value) + t.Respondents.Set(origCaller, struct{}{}) +} From 2f5d997fbb7be30094b1cc34234a70cf9a21def1 Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 9 Jan 2024 13:55:56 -0800 Subject: [PATCH 03/48] reorg and more features --- agent/p/agent/agent.go | 30 ++++++- .../task/definition.go | 6 ++ agent/p/agent/random/task/definition.go | 6 ++ agent/p/agent/task/definition.go | 1 + agent/p/agent/task/type.go | 7 -- agent/p/agent/tasks/continuous/task.go | 8 +- agent/p/agent/tasks/singular/task.go | 84 +++++++++++++++++++ 7 files changed, 130 insertions(+), 12 deletions(-) rename agent/p/agent/github/{registration => verification}/task/definition.go (79%) delete mode 100644 agent/p/agent/task/type.go create mode 100644 agent/p/agent/tasks/singular/task.go diff --git a/agent/p/agent/agent.go b/agent/p/agent/agent.go index e84b4901383..b8298ae290f 100644 --- a/agent/p/agent/agent.go +++ b/agent/p/agent/agent.go @@ -57,13 +57,19 @@ func RequestTasks() string { buf.WriteString(",") } - first = false task := value.(Task) taskBytes, err := task.MarshalJSON() if err != nil { panic(err) } + // Guard against any tasks that shouldn't be returned; maybe they are not active because they have + // already been completed. + if len(taskBytes) == 0 { + return true + } + + first = false buf.Write(taskBytes) return true }) @@ -80,6 +86,28 @@ func SubmitTaskValue(id, value string) { task.(Task).SubmitResult(string(std.GetOrigCaller()), value) } +func AddTask(task Task) { + if string(std.GetOrigCaller()) != adminAddress { + panic("only admin can add tasks") + } + + if tasks.Has(task.ID()) { + panic("task id " + task.ID() + " already exists") + } + + tasks.Set(task.ID(), task) +} + +func RemoveTask(id string) { + if string(std.GetOrigCaller()) != adminAddress { + panic("only admin can remove tasks") + } + + if _, removed := tasks.Remove(id); !removed { + panic("task id " + id + " not found") + } +} + func Init(admin std.Address, newTasks ...Task) { if tasks != nil { panic("already initialized") diff --git a/agent/p/agent/github/registration/task/definition.go b/agent/p/agent/github/verification/task/definition.go similarity index 79% rename from agent/p/agent/github/registration/task/definition.go rename to agent/p/agent/github/verification/task/definition.go index 836c208a83c..51851effc04 100644 --- a/agent/p/agent/github/registration/task/definition.go +++ b/agent/p/agent/github/verification/task/definition.go @@ -5,6 +5,8 @@ import ( "bytes" ) +const Type string = "gh-verification" + type Definition struct { Handle string Signature string @@ -22,3 +24,7 @@ func (d Definition) MarshalJSON() ([]byte, error) { w.Flush() return buf.Bytes(), nil } + +func (d Definition) Type() string { + return Type +} diff --git a/agent/p/agent/random/task/definition.go b/agent/p/agent/random/task/definition.go index 833a855e68c..b0165832103 100644 --- a/agent/p/agent/random/task/definition.go +++ b/agent/p/agent/random/task/definition.go @@ -6,6 +6,8 @@ import ( "strconv" ) +const Type string = "random" + // Input in this range. type Definition struct { RangeStart uint64 @@ -24,3 +26,7 @@ func (d Definition) MarshalJSON() ([]byte, error) { w.Flush() return buf.Bytes(), nil } + +func (d Definition) Type() string { + return Type +} diff --git a/agent/p/agent/task/definition.go b/agent/p/agent/task/definition.go index 2a53173c289..39f40eeb026 100644 --- a/agent/p/agent/task/definition.go +++ b/agent/p/agent/task/definition.go @@ -2,4 +2,5 @@ package task type Definition interface { MarshalJSON() ([]byte, error) + Type() string } diff --git a/agent/p/agent/task/type.go b/agent/p/agent/task/type.go deleted file mode 100644 index 0b6fb8ab981..00000000000 --- a/agent/p/agent/task/type.go +++ /dev/null @@ -1,7 +0,0 @@ -package task - -type Type uint - -const ( - TypeRandom Type = iota -) diff --git a/agent/p/agent/tasks/continuous/task.go b/agent/p/agent/tasks/continuous/task.go index 21c939e55c2..00308113e4f 100644 --- a/agent/p/agent/tasks/continuous/task.go +++ b/agent/p/agent/tasks/continuous/task.go @@ -13,7 +13,6 @@ import ( type Task struct { id string - Type uint8 Era int NextDue time.Time Interval time.Duration @@ -64,8 +63,8 @@ func (t Task) MarshalJSON() ([]byte, error) { w := bufio.NewWriter(buf) w.WriteString( `{"id":"` + t.id + - `","type":` + strconv.FormatUint(uint64(t.Type), 10) + - `,"era":` + strconv.Itoa(t.Era) + + `","type":"` + t.Definition.Type() + + `","era":` + strconv.Itoa(t.Era) + `,"next_due":` + strconv.FormatInt(t.NextDue.Unix(), 10) + `,"interval":` + strconv.FormatInt(int64(t.Interval/time.Second), 10) + `,"definition":`, @@ -75,7 +74,8 @@ func (t Task) MarshalJSON() ([]byte, error) { return nil, err } - w.WriteString(string(taskDefinitionBytes) + "}") + w.Write(taskDefinitionBytes) + w.WriteString("}") w.Flush() return buf.Bytes(), nil } diff --git a/agent/p/agent/tasks/singular/task.go b/agent/p/agent/tasks/singular/task.go new file mode 100644 index 00000000000..82bb709bf0d --- /dev/null +++ b/agent/p/agent/tasks/singular/task.go @@ -0,0 +1,84 @@ +package singular + +import ( + "bufio" + "bytes" + "time" + + "github.com/gnolang/gno/agent/p/agent/task" +) + +type Task struct { + id string + definition task.Definition + + isInactive bool + result *task.Result + authorizedRespondent string +} + +func NewTask(id string, definition task.Definition, authorizedRespondent string) *Task { + return &Task{ + id: id, + definition: definition, + authorizedRespondent: authorizedRespondent, + } +} + +func (t Task) Finish(_ string) { + panic("singular tasks are implicitly finished when a result is submitted") +} + +func (t Task) GetResult() (result task.Result, hasResult bool) { + if t.result == nil { + return + } + + return *t.result, true +} + +func (t Task) ID() string { + return t.id +} + +func (t Task) MarshalJSON() ([]byte, error) { + if !t.isInactive { + return nil, nil + } + + buf := new(bytes.Buffer) + w := bufio.NewWriter(buf) + w.WriteString( + `{"id":"` + t.id + + `","type":"` + t.definition.Type() + + `","definition":`, + ) + + taskDefinitionBytes, err := t.definition.MarshalJSON() + if err != nil { + return nil, err + } + + w.Write(taskDefinitionBytes) + w.WriteString("}") + w.Flush() + return buf.Bytes(), nil +} + +func (t *Task) SubmitResult(origCaller, value string) { + if t.isInactive { + panic("task is inactive") + } + + if t.authorizedRespondent != origCaller { + panic("caller not authorized to submit result") + } + + t.result = &task.Result{ + Value: value, + Time: time.Now(), + } + + t.isInactive = true + return +} From b6f99b067f897e107e01d76e52114d599f59e9d3 Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 9 Jan 2024 15:01:11 -0800 Subject: [PATCH 04/48] added github contract that uses the oracle --- agent/p/agent/agent.go | 42 +++++---- .../github/verification/task/definition.go | 6 +- agent/p/agent/task.go | 1 + agent/p/agent/tasks/continuous/task.go | 10 ++- agent/p/agent/tasks/singular/task.go | 4 + agent/r/gh/contract.go | 90 +++++++++++++++++++ 6 files changed, 131 insertions(+), 22 deletions(-) create mode 100644 agent/r/gh/contract.go diff --git a/agent/p/agent/agent.go b/agent/p/agent/agent.go index b8298ae290f..8a4b214217d 100644 --- a/agent/p/agent/agent.go +++ b/agent/p/agent/agent.go @@ -10,28 +10,44 @@ import ( ) var ( - tasks *avl.Tree + tasks *avl.Tree + + // Not sure this is necessary. adminAddress string ) -func HandleRequest(payload string) string { +const ( + FunctionFinish = "finish" + FunctionRequest = "request" + FunctionSubmit = "submit" +) + +type PostRequestAction func(function string, task Task) + +func HandleRequest(payload string, post PostRequestAction) string { payloadParts := strings.SplitN(payload, ",", 1) if len(payloadParts) != 2 { panic("invalid agent payload") } switch function := payloadParts[0]; function { - case "finish": - FinishTask(payloadParts[1]) - case "request": + case FunctionFinish: + task := FinishTask(payloadParts[1]) + if post != nil { + post(function, task) + } + case FunctionRequest: return RequestTasks() - case "submit": + case FunctionSubmit: submitArgs := strings.SplitN(payloadParts[1], ",", 1) if len(submitArgs) != 2 { panic("invalid agent submission payload") } SubmitTaskValue(submitArgs[0], submitArgs[1]) + if post != nil { + post(function, nil) + } default: panic("unknown function " + function) } @@ -39,13 +55,14 @@ func HandleRequest(payload string) string { return "" } -func FinishTask(id string) { +func FinishTask(id string) Task { task, ok := tasks.Get(id) if !ok { panic("task not found") } task.(Task).Finish(string(std.GetOrigCaller())) + return task.(Task) } func RequestTasks() string { @@ -77,20 +94,17 @@ func RequestTasks() string { return buf.String() } -func SubmitTaskValue(id, value string) { +func SubmitTaskValue(id, value string) Task { task, ok := tasks.Get(id) if !ok { panic("task not found") } task.(Task).SubmitResult(string(std.GetOrigCaller()), value) + return task.(Task) } func AddTask(task Task) { - if string(std.GetOrigCaller()) != adminAddress { - panic("only admin can add tasks") - } - if tasks.Has(task.ID()) { panic("task id " + task.ID() + " already exists") } @@ -99,10 +113,6 @@ func AddTask(task Task) { } func RemoveTask(id string) { - if string(std.GetOrigCaller()) != adminAddress { - panic("only admin can remove tasks") - } - if _, removed := tasks.Remove(id); !removed { panic("task id " + id + " not found") } diff --git a/agent/p/agent/github/verification/task/definition.go b/agent/p/agent/github/verification/task/definition.go index 51851effc04..87c918027ca 100644 --- a/agent/p/agent/github/verification/task/definition.go +++ b/agent/p/agent/github/verification/task/definition.go @@ -8,8 +8,8 @@ import ( const Type string = "gh-verification" type Definition struct { - Handle string - Signature string + Handle string + Address string } func (d Definition) MarshalJSON() ([]byte, error) { @@ -18,7 +18,7 @@ func (d Definition) MarshalJSON() ([]byte, error) { w.WriteString( `{"handle":"` + d.Handle + - `","signature":"` + d.Signature + `"}`, + `","address":"` + d.Address + `"}`, ) w.Flush() diff --git a/agent/p/agent/task.go b/agent/p/agent/task.go index a9893546069..6815b8a40db 100644 --- a/agent/p/agent/task.go +++ b/agent/p/agent/task.go @@ -3,6 +3,7 @@ package agent import "github.com/gnolang/gno/agent/p/agent/task" type Task interface { + Definition() task.Definition Finish(origCaller string) GetResult() (result task.Result, hasResult bool) ID() string diff --git a/agent/p/agent/tasks/continuous/task.go b/agent/p/agent/tasks/continuous/task.go index 00308113e4f..820336d4082 100644 --- a/agent/p/agent/tasks/continuous/task.go +++ b/agent/p/agent/tasks/continuous/task.go @@ -17,12 +17,16 @@ type Task struct { NextDue time.Time Interval time.Duration Aggregator task.Aggregator - Definition task.Definition + definition task.Definition RespondentWhiteList *avl.Tree Respondents *avl.Tree History task.History } +func (t Task) Definition() task.Definition { + return t.definition +} + func (t *Task) Finish(origCaller string) { now := time.Now() if now.Before(t.NextDue) { @@ -63,13 +67,13 @@ func (t Task) MarshalJSON() ([]byte, error) { w := bufio.NewWriter(buf) w.WriteString( `{"id":"` + t.id + - `","type":"` + t.Definition.Type() + + `","type":"` + t.definition.Type() + `","era":` + strconv.Itoa(t.Era) + `,"next_due":` + strconv.FormatInt(t.NextDue.Unix(), 10) + `,"interval":` + strconv.FormatInt(int64(t.Interval/time.Second), 10) + `,"definition":`, ) - taskDefinitionBytes, err := t.Definition.MarshalJSON() + taskDefinitionBytes, err := t.definition.MarshalJSON() if err != nil { return nil, err } diff --git a/agent/p/agent/tasks/singular/task.go b/agent/p/agent/tasks/singular/task.go index 82bb709bf0d..75949bbd47d 100644 --- a/agent/p/agent/tasks/singular/task.go +++ b/agent/p/agent/tasks/singular/task.go @@ -25,6 +25,10 @@ func NewTask(id string, definition task.Definition, authorizedRespondent string) } } +func (t Task) Definition() task.Definition { + return t.definition +} + func (t Task) Finish(_ string) { panic("singular tasks are implicitly finished when a result is submitted") } diff --git a/agent/r/gh/contract.go b/agent/r/gh/contract.go new file mode 100644 index 00000000000..9dc1e258eb5 --- /dev/null +++ b/agent/r/gh/contract.go @@ -0,0 +1,90 @@ +package gh + +import ( + "bufio" + "bytes" + + "github.com/gnolang/gno/agent/p/agent" + ghTask "github.com/gnolang/gno/agent/p/agent/github/verification/task" + "github.com/gnolang/gno/agent/p/agent/tasks/singular" + "gno.land/p/demo/avl" + "gno.land/p/demo/std" +) + +const ( + authorizedAgentAddress string = "" + verified = "OK" +) + +var ( + handleToAddress = avl.NewTree() + addressToHandle = avl.NewTree() +) + +func init() { + agent.Init(std.GetOrigCaller()) +} + +func updateVerifiedGHData(function string, task agent.Task) { + if function != agent.FunctionSubmit { + return + } + + result, hasResult := task.GetResult() + if !hasResult { + return + } + + if result.Value != verified { + return + } + + definition, ok := task.Definition().(ghTask.Definition) + if !ok { + panic("unexpected task definition of type " + definition.Type()) + } + + handleToAddress.Set(definition.Handle, definition.Address) + addressToHandle.Set(definition.Address, definition.Handle) + + // It's been verified so clean it up. + agent.RemoveTask(task.ID()) +} + +func OrkleAgentSubmitAction(payload string) string { + return agent.HandleRequest(payload, updateVerifiedGHData) +} + +func RequestVerification(handle, address string) { + if address == "" { + address = string(std.GetOrigCaller()) + } + + agent.AddTask( + singular.NewTask( + handle, + ghTask.Definition{Handle: handle, Address: address}, + authorizedAgentAddress, + ), + ) +} + +func Render(_ string) string { + buf := new(bytes.Buffer) + w := bufio.NewWriter(buf) + w.WriteString(`{"verified":{`) + first := true + handleToAddress.Iterate("", "", func(key string, value interface{}) bool { + if !first { + w.WriteString(",") + } + + w.WriteString(`"` + key + `":"` + value.(string) + `"`) + first = false + return true + }) + + w.WriteString(`}}`) + w.Flush() + return buf.String() +} From 43c11309a05c8d1fe94ae25cee119aac1c32711a Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 9 Jan 2024 16:21:21 -0800 Subject: [PATCH 05/48] moved files to examples for creation at genesis --- .../p/demo/agent/aatask/aggregator.gno | 8 ++ .../p/demo/agent/aatask/definition.gno | 6 + examples/gno.land/p/demo/agent/aatask/gno.mod | 1 + .../gno.land/p/demo/agent/aatask/history.gno | 16 +++ .../gno.land/p/demo/agent/aatask/result.gno | 8 ++ .../gno.land/p/demo/agent/agent/agent.gno | 135 ++++++++++++++++++ examples/gno.land/p/demo/agent/agent/gno.mod | 1 + examples/gno.land/p/demo/agent/agent/task.gno | 12 ++ .../github/verification/task/definition.gno | 30 ++++ .../agent/github/verification/task/gno.mod | 1 + .../demo/agent/pricefeed/task/definition.gno | 6 + .../p/demo/agent/pricefeed/task/gno.mod | 1 + .../p/demo/agent/random/task/aggregator.gno | 35 +++++ .../p/demo/agent/random/task/definition.gno | 32 +++++ .../gno.land/p/demo/agent/random/task/gno.mod | 1 + .../p/demo/agent/tasks/continuous/gno.mod | 1 + .../p/demo/agent/tasks/continuous/task.gno | 124 ++++++++++++++++ .../p/demo/agent/tasks/singular/gno.mod | 1 + .../p/demo/agent/tasks/singular/task.gno | 88 ++++++++++++ examples/gno.land/r/gnoland/gh/contract.gno | 91 ++++++++++++ examples/gno.land/r/gnoland/gh/gno.mod | 1 + 21 files changed, 599 insertions(+) create mode 100644 examples/gno.land/p/demo/agent/aatask/aggregator.gno create mode 100644 examples/gno.land/p/demo/agent/aatask/definition.gno create mode 100644 examples/gno.land/p/demo/agent/aatask/gno.mod create mode 100644 examples/gno.land/p/demo/agent/aatask/history.gno create mode 100644 examples/gno.land/p/demo/agent/aatask/result.gno create mode 100644 examples/gno.land/p/demo/agent/agent/agent.gno create mode 100644 examples/gno.land/p/demo/agent/agent/gno.mod create mode 100644 examples/gno.land/p/demo/agent/agent/task.gno create mode 100644 examples/gno.land/p/demo/agent/github/verification/task/definition.gno create mode 100644 examples/gno.land/p/demo/agent/github/verification/task/gno.mod create mode 100644 examples/gno.land/p/demo/agent/pricefeed/task/definition.gno create mode 100644 examples/gno.land/p/demo/agent/pricefeed/task/gno.mod create mode 100644 examples/gno.land/p/demo/agent/random/task/aggregator.gno create mode 100644 examples/gno.land/p/demo/agent/random/task/definition.gno create mode 100644 examples/gno.land/p/demo/agent/random/task/gno.mod create mode 100644 examples/gno.land/p/demo/agent/tasks/continuous/gno.mod create mode 100644 examples/gno.land/p/demo/agent/tasks/continuous/task.gno create mode 100644 examples/gno.land/p/demo/agent/tasks/singular/gno.mod create mode 100644 examples/gno.land/p/demo/agent/tasks/singular/task.gno create mode 100644 examples/gno.land/r/gnoland/gh/contract.gno create mode 100644 examples/gno.land/r/gnoland/gh/gno.mod diff --git a/examples/gno.land/p/demo/agent/aatask/aggregator.gno b/examples/gno.land/p/demo/agent/aatask/aggregator.gno new file mode 100644 index 00000000000..7a0eb541d7a --- /dev/null +++ b/examples/gno.land/p/demo/agent/aatask/aggregator.gno @@ -0,0 +1,8 @@ +package aatask + +type Aggregator interface { + // Aggregate runs an aggregation on all values written using the AddValue method. + // It returns the aggregated value as a string. + Aggregate() string + AddValue(value string) +} diff --git a/examples/gno.land/p/demo/agent/aatask/definition.gno b/examples/gno.land/p/demo/agent/aatask/definition.gno new file mode 100644 index 00000000000..aeb6cff8dbb --- /dev/null +++ b/examples/gno.land/p/demo/agent/aatask/definition.gno @@ -0,0 +1,6 @@ +package aatask + +type Definition interface { + MarshalJSON() ([]byte, error) + Type() string +} diff --git a/examples/gno.land/p/demo/agent/aatask/gno.mod b/examples/gno.land/p/demo/agent/aatask/gno.mod new file mode 100644 index 00000000000..518feb52405 --- /dev/null +++ b/examples/gno.land/p/demo/agent/aatask/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/agent/aatask diff --git a/examples/gno.land/p/demo/agent/aatask/history.gno b/examples/gno.land/p/demo/agent/aatask/history.gno new file mode 100644 index 00000000000..5e460ccca57 --- /dev/null +++ b/examples/gno.land/p/demo/agent/aatask/history.gno @@ -0,0 +1,16 @@ +package aatask + +type History struct { + Size int + NumRemoved int + Results []Result +} + +func (h *History) AddResult(result Result) { + h.Results = append(h.Results, result) + if numResults := len(h.Results); numResults > h.Size { + numToRemove := numResults - h.Size + h.Results = h.Results[numToRemove:] + h.NumRemoved += numToRemove + } +} diff --git a/examples/gno.land/p/demo/agent/aatask/result.gno b/examples/gno.land/p/demo/agent/aatask/result.gno new file mode 100644 index 00000000000..64848f08ade --- /dev/null +++ b/examples/gno.land/p/demo/agent/aatask/result.gno @@ -0,0 +1,8 @@ +package aatask + +import "time" + +type Result struct { + Value string + Time time.Time +} diff --git a/examples/gno.land/p/demo/agent/agent/agent.gno b/examples/gno.land/p/demo/agent/agent/agent.gno new file mode 100644 index 00000000000..5fe32465fb7 --- /dev/null +++ b/examples/gno.land/p/demo/agent/agent/agent.gno @@ -0,0 +1,135 @@ +package agent + +import ( + // TODO: replace with std + + "std" + "strings" + + "gno.land/p/demo/avl" +) + +type Instance struct { + tasks *avl.Tree + + // Not sure this is necessary. + adminAddress string +} + +const ( + FunctionFinish = "finish" + FunctionRequest = "request" + FunctionSubmit = "submit" +) + +type PostRequestAction func(function string, task Task) + +func (i *Instance) HandleRequest(payload string, post PostRequestAction) string { + payloadParts := strings.SplitN(payload, ",", 1) + if len(payloadParts) != 2 { + panic("invalid agent payload") + } + + switch function := payloadParts[0]; function { + case FunctionFinish: + task := i.FinishTask(payloadParts[1]) + if post != nil { + post(function, task) + } + case FunctionRequest: + return i.RequestTasks() + case FunctionSubmit: + submitArgs := strings.SplitN(payloadParts[1], ",", 1) + if len(submitArgs) != 2 { + panic("invalid agent submission payload") + } + + task := i.SubmitTaskValue(submitArgs[0], submitArgs[1]) + if post != nil { + post(function, task) + } + default: + panic("unknown function " + function) + } + + return "" +} + +func (i *Instance) FinishTask(id string) Task { + task, ok := i.tasks.Get(id) + if !ok { + panic("task not found") + } + + task.(Task).Finish(string(std.GetOrigCaller())) + return task.(Task) +} + +func (i *Instance) RequestTasks() string { + buf := new(strings.Builder) + buf.WriteString("[") + first := true + i.tasks.Iterate("", "", func(_ string, value interface{}) bool { + if !first { + buf.WriteString(",") + } + + task := value.(Task) + taskBytes, err := task.MarshalJSON() + if err != nil { + panic(err) + } + + // Guard against any tasks that shouldn't be returned; maybe they are not active because they have + // already been completed. + if len(taskBytes) == 0 { + return true + } + + first = false + buf.Write(taskBytes) + return true + }) + buf.WriteString("]") + return buf.String() +} + +func (i *Instance) SubmitTaskValue(id, value string) Task { + task, ok := i.tasks.Get(id) + if !ok { + panic("task not found") + } + + task.(Task).SubmitResult(string(std.GetOrigCaller()), value) + return task.(Task) +} + +func (i *Instance) AddTask(task Task) { + if i.tasks.Has(task.ID()) { + panic("task id " + task.ID() + " already exists") + } + + i.tasks.Set(task.ID(), task) +} + +func (i *Instance) RemoveTask(id string) { + if _, removed := i.tasks.Remove(id); !removed { + panic("task id " + id + " not found") + } +} + +func Init(admin std.Address, newTasks ...Task) *Instance { + i := &Instance{ + adminAddress: string(admin), + } + + tasks := avl.NewTree() + for _, task := range newTasks { + if updated := tasks.Set(task.ID(), task); updated { + panic("task id " + task.ID() + " already exists") + } + } + + i.tasks = tasks + return i +} diff --git a/examples/gno.land/p/demo/agent/agent/gno.mod b/examples/gno.land/p/demo/agent/agent/gno.mod new file mode 100644 index 00000000000..1bb323c9b5b --- /dev/null +++ b/examples/gno.land/p/demo/agent/agent/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/agent/agent diff --git a/examples/gno.land/p/demo/agent/agent/task.gno b/examples/gno.land/p/demo/agent/agent/task.gno new file mode 100644 index 00000000000..5fdbd18f83e --- /dev/null +++ b/examples/gno.land/p/demo/agent/agent/task.gno @@ -0,0 +1,12 @@ +package agent + +import "gno.land/p/demo/agent/aatask" + +type Task interface { + Definition() aatask.Definition + Finish(origCaller string) + GetResult() (result aatask.Result, hasResult bool) + ID() string + MarshalJSON() ([]byte, error) + SubmitResult(origCaller, value string) +} diff --git a/examples/gno.land/p/demo/agent/github/verification/task/definition.gno b/examples/gno.land/p/demo/agent/github/verification/task/definition.gno new file mode 100644 index 00000000000..87c918027ca --- /dev/null +++ b/examples/gno.land/p/demo/agent/github/verification/task/definition.gno @@ -0,0 +1,30 @@ +package task + +import ( + "bufio" + "bytes" +) + +const Type string = "gh-verification" + +type Definition struct { + Handle string + Address string +} + +func (d Definition) MarshalJSON() ([]byte, error) { + buf := new(bytes.Buffer) + w := bufio.NewWriter(buf) + + w.WriteString( + `{"handle":"` + d.Handle + + `","address":"` + d.Address + `"}`, + ) + + w.Flush() + return buf.Bytes(), nil +} + +func (d Definition) Type() string { + return Type +} diff --git a/examples/gno.land/p/demo/agent/github/verification/task/gno.mod b/examples/gno.land/p/demo/agent/github/verification/task/gno.mod new file mode 100644 index 00000000000..526238926b7 --- /dev/null +++ b/examples/gno.land/p/demo/agent/github/verification/task/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/agent/github/verification/task diff --git a/examples/gno.land/p/demo/agent/pricefeed/task/definition.gno b/examples/gno.land/p/demo/agent/pricefeed/task/definition.gno new file mode 100644 index 00000000000..8aef6ee02a5 --- /dev/null +++ b/examples/gno.land/p/demo/agent/pricefeed/task/definition.gno @@ -0,0 +1,6 @@ +package task + +type Definition struct { + Pair string // USD/CAD + AcceptedDeviation float64 +} diff --git a/examples/gno.land/p/demo/agent/pricefeed/task/gno.mod b/examples/gno.land/p/demo/agent/pricefeed/task/gno.mod new file mode 100644 index 00000000000..16b38cd9e7f --- /dev/null +++ b/examples/gno.land/p/demo/agent/pricefeed/task/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/agent/pricefeed/task diff --git a/examples/gno.land/p/demo/agent/random/task/aggregator.gno b/examples/gno.land/p/demo/agent/random/task/aggregator.gno new file mode 100644 index 00000000000..05a44eb5f75 --- /dev/null +++ b/examples/gno.land/p/demo/agent/random/task/aggregator.gno @@ -0,0 +1,35 @@ +package task + +import ( + "math" + "strconv" +) + +type Aggregator struct { + sum uint64 + taskDefinition *Definition +} + +func (a *Aggregator) Aggregate() string { + randomValue := a.taskDefinition.RangeStart + a.sum%a.taskDefinition.RangeEnd + a.sum = 0 + return strconv.FormatUint(randomValue, 10) +} + +func (a *Aggregator) AddValue(value string) { + intValue, err := strconv.Atoi(value) + if err != nil { + panic("value needs to be type uint64: " + err.Error()) + } + + a.sum += uint64(intValue) + + // No need to check for overflow currently because ParseUint is not supported. + + // Account for overflow. + // if diff := math.MaxUint64 - a.sum; diff < intValue { + // a.sum = intValue - diff + // } else { + // a.sum += intValue + // } +} diff --git a/examples/gno.land/p/demo/agent/random/task/definition.gno b/examples/gno.land/p/demo/agent/random/task/definition.gno new file mode 100644 index 00000000000..b0165832103 --- /dev/null +++ b/examples/gno.land/p/demo/agent/random/task/definition.gno @@ -0,0 +1,32 @@ +package task + +import ( + "bufio" + "bytes" + "strconv" +) + +const Type string = "random" + +// Input in this range. +type Definition struct { + RangeStart uint64 + RangeEnd uint64 +} + +func (d Definition) MarshalJSON() ([]byte, error) { + buf := new(bytes.Buffer) + w := bufio.NewWriter(buf) + + w.WriteString( + `{"start":` + strconv.FormatUint(d.RangeStart, 10) + + `,"end":` + strconv.FormatUint(d.RangeEnd, 10) + `}`, + ) + + w.Flush() + return buf.Bytes(), nil +} + +func (d Definition) Type() string { + return Type +} diff --git a/examples/gno.land/p/demo/agent/random/task/gno.mod b/examples/gno.land/p/demo/agent/random/task/gno.mod new file mode 100644 index 00000000000..946fd320649 --- /dev/null +++ b/examples/gno.land/p/demo/agent/random/task/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/agent/random/task diff --git a/examples/gno.land/p/demo/agent/tasks/continuous/gno.mod b/examples/gno.land/p/demo/agent/tasks/continuous/gno.mod new file mode 100644 index 00000000000..e305a9640c5 --- /dev/null +++ b/examples/gno.land/p/demo/agent/tasks/continuous/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/agent/tasks/continuous diff --git a/examples/gno.land/p/demo/agent/tasks/continuous/task.gno b/examples/gno.land/p/demo/agent/tasks/continuous/task.gno new file mode 100644 index 00000000000..3021c7f60d8 --- /dev/null +++ b/examples/gno.land/p/demo/agent/tasks/continuous/task.gno @@ -0,0 +1,124 @@ +package continuous + +import ( + "bufio" + "bytes" + "strconv" + "strings" + "time" + + "gno.land/p/demo/agent/aatask" + "gno.land/p/demo/avl" +) + +type Task struct { + id string + Era int + NextDue time.Time + Interval time.Duration + Aggregator aatask.Aggregator + definition aatask.Definition + RespondentWhiteList *avl.Tree + Respondents *avl.Tree + History aatask.History +} + +func (t Task) Definition() aatask.Definition { + return t.definition +} + +func (t *Task) Finish(origCaller string) { + now := time.Now() + if now.Before(t.NextDue) { + panic("era can not be transitioned until " + t.NextDue.String()) + } + + if t.RespondentWhiteList != nil { + if !t.RespondentWhiteList.Has(origCaller) { + panic("caller not in whitelist") + } + } + + // Handle the task state transitions. + t.Era++ + t.NextDue = now.Add(t.Interval) + t.Respondents = avl.NewTree() + + resultValue := t.Aggregator.Aggregate() + t.History.AddResult(aatask.Result{Value: resultValue, Time: now}) + + return +} + +func (t Task) GetResult() (result aatask.Result, hasResult bool) { + if len(t.History.Results) == 0 { + return + } + + return t.History.Results[len(t.History.Results)-1], true +} + +func (t Task) ID() string { + return t.id +} + +func (t Task) MarshalJSON() ([]byte, error) { + buf := new(bytes.Buffer) + w := bufio.NewWriter(buf) + w.WriteString( + `{"id":"` + t.id + + `","type":"` + t.definition.Type() + + `","era":` + strconv.Itoa(t.Era) + + `,"next_due":` + strconv.FormatInt(t.NextDue.Unix(), 10) + + `,"interval":` + strconv.FormatInt(int64(t.Interval/time.Second), 10) + + `,"definition":`, + ) + taskDefinitionBytes, err := t.definition.MarshalJSON() + if err != nil { + return nil, err + } + + w.Write(taskDefinitionBytes) + w.WriteString("}") + w.Flush() + return buf.Bytes(), nil +} + +func (t *Task) SubmitResult(origCaller, value string) { + var era int + valueParts := strings.SplitN(value, ",", 1) + if len(valueParts) != 2 { + panic("invalid result value; must be prefixed with era + ','") + } + + era, err := strconv.Atoi(valueParts[0]) + if err != nil { + panic(valueParts[0] + " is not a valid era") + } + + value = valueParts[1] + + // Check that the era is for the next expected result. + if era != t.Era { + panic("expected era of " + strconv.Itoa(t.Era) + ", got " + strconv.Itoa(era)) + } + + // Check that the window to write has not ended. + if time.Now().After(t.NextDue) { + panic("era " + strconv.Itoa(t.Era) + " has ended; call Finish to transition to the next era") + } + + if t.RespondentWhiteList != nil { + if !t.RespondentWhiteList.Has(origCaller) { + panic("caller not in whitelist") + } + } + + // Each agent can only respond once during each era. + if t.Respondents.Has(origCaller) { + panic("response already sent for this era") + } + + t.Aggregator.AddValue(value) + t.Respondents.Set(origCaller, struct{}{}) +} diff --git a/examples/gno.land/p/demo/agent/tasks/singular/gno.mod b/examples/gno.land/p/demo/agent/tasks/singular/gno.mod new file mode 100644 index 00000000000..18fe86643c2 --- /dev/null +++ b/examples/gno.land/p/demo/agent/tasks/singular/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/agent/tasks/singular diff --git a/examples/gno.land/p/demo/agent/tasks/singular/task.gno b/examples/gno.land/p/demo/agent/tasks/singular/task.gno new file mode 100644 index 00000000000..178097fac3e --- /dev/null +++ b/examples/gno.land/p/demo/agent/tasks/singular/task.gno @@ -0,0 +1,88 @@ +package singular + +import ( + "bufio" + "bytes" + "time" + + "gno.land/p/demo/agent/aatask" +) + +type Task struct { + id string + definition aatask.Definition + + isInactive bool + result *aatask.Result + authorizedRespondent string +} + +func NewTask(id string, definition aatask.Definition, authorizedRespondent string) *Task { + return &Task{ + id: id, + definition: definition, + authorizedRespondent: authorizedRespondent, + } +} + +func (t Task) Definition() aatask.Definition { + return t.definition +} + +func (t Task) Finish(_ string) { + panic("singular tasks are implicitly finished when a result is submitted") +} + +func (t Task) GetResult() (result aatask.Result, hasResult bool) { + if t.result == nil { + return + } + + return *t.result, true +} + +func (t Task) ID() string { + return t.id +} + +func (t Task) MarshalJSON() ([]byte, error) { + if !t.isInactive { + return nil, nil + } + + buf := new(bytes.Buffer) + w := bufio.NewWriter(buf) + w.WriteString( + `{"id":"` + t.id + + `","type":"` + t.definition.Type() + + `","definition":`, + ) + + taskDefinitionBytes, err := t.definition.MarshalJSON() + if err != nil { + return nil, err + } + + w.Write(taskDefinitionBytes) + w.WriteString("}") + w.Flush() + return buf.Bytes(), nil +} + +func (t *Task) SubmitResult(origCaller, value string) { + if t.isInactive { + panic("task is inactive") + } + + if t.authorizedRespondent != origCaller { + panic("caller not authorized to submit result") + } + + t.result = &aatask.Result{ + Value: value, + Time: time.Now(), + } + + t.isInactive = true + return +} diff --git a/examples/gno.land/r/gnoland/gh/contract.gno b/examples/gno.land/r/gnoland/gh/contract.gno new file mode 100644 index 00000000000..53bcdbe6b7c --- /dev/null +++ b/examples/gno.land/r/gnoland/gh/contract.gno @@ -0,0 +1,91 @@ +package gh + +import ( + "bufio" + "bytes" + "std" + + "gno.land/p/demo/agent/agent" + ghTask "gno.land/p/demo/agent/github/verification/task" + "gno.land/p/demo/agent/tasks/singular" + "gno.land/p/demo/avl" +) + +const ( + authorizedAgentAddress string = "" + verified = "OK" +) + +var ( + handleToAddress = avl.NewTree() + addressToHandle = avl.NewTree() + gnorkleInstance *agent.Instance +) + +func init() { + gnorkleInstance = agent.Init(std.GetOrigCaller()) +} + +func updateVerifiedGHData(function string, task agent.Task) { + if function != agent.FunctionSubmit { + return + } + + result, hasResult := task.GetResult() + if !hasResult { + return + } + + if result.Value != verified { + return + } + + definition, ok := task.Definition().(ghTask.Definition) + if !ok { + panic("unexpected task definition of type " + definition.Type()) + } + + handleToAddress.Set(definition.Handle, definition.Address) + addressToHandle.Set(definition.Address, definition.Handle) + + // It's been verified so clean it up. + gnorkleInstance.RemoveTask(task.ID()) +} + +func GnorkleAgentSubmitAction(payload string) string { + return gnorkleInstance.HandleRequest(payload, updateVerifiedGHData) +} + +func RequestVerification(handle, address string) { + if address == "" { + address = string(std.GetOrigCaller()) + } + + gnorkleInstance.AddTask( + singular.NewTask( + handle, + ghTask.Definition{Handle: handle, Address: address}, + authorizedAgentAddress, + ), + ) +} + +func Render(_ string) string { + buf := new(bytes.Buffer) + w := bufio.NewWriter(buf) + w.WriteString(`{"verified":{`) + first := true + handleToAddress.Iterate("", "", func(key string, value interface{}) bool { + if !first { + w.WriteString(",") + } + + w.WriteString(`"` + key + `":"` + value.(string) + `"`) + first = false + return true + }) + + w.WriteString(`}}`) + w.Flush() + return buf.String() +} diff --git a/examples/gno.land/r/gnoland/gh/gno.mod b/examples/gno.land/r/gnoland/gh/gno.mod new file mode 100644 index 00000000000..b5b87fd8459 --- /dev/null +++ b/examples/gno.land/r/gnoland/gh/gno.mod @@ -0,0 +1 @@ +module gno.land/r/gnoland/gh From a95f0ec11ced1f28a48fe31311f701c7fd9bf9cd Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 9 Jan 2024 16:36:05 -0800 Subject: [PATCH 06/48] docs --- examples/gno.land/r/gnoland/gh/contract.gno | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/examples/gno.land/r/gnoland/gh/contract.gno b/examples/gno.land/r/gnoland/gh/contract.gno index 53bcdbe6b7c..425de5bc329 100644 --- a/examples/gno.land/r/gnoland/gh/contract.gno +++ b/examples/gno.land/r/gnoland/gh/contract.gno @@ -12,6 +12,7 @@ import ( ) const ( + // TODO: set this to the address of the gnorkle agent that is authorized to submit verification tasks. authorizedAgentAddress string = "" verified = "OK" ) @@ -26,6 +27,9 @@ func init() { gnorkleInstance = agent.Init(std.GetOrigCaller()) } +// This is a callback function that is invoked at the end of the GnorkleAgentSubmitAction execution. +// It only cares about submissions because we want to save the result of the verification and clean +// up the finished task. func updateVerifiedGHData(function string, task agent.Task) { if function != agent.FunctionSubmit { return @@ -50,12 +54,21 @@ func updateVerifiedGHData(function string, task agent.Task) { // It's been verified so clean it up. gnorkleInstance.RemoveTask(task.ID()) + + // TODO: should it also clean up tasks that failed to verify? } +// This is called by the gnorkle agent. The payload contains a function and optional data. +// In the case of this contract, it should only be called with payloads of: +// - request (returns all verification tasks in the queue) +// - submit,, (submits a verification result for the given task id where data is "OK" or not) func GnorkleAgentSubmitAction(payload string) string { return gnorkleInstance.HandleRequest(payload, updateVerifiedGHData) } +// RequestVerification will request that the gnorkle agent verify the given handle/address pair. +// A new tasks is created to be processed by the agent and verify there is a github repo of +// github.com//gno-verification with a file "address.txt" that contains the given address. func RequestVerification(handle, address string) { if address == "" { address = string(std.GetOrigCaller()) @@ -70,6 +83,7 @@ func RequestVerification(handle, address string) { ) } +// Render will return all of the verified handle -> gno address pairs. func Render(_ string) string { buf := new(bytes.Buffer) w := bufio.NewWriter(buf) From c9f6b51fcdfb867c8fc846c8cf91c6b0f68baf35 Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 11 Jan 2024 14:30:35 -0800 Subject: [PATCH 07/48] oracle package bug fixes --- .../gno.land/p/demo/agent/agent/agent.gno | 31 +++++++++++++------ .../p/demo/agent/tasks/singular/task.gno | 2 +- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/examples/gno.land/p/demo/agent/agent/agent.gno b/examples/gno.land/p/demo/agent/agent/agent.gno index 5fe32465fb7..42bf5ba31b7 100644 --- a/examples/gno.land/p/demo/agent/agent/agent.gno +++ b/examples/gno.land/p/demo/agent/agent/agent.gno @@ -25,31 +25,39 @@ const ( type PostRequestAction func(function string, task Task) func (i *Instance) HandleRequest(payload string, post PostRequestAction) string { - payloadParts := strings.SplitN(payload, ",", 1) - if len(payloadParts) != 2 { - panic("invalid agent payload") + payloadParts := strings.SplitN(payload, ",", 2) + if len(payloadParts) == 0 { + panic("invalid empty agent payload") } switch function := payloadParts[0]; function { + case FunctionRequest: + return i.RequestTasks() case FunctionFinish: + if len(payloadParts) != 2 { + panic("invalid agent finish payload") + } + task := i.FinishTask(payloadParts[1]) if post != nil { post(function, task) } - case FunctionRequest: - return i.RequestTasks() case FunctionSubmit: - submitArgs := strings.SplitN(payloadParts[1], ",", 1) - if len(submitArgs) != 2 { + if len(payloadParts) != 2 { panic("invalid agent submission payload") } + submitArgs := strings.SplitN(payloadParts[1], ",", 2) + if len(submitArgs) != 2 { + panic("invalid agent submission args") + } + task := i.SubmitTaskValue(submitArgs[0], submitArgs[1]) if post != nil { post(function, task) } default: - panic("unknown function " + function) + panic("unknown agent payload function " + function) } return "" @@ -69,12 +77,17 @@ func (i *Instance) RequestTasks() string { buf := new(strings.Builder) buf.WriteString("[") first := true + i.tasks.Iterate("", "", func(_ string, value interface{}) bool { if !first { buf.WriteString(",") } - task := value.(Task) + task, ok := value.(Task) + if !ok { + panic("invalid task type") + } + taskBytes, err := task.MarshalJSON() if err != nil { panic(err) diff --git a/examples/gno.land/p/demo/agent/tasks/singular/task.gno b/examples/gno.land/p/demo/agent/tasks/singular/task.gno index 178097fac3e..69167aad553 100644 --- a/examples/gno.land/p/demo/agent/tasks/singular/task.gno +++ b/examples/gno.land/p/demo/agent/tasks/singular/task.gno @@ -46,7 +46,7 @@ func (t Task) ID() string { } func (t Task) MarshalJSON() ([]byte, error) { - if !t.isInactive { + if t.isInactive { return nil, nil } From 6f0c7ceb63d6afe1684bb240665229508e1a3852 Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 16 Jan 2024 20:34:12 -0800 Subject: [PATCH 08/48] prototype new version of the oracle package --- agent2/devmock/avl/node.go | 463 +++++++++++++++++++++ agent2/devmock/avl/tree.go | 82 ++++ agent2/devmock/go.mod | 1 + agent2/devmock/std/std.go | 7 + agent2/p/orkle/feed.go | 12 + agent2/p/orkle/feed/static/feed.go | 60 +++ agent2/p/orkle/feed/type.go | 9 + agent2/p/orkle/feed/value.go | 8 + agent2/p/orkle/ingester.go | 9 + agent2/p/orkle/ingester/single/ingester.go | 22 + agent2/p/orkle/ingester/type.go | 8 + agent2/p/orkle/message/parse.go | 19 + agent2/p/orkle/storage.go | 9 + agent2/p/orkle/storage/simple.go | 31 ++ 14 files changed, 740 insertions(+) create mode 100644 agent2/devmock/avl/node.go create mode 100644 agent2/devmock/avl/tree.go create mode 100644 agent2/devmock/go.mod create mode 100644 agent2/devmock/std/std.go create mode 100644 agent2/p/orkle/feed.go create mode 100644 agent2/p/orkle/feed/static/feed.go create mode 100644 agent2/p/orkle/feed/type.go create mode 100644 agent2/p/orkle/feed/value.go create mode 100644 agent2/p/orkle/ingester.go create mode 100644 agent2/p/orkle/ingester/single/ingester.go create mode 100644 agent2/p/orkle/ingester/type.go create mode 100644 agent2/p/orkle/message/parse.go create mode 100644 agent2/p/orkle/storage.go create mode 100644 agent2/p/orkle/storage/simple.go diff --git a/agent2/devmock/avl/node.go b/agent2/devmock/avl/node.go new file mode 100644 index 00000000000..a49dba00ae1 --- /dev/null +++ b/agent2/devmock/avl/node.go @@ -0,0 +1,463 @@ +package avl + +//---------------------------------------- +// Node + +type Node struct { + key string + value interface{} + height int8 + size int + leftNode *Node + rightNode *Node +} + +func NewNode(key string, value interface{}) *Node { + return &Node{ + key: key, + value: value, + height: 0, + size: 1, + } +} + +func (node *Node) Size() int { + if node == nil { + return 0 + } + return node.size +} + +func (node *Node) IsLeaf() bool { + return node.height == 0 +} + +func (node *Node) Key() string { + return node.key +} + +func (node *Node) Value() interface{} { + return node.value +} + +func (node *Node) _copy() *Node { + if node.height == 0 { + panic("Why are you copying a value node?") + } + return &Node{ + key: node.key, + height: node.height, + size: node.size, + leftNode: node.leftNode, + rightNode: node.rightNode, + } +} + +func (node *Node) Has(key string) (has bool) { + if node == nil { + return false + } + if node.key == key { + return true + } + if node.height == 0 { + return false + } else { + if key < node.key { + return node.getLeftNode().Has(key) + } else { + return node.getRightNode().Has(key) + } + } +} + +func (node *Node) Get(key string) (index int, value interface{}, exists bool) { + if node == nil { + return 0, nil, false + } + if node.height == 0 { + if node.key == key { + return 0, node.value, true + } else if node.key < key { + return 1, nil, false + } else { + return 0, nil, false + } + } else { + if key < node.key { + return node.getLeftNode().Get(key) + } else { + rightNode := node.getRightNode() + index, value, exists = rightNode.Get(key) + index += node.size - rightNode.size + return index, value, exists + } + } +} + +func (node *Node) GetByIndex(index int) (key string, value interface{}) { + if node.height == 0 { + if index == 0 { + return node.key, node.value + } else { + panic("GetByIndex asked for invalid index") + return "", nil + } + } else { + // TODO: could improve this by storing the sizes + leftNode := node.getLeftNode() + if index < leftNode.size { + return leftNode.GetByIndex(index) + } else { + return node.getRightNode().GetByIndex(index - leftNode.size) + } + } +} + +// XXX consider a better way to do this... perhaps split Node from Node. +func (node *Node) Set(key string, value interface{}) (newSelf *Node, updated bool) { + if node == nil { + return NewNode(key, value), false + } + if node.height == 0 { + if key < node.key { + return &Node{ + key: node.key, + height: 1, + size: 2, + leftNode: NewNode(key, value), + rightNode: node, + }, false + } else if key == node.key { + return NewNode(key, value), true + } else { + return &Node{ + key: key, + height: 1, + size: 2, + leftNode: node, + rightNode: NewNode(key, value), + }, false + } + } else { + node = node._copy() + if key < node.key { + node.leftNode, updated = node.getLeftNode().Set(key, value) + } else { + node.rightNode, updated = node.getRightNode().Set(key, value) + } + if updated { + return node, updated + } else { + node.calcHeightAndSize() + return node.balance(), updated + } + } +} + +// newNode: The new node to replace node after remove. +// newKey: new leftmost leaf key for node after successfully removing 'key' if changed. +// value: removed value. +func (node *Node) Remove(key string) ( + newNode *Node, newKey string, value interface{}, removed bool, +) { + if node == nil { + return nil, "", nil, false + } + if node.height == 0 { + if key == node.key { + return nil, "", node.value, true + } else { + return node, "", nil, false + } + } else { + if key < node.key { + var newLeftNode *Node + newLeftNode, newKey, value, removed = node.getLeftNode().Remove(key) + if !removed { + return node, "", value, false + } else if newLeftNode == nil { // left node held value, was removed + return node.rightNode, node.key, value, true + } + node = node._copy() + node.leftNode = newLeftNode + node.calcHeightAndSize() + node = node.balance() + return node, newKey, value, true + } else { + var newRightNode *Node + newRightNode, newKey, value, removed = node.getRightNode().Remove(key) + if !removed { + return node, "", value, false + } else if newRightNode == nil { // right node held value, was removed + return node.leftNode, "", value, true + } + node = node._copy() + node.rightNode = newRightNode + if newKey != "" { + node.key = newKey + } + node.calcHeightAndSize() + node = node.balance() + return node, "", value, true + } + } +} + +func (node *Node) getLeftNode() *Node { + return node.leftNode +} + +func (node *Node) getRightNode() *Node { + return node.rightNode +} + +// NOTE: overwrites node +// TODO: optimize balance & rotate +func (node *Node) rotateRight() *Node { + node = node._copy() + l := node.getLeftNode() + _l := l._copy() + + _lrCached := _l.rightNode + _l.rightNode = node + node.leftNode = _lrCached + + node.calcHeightAndSize() + _l.calcHeightAndSize() + + return _l +} + +// NOTE: overwrites node +// TODO: optimize balance & rotate +func (node *Node) rotateLeft() *Node { + node = node._copy() + r := node.getRightNode() + _r := r._copy() + + _rlCached := _r.leftNode + _r.leftNode = node + node.rightNode = _rlCached + + node.calcHeightAndSize() + _r.calcHeightAndSize() + + return _r +} + +// NOTE: mutates height and size +func (node *Node) calcHeightAndSize() { + node.height = maxInt8(node.getLeftNode().height, node.getRightNode().height) + 1 + node.size = node.getLeftNode().size + node.getRightNode().size +} + +func (node *Node) calcBalance() int { + return int(node.getLeftNode().height) - int(node.getRightNode().height) +} + +// NOTE: assumes that node can be modified +// TODO: optimize balance & rotate +func (node *Node) balance() (newSelf *Node) { + balance := node.calcBalance() + if balance > 1 { + if node.getLeftNode().calcBalance() >= 0 { + // Left Left Case + return node.rotateRight() + } else { + // Left Right Case + // node = node._copy() + left := node.getLeftNode() + node.leftNode = left.rotateLeft() + // node.calcHeightAndSize() + return node.rotateRight() + } + } + if balance < -1 { + if node.getRightNode().calcBalance() <= 0 { + // Right Right Case + return node.rotateLeft() + } else { + // Right Left Case + // node = node._copy() + right := node.getRightNode() + node.rightNode = right.rotateRight() + // node.calcHeightAndSize() + return node.rotateLeft() + } + } + // Nothing changed + return node +} + +// Shortcut for TraverseInRange. +func (node *Node) Iterate(start, end string, cb func(*Node) bool) bool { + return node.TraverseInRange(start, end, true, true, cb) +} + +// Shortcut for TraverseInRange. +func (node *Node) ReverseIterate(start, end string, cb func(*Node) bool) bool { + return node.TraverseInRange(start, end, false, true, cb) +} + +// TraverseInRange traverses all nodes, including inner nodes. +// Start is inclusive and end is exclusive when ascending, +// Start and end are inclusive when descending. +// Empty start and empty end denote no start and no end. +// If leavesOnly is true, only visit leaf nodes. +// NOTE: To simulate an exclusive reverse traversal, +// just append 0x00 to start. +func (node *Node) TraverseInRange(start, end string, ascending bool, leavesOnly bool, cb func(*Node) bool) bool { + if node == nil { + return false + } + afterStart := (start == "" || start < node.key) + startOrAfter := (start == "" || start <= node.key) + beforeEnd := false + if ascending { + beforeEnd = (end == "" || node.key < end) + } else { + beforeEnd = (end == "" || node.key <= end) + } + + // Run callback per inner/leaf node. + stop := false + if (!node.IsLeaf() && !leavesOnly) || + (node.IsLeaf() && startOrAfter && beforeEnd) { + stop = cb(node) + if stop { + return stop + } + } + if node.IsLeaf() { + return stop + } + + if ascending { + // check lower nodes, then higher + if afterStart { + stop = node.getLeftNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + if stop { + return stop + } + if beforeEnd { + stop = node.getRightNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + } else { + // check the higher nodes first + if beforeEnd { + stop = node.getRightNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + if stop { + return stop + } + if afterStart { + stop = node.getLeftNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + } + + return stop +} + +// TraverseByOffset traverses all nodes, including inner nodes. +// A limit of math.MaxInt means no limit. +func (node *Node) TraverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool { + if node == nil { + return false + } + + // fast paths. these happen only if TraverseByOffset is called directly on a leaf. + if limit <= 0 || offset >= node.size { + return false + } + if node.IsLeaf() { + if offset > 0 { + return false + } + return cb(node) + } + + // go to the actual recursive function. + return node.traverseByOffset(offset, limit, descending, leavesOnly, cb) +} + +func (node *Node) traverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool { + // caller guarantees: offset < node.size; limit > 0. + + if !leavesOnly { + if cb(node) { + return true + } + } + first, second := node.getLeftNode(), node.getRightNode() + if descending { + first, second = second, first + } + if first.IsLeaf() { + // either run or skip, based on offset + if offset > 0 { + offset-- + } else { + cb(first) + limit-- + if limit <= 0 { + return false + } + } + } else { + // possible cases: + // 1 the offset given skips the first node entirely + // 2 the offset skips none or part of the first node, but the limit requires some of the second node. + // 3 the offset skips none or part of the first node, and the limit stops our search on the first node. + if offset >= first.size { + offset -= first.size // 1 + } else { + if first.traverseByOffset(offset, limit, descending, leavesOnly, cb) { + return true + } + // number of leaves which could actually be called from inside + delta := first.size - offset + offset = 0 + if delta >= limit { + return true // 3 + } + limit -= delta // 2 + } + } + + // because of the caller guarantees and the way we handle the first node, + // at this point we know that limit > 0 and there must be some values in + // this second node that we include. + + // => if the second node is a leaf, it has to be included. + if second.IsLeaf() { + return cb(second) + } + // => if it is not a leaf, it will still be enough to recursively call this + // function with the updated offset and limit + return second.traverseByOffset(offset, limit, descending, leavesOnly, cb) +} + +// Only used in testing... +func (node *Node) lmd() *Node { + if node.height == 0 { + return node + } + return node.getLeftNode().lmd() +} + +// Only used in testing... +func (node *Node) rmd() *Node { + if node.height == 0 { + return node + } + return node.getRightNode().rmd() +} + +func maxInt8(a, b int8) int8 { + if a > b { + return a + } + return b +} diff --git a/agent2/devmock/avl/tree.go b/agent2/devmock/avl/tree.go new file mode 100644 index 00000000000..7b33d28fbe3 --- /dev/null +++ b/agent2/devmock/avl/tree.go @@ -0,0 +1,82 @@ +package avl + +type IterCbFn func(key string, value interface{}) bool + +//---------------------------------------- +// Tree + +// The zero struct can be used as an empty tree. +type Tree struct { + node *Node +} + +func NewTree() *Tree { + return &Tree{ + node: nil, + } +} + +func (tree *Tree) Size() int { + return tree.node.Size() +} + +func (tree *Tree) Has(key string) (has bool) { + return tree.node.Has(key) +} + +func (tree *Tree) Get(key string) (value interface{}, exists bool) { + _, value, exists = tree.node.Get(key) + return +} + +func (tree *Tree) GetByIndex(index int) (key string, value interface{}) { + return tree.node.GetByIndex(index) +} + +func (tree *Tree) Set(key string, value interface{}) (updated bool) { + newnode, updated := tree.node.Set(key, value) + tree.node = newnode + return updated +} + +func (tree *Tree) Remove(key string) (value interface{}, removed bool) { + newnode, _, value, removed := tree.node.Remove(key) + tree.node = newnode + return value, removed +} + +// Shortcut for TraverseInRange. +func (tree *Tree) Iterate(start, end string, cb IterCbFn) bool { + return tree.node.TraverseInRange(start, end, true, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} + +// Shortcut for TraverseInRange. +func (tree *Tree) ReverseIterate(start, end string, cb IterCbFn) bool { + return tree.node.TraverseInRange(start, end, false, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} + +// Shortcut for TraverseByOffset. +func (tree *Tree) IterateByOffset(offset int, count int, cb IterCbFn) bool { + return tree.node.TraverseByOffset(offset, count, true, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} + +// Shortcut for TraverseByOffset. +func (tree *Tree) ReverseIterateByOffset(offset int, count int, cb IterCbFn) bool { + return tree.node.TraverseByOffset(offset, count, false, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} diff --git a/agent2/devmock/go.mod b/agent2/devmock/go.mod new file mode 100644 index 00000000000..6c7ce04c7da --- /dev/null +++ b/agent2/devmock/go.mod @@ -0,0 +1 @@ +package devmock \ No newline at end of file diff --git a/agent2/devmock/std/std.go b/agent2/devmock/std/std.go new file mode 100644 index 00000000000..06cdd59952f --- /dev/null +++ b/agent2/devmock/std/std.go @@ -0,0 +1,7 @@ +package std + +type Address string + +func GetOrigCaller() Address { + return "" +} diff --git a/agent2/p/orkle/feed.go b/agent2/p/orkle/feed.go new file mode 100644 index 00000000000..a407b349d47 --- /dev/null +++ b/agent2/p/orkle/feed.go @@ -0,0 +1,12 @@ +package orkle + +import "github.com/gnolang/gno/agent2/p/orkle/feed" + +type Feed interface { + ID() string // necessary? + Type() feed.Type + Value() (value feed.Value, dataType string) + Ingest(rawMessage, providerAddress string) + Consumable() bool + MarshalJSON() ([]byte, error) +} diff --git a/agent2/p/orkle/feed/static/feed.go b/agent2/p/orkle/feed/static/feed.go new file mode 100644 index 00000000000..27efeec168d --- /dev/null +++ b/agent2/p/orkle/feed/static/feed.go @@ -0,0 +1,60 @@ +package static + +import ( + "github.com/gnolang/gno/agent2/p/orkle" + "github.com/gnolang/gno/agent2/p/orkle/feed" + "github.com/gnolang/gno/agent2/p/orkle/message" + "gno.land/p/demo/std" +) + +type Feed struct { + id string + isLocked bool + valueDataType string + ingester orkle.Ingester + storage orkle.Storage +} + +func (f *Feed) ID() string { + return f.id +} + +func (f *Feed) Type() feed.Type { + return feed.TypeStatic +} + +func (f *Feed) Ingest(msg string) { + if f.isLocked { + panic("feed locked") + } + + origCaller := string(std.GetOrigCaller()) + + msgFunc, msg := message.ParseFunc(msg) + switch msgFunc { + case message.FuncTypeIngest: + canAutoCommit := f.ingester.Ingest(msg, origCaller) + + // Autocommit the ingester's value if it's a single value ingester + // because this is a static feed and this is the only value it will ever have. + if canAutoCommit { + f.ingester.CommitValue(f.storage, origCaller) + f.isLocked = true + } + + case message.FuncTypeCommit: + f.ingester.CommitValue(f.storage, origCaller) + f.isLocked = true + + default: + panic("invalid message function " + string(msgFunc)) + } +} + +func (f *Feed) Consumable() bool { + return f.isLocked +} + +func (f *Feed) Value() (feed.Value, string) { + return f.storage.GetLatest(), f.valueDataType +} diff --git a/agent2/p/orkle/feed/type.go b/agent2/p/orkle/feed/type.go new file mode 100644 index 00000000000..aa6cd518a86 --- /dev/null +++ b/agent2/p/orkle/feed/type.go @@ -0,0 +1,9 @@ +package feed + +type Type int + +const ( + TypeStatic Type = iota + TypeContinuous + TypePeriodic +) diff --git a/agent2/p/orkle/feed/value.go b/agent2/p/orkle/feed/value.go new file mode 100644 index 00000000000..66e6b2fcef0 --- /dev/null +++ b/agent2/p/orkle/feed/value.go @@ -0,0 +1,8 @@ +package feed + +import "time" + +type Value struct { + String string + Time time.Time +} diff --git a/agent2/p/orkle/ingester.go b/agent2/p/orkle/ingester.go new file mode 100644 index 00000000000..3b36ef4a60e --- /dev/null +++ b/agent2/p/orkle/ingester.go @@ -0,0 +1,9 @@ +package orkle + +import "github.com/gnolang/gno/agent2/p/orkle/ingester" + +type Ingester interface { + Type() ingester.Type + Ingest(value, providerAddress string) (canAutoCommit bool) + CommitValue(storage Storage, providerAddress string) +} diff --git a/agent2/p/orkle/ingester/single/ingester.go b/agent2/p/orkle/ingester/single/ingester.go new file mode 100644 index 00000000000..095050f6aab --- /dev/null +++ b/agent2/p/orkle/ingester/single/ingester.go @@ -0,0 +1,22 @@ +package single + +import ( + "github.com/gnolang/gno/agent2/p/orkle" + "github.com/gnolang/gno/agent2/p/orkle/ingester" +) + +type SingleValueIngester struct { + value string +} + +func (i *SingleValueIngester) Type() ingester.Type { + return ingester.TypeSingle +} + +func (i *SingleValueIngester) Ingest(value, providerAddress string) { + i.value = value +} + +func (i *SingleValueIngester) CommitValue(valueStorer orkle.Storage, providerAddress string) { + valueStorer.Put(i.value) +} diff --git a/agent2/p/orkle/ingester/type.go b/agent2/p/orkle/ingester/type.go new file mode 100644 index 00000000000..454b46be3fe --- /dev/null +++ b/agent2/p/orkle/ingester/type.go @@ -0,0 +1,8 @@ +package ingester + +type Type int + +const ( + TypeSingle Type = iota + IngesterTypeMulti +) diff --git a/agent2/p/orkle/message/parse.go b/agent2/p/orkle/message/parse.go new file mode 100644 index 00000000000..818ec802434 --- /dev/null +++ b/agent2/p/orkle/message/parse.go @@ -0,0 +1,19 @@ +package message + +import "strings" + +type FuncType string + +const ( + FuncTypeIngest FuncType = "ingest" + FuncTypeCommit FuncType = "commit" +) + +func ParseFunc(rawMsg string) (FuncType, string) { + msgParts := strings.Split(rawMsg, ",") + if len(msgParts) < 2 { + return FuncType(msgParts[0]), "" + } + + return FuncType(msgParts[0]), msgParts[1] +} diff --git a/agent2/p/orkle/storage.go b/agent2/p/orkle/storage.go new file mode 100644 index 00000000000..258128158f6 --- /dev/null +++ b/agent2/p/orkle/storage.go @@ -0,0 +1,9 @@ +package orkle + +import "github.com/gnolang/gno/agent2/p/orkle/feed" + +type Storage interface { + Put(value string) + GetLatest() feed.Value + GetHistory() []feed.Value +} diff --git a/agent2/p/orkle/storage/simple.go b/agent2/p/orkle/storage/simple.go new file mode 100644 index 00000000000..4e71dd53f02 --- /dev/null +++ b/agent2/p/orkle/storage/simple.go @@ -0,0 +1,31 @@ +package storage + +import ( + "time" + + "github.com/gnolang/gno/agent2/p/orkle/feed" +) + +type Simple struct { + values []feed.Value + maxValues int +} + +func (s *Simple) Put(value string) { + s.values = append(s.values, feed.Value{String: value, Time: time.Now()}) + if len(s.values) > s.maxValues { + s.values = s.values[1:] + } +} + +func (s *Simple) GetLatest() feed.Value { + if len(s.values) == 0 { + return feed.Value{} + } + + return s.values[len(s.values)-1] +} + +func (s *Simple) GetHistory() []feed.Value { + return s.values +} From 393d4184f86c2b422ea268eb64a8dc477e6f7908 Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 17 Jan 2024 20:09:44 -0800 Subject: [PATCH 09/48] more work on v2 orkle prototype --- agent2/p/orkle/feed.go | 10 +-- agent2/p/orkle/feed/static/feed.go | 26 +++----- agent2/p/orkle/ingester/type.go | 2 +- agent2/p/orkle/instance.go | 101 +++++++++++++++++++++++++++++ agent2/p/orkle/message/parse.go | 16 ++++- 5 files changed, 129 insertions(+), 26 deletions(-) create mode 100644 agent2/p/orkle/instance.go diff --git a/agent2/p/orkle/feed.go b/agent2/p/orkle/feed.go index a407b349d47..9fef6f2d0cc 100644 --- a/agent2/p/orkle/feed.go +++ b/agent2/p/orkle/feed.go @@ -1,12 +1,14 @@ package orkle -import "github.com/gnolang/gno/agent2/p/orkle/feed" +import ( + "github.com/gnolang/gno/agent2/p/orkle/feed" + "github.com/gnolang/gno/agent2/p/orkle/message" +) type Feed interface { ID() string // necessary? Type() feed.Type - Value() (value feed.Value, dataType string) - Ingest(rawMessage, providerAddress string) - Consumable() bool + Value() (value feed.Value, dataType string, consumable bool) + Ingest(funcType message.FuncType, rawMessage, providerAddress string) MarshalJSON() ([]byte, error) } diff --git a/agent2/p/orkle/feed/static/feed.go b/agent2/p/orkle/feed/static/feed.go index 27efeec168d..0f7a091c3c3 100644 --- a/agent2/p/orkle/feed/static/feed.go +++ b/agent2/p/orkle/feed/static/feed.go @@ -4,7 +4,6 @@ import ( "github.com/gnolang/gno/agent2/p/orkle" "github.com/gnolang/gno/agent2/p/orkle/feed" "github.com/gnolang/gno/agent2/p/orkle/message" - "gno.land/p/demo/std" ) type Feed struct { @@ -23,38 +22,29 @@ func (f *Feed) Type() feed.Type { return feed.TypeStatic } -func (f *Feed) Ingest(msg string) { +func (f *Feed) Ingest(funcType message.FuncType, msg, providerAddress string) { if f.isLocked { panic("feed locked") } - origCaller := string(std.GetOrigCaller()) - - msgFunc, msg := message.ParseFunc(msg) - switch msgFunc { + switch funcType { case message.FuncTypeIngest: - canAutoCommit := f.ingester.Ingest(msg, origCaller) - // Autocommit the ingester's value if it's a single value ingester // because this is a static feed and this is the only value it will ever have. - if canAutoCommit { - f.ingester.CommitValue(f.storage, origCaller) + if canAutoCommit := f.ingester.Ingest(msg, providerAddress); canAutoCommit { + f.ingester.CommitValue(f.storage, providerAddress) f.isLocked = true } case message.FuncTypeCommit: - f.ingester.CommitValue(f.storage, origCaller) + f.ingester.CommitValue(f.storage, providerAddress) f.isLocked = true default: - panic("invalid message function " + string(msgFunc)) + panic("invalid message function " + string(funcType)) } } -func (f *Feed) Consumable() bool { - return f.isLocked -} - -func (f *Feed) Value() (feed.Value, string) { - return f.storage.GetLatest(), f.valueDataType +func (f *Feed) Value() (feed.Value, string, bool) { + return f.storage.GetLatest(), f.valueDataType, f.isLocked } diff --git a/agent2/p/orkle/ingester/type.go b/agent2/p/orkle/ingester/type.go index 454b46be3fe..abc48eb1f32 100644 --- a/agent2/p/orkle/ingester/type.go +++ b/agent2/p/orkle/ingester/type.go @@ -4,5 +4,5 @@ type Type int const ( TypeSingle Type = iota - IngesterTypeMulti + TypeMulti ) diff --git a/agent2/p/orkle/instance.go b/agent2/p/orkle/instance.go new file mode 100644 index 00000000000..b122520b9ee --- /dev/null +++ b/agent2/p/orkle/instance.go @@ -0,0 +1,101 @@ +package orkle + +import ( + "strings" + + "github.com/gnolang/gno/agent2/p/orkle/feed" + "github.com/gnolang/gno/agent2/p/orkle/message" + "gno.land/p/demo/avl" + "gno.land/p/demo/std" +) + +type Instance struct { + feeds *avl.Tree + whitelist *avl.Tree + ownerAddress string +} + +type PostMessageHandler interface { + Handle(i *Instance, funcType message.FuncType, feed Feed) +} + +func (i *Instance) HandleMessage(msg string, postHandler PostMessageHandler) string { + caller := string(std.GetOrigCaller()) + if i.whitelist != nil { + // Check that the caller is whitelisted. + if _, ok := i.whitelist.Get(caller); !ok { + panic("caller not whitelisted") + } + } + + funcType, msg := message.ParseFunc(msg) + + switch funcType { + case message.FuncTypeRequest: + return i.RequestTasks() + + default: + id, msg := message.ParseID(msg) + feed := i.getFeed(id) + + feed.Ingest(funcType, msg, caller) + + if postHandler != nil { + postHandler.Handle(i, funcType, feed) + } + } + + return "" +} + +func (i *Instance) getFeed(id string) Feed { + untypedFeed, ok := i.feeds.Get(id) + if !ok { + panic("invalid ingest id: " + id) + } + + feed, ok := untypedFeed.(Feed) + if !ok { + panic("invalid feed type") + } + + return feed +} + +func (i *Instance) GetFeedValue(id string) (feed.Value, string, bool) { + return i.getFeed(id).Value() +} + +func (i *Instance) RequestTasks() string { + buf := new(strings.Builder) + buf.WriteString("[") + first := true + + i.feeds.Iterate("", "", func(_ string, value interface{}) bool { + if !first { + buf.WriteString(",") + } + + task, ok := value.(Feed) + if !ok { + panic("invalid task type") + } + + taskBytes, err := task.MarshalJSON() + if err != nil { + panic(err) + } + + // Guard against any tasks that shouldn't be returned; maybe they are not active because they have + // already been completed. + if len(taskBytes) == 0 { + return true + } + + first = false + buf.Write(taskBytes) + return true + }) + buf.WriteString("]") + return buf.String() +} diff --git a/agent2/p/orkle/message/parse.go b/agent2/p/orkle/message/parse.go index 818ec802434..ec54caf03ab 100644 --- a/agent2/p/orkle/message/parse.go +++ b/agent2/p/orkle/message/parse.go @@ -5,15 +5,25 @@ import "strings" type FuncType string const ( - FuncTypeIngest FuncType = "ingest" - FuncTypeCommit FuncType = "commit" + FuncTypeIngest FuncType = "ingest" + FuncTypeCommit FuncType = "commit" + FuncTypeRequest FuncType = "request" ) func ParseFunc(rawMsg string) (FuncType, string) { - msgParts := strings.Split(rawMsg, ",") + msgParts := strings.SplitN(rawMsg, ",", 2) if len(msgParts) < 2 { return FuncType(msgParts[0]), "" } return FuncType(msgParts[0]), msgParts[1] } + +func ParseID(rawMsg string) (string, string) { + msgParts := strings.SplitN(rawMsg, ",", 2) + if len(msgParts) < 2 { + return msgParts[0], "" + } + + return msgParts[0], msgParts[1] +} From ed864e375596c78d83613e11281878e6a4fba4a1 Mon Sep 17 00:00:00 2001 From: deelawn Date: Fri, 19 Jan 2024 14:19:44 -0800 Subject: [PATCH 10/48] implemented gh verify oracle using new model --- agent2/p/orkle/feed.go | 2 + agent2/p/orkle/feed/static/feed.go | 94 ++++++++++++++++++++++ agent2/p/orkle/feed/task.go | 5 ++ agent2/p/orkle/feed/tasks/ghverify/task.go | 38 +++++++++ agent2/p/orkle/ingester/single/ingester.go | 9 ++- agent2/p/orkle/instance.go | 88 ++++++++++++++++---- agent2/p/orkle/storage/simple.go | 8 +- agent2/r/gh/contract.go | 75 +++++++++++++++++ 8 files changed, 300 insertions(+), 19 deletions(-) create mode 100644 agent2/p/orkle/feed/task.go create mode 100644 agent2/p/orkle/feed/tasks/ghverify/task.go create mode 100644 agent2/r/gh/contract.go diff --git a/agent2/p/orkle/feed.go b/agent2/p/orkle/feed.go index 9fef6f2d0cc..97c745d687f 100644 --- a/agent2/p/orkle/feed.go +++ b/agent2/p/orkle/feed.go @@ -11,4 +11,6 @@ type Feed interface { Value() (value feed.Value, dataType string, consumable bool) Ingest(funcType message.FuncType, rawMessage, providerAddress string) MarshalJSON() ([]byte, error) + HasAddressWhitelisted(address string) (isWhitelisted, feedHasWhitelist bool) + Tasks() []feed.Task } diff --git a/agent2/p/orkle/feed/static/feed.go b/agent2/p/orkle/feed/static/feed.go index 0f7a091c3c3..f148ccd8f4c 100644 --- a/agent2/p/orkle/feed/static/feed.go +++ b/agent2/p/orkle/feed/static/feed.go @@ -1,17 +1,68 @@ package static import ( + "bufio" + "bytes" + ufmt "fmt" + "github.com/gnolang/gno/agent2/p/orkle" "github.com/gnolang/gno/agent2/p/orkle/feed" + "github.com/gnolang/gno/agent2/p/orkle/ingester/single" "github.com/gnolang/gno/agent2/p/orkle/message" + "github.com/gnolang/gno/agent2/p/orkle/storage" + "gno.land/p/demo/avl" ) type Feed struct { id string isLocked bool valueDataType string + whitelist *avl.Tree ingester orkle.Ingester storage orkle.Storage + tasks []feed.Task +} + +func NewFeed( + id string, + valueDataType string, + whitelist []string, + ingester orkle.Ingester, + storage orkle.Storage, + tasks ...feed.Task, +) *Feed { + feed := &Feed{ + id: id, + valueDataType: valueDataType, + ingester: ingester, + storage: storage, + tasks: tasks, + } + + if len(whitelist) != 0 { + feed.whitelist = avl.NewTree() + for _, address := range whitelist { + feed.whitelist.Set(address, struct{}{}) + } + } + + return feed +} + +func NewSingleValueFeed( + id string, + valueDataType string, + whitelist []string, + tasks ...feed.Task, +) *Feed { + return NewFeed( + id, + valueDataType, + whitelist, + &single.ValueIngester{}, + storage.NewSimple(1), + tasks..., + ) } func (f *Feed) ID() string { @@ -48,3 +99,46 @@ func (f *Feed) Ingest(funcType message.FuncType, msg, providerAddress string) { func (f *Feed) Value() (feed.Value, string, bool) { return f.storage.GetLatest(), f.valueDataType, f.isLocked } + +func (f *Feed) MarshalJSON() ([]byte, error) { + buf := new(bytes.Buffer) + w := bufio.NewWriter(buf) + + w.Write([]byte( + `{"id":"` + f.id + + `","type":"` + ufmt.Sprintf("%d", f.Type()) + + `","value_type":"` + f.valueDataType + + `","tasks":[`), + ) + + first := true + for _, task := range f.tasks { + if !first { + w.WriteString(",") + } + + taskJSON, err := task.MarshalToJSON() + if err != nil { + return nil, err + } + + w.Write(taskJSON) + } + + w.Write([]byte("]}")) + w.Flush() + + return buf.Bytes(), nil +} + +func (f *Feed) HasAddressWhitelisted(address string) (isWhitelisted, feedHasWhitelist bool) { + if f.whitelist == nil { + return true, false + } + + return f.whitelist.Has(address), true +} + +func (f *Feed) Tasks() []feed.Task { + return f.tasks +} diff --git a/agent2/p/orkle/feed/task.go b/agent2/p/orkle/feed/task.go new file mode 100644 index 00000000000..e47cd3fca35 --- /dev/null +++ b/agent2/p/orkle/feed/task.go @@ -0,0 +1,5 @@ +package feed + +type Task interface { + MarshalToJSON() ([]byte, error) +} diff --git a/agent2/p/orkle/feed/tasks/ghverify/task.go b/agent2/p/orkle/feed/tasks/ghverify/task.go new file mode 100644 index 00000000000..157c3b6dee2 --- /dev/null +++ b/agent2/p/orkle/feed/tasks/ghverify/task.go @@ -0,0 +1,38 @@ +package ghverify + +import ( + "bufio" + "bytes" +) + +type Task struct { + gnoAddress string + githubHandle string +} + +func NewTask(gnoAddress, githubHandle string) *Task { + return &Task{ + gnoAddress: gnoAddress, + githubHandle: githubHandle, + } +} + +func (t *Task) MarshalToJSON() ([]byte, error) { + buf := new(bytes.Buffer) + w := bufio.NewWriter(buf) + + w.Write( + []byte(`{"gnoAddress":"` + t.gnoAddress + `","githubHandle":"` + t.githubHandle + `"}`), + ) + + w.Flush() + return buf.Bytes(), nil +} + +func (t *Task) GnoAddress() string { + return t.gnoAddress +} + +func (t *Task) GithubHandle() string { + return t.githubHandle +} diff --git a/agent2/p/orkle/ingester/single/ingester.go b/agent2/p/orkle/ingester/single/ingester.go index 095050f6aab..86763c83164 100644 --- a/agent2/p/orkle/ingester/single/ingester.go +++ b/agent2/p/orkle/ingester/single/ingester.go @@ -5,18 +5,19 @@ import ( "github.com/gnolang/gno/agent2/p/orkle/ingester" ) -type SingleValueIngester struct { +type ValueIngester struct { value string } -func (i *SingleValueIngester) Type() ingester.Type { +func (i *ValueIngester) Type() ingester.Type { return ingester.TypeSingle } -func (i *SingleValueIngester) Ingest(value, providerAddress string) { +func (i *ValueIngester) Ingest(value, providerAddress string) bool { i.value = value + return true } -func (i *SingleValueIngester) CommitValue(valueStorer orkle.Storage, providerAddress string) { +func (i *ValueIngester) CommitValue(valueStorer orkle.Storage, providerAddress string) { valueStorer.Put(i.value) } diff --git a/agent2/p/orkle/instance.go b/agent2/p/orkle/instance.go index b122520b9ee..62287bac85e 100644 --- a/agent2/p/orkle/instance.go +++ b/agent2/p/orkle/instance.go @@ -15,29 +15,55 @@ type Instance struct { ownerAddress string } +func NewInstance(ownerAddress string) *Instance { + return &Instance{ + ownerAddress: ownerAddress, + } +} + +func (i *Instance) WithWhitelist(addresses ...string) *Instance { + i.whitelist = avl.NewTree() + for _, address := range addresses { + i.whitelist.Set(address, struct{}{}) + } + return i +} + +func (i *Instance) WithFeeds(feeds ...Feed) *Instance { + i.feeds = avl.NewTree() + for _, feed := range feeds { + i.feeds.Set(feed.ID(), feed) + } + return i +} + +func (i *Instance) AddFeeds(feeds ...Feed) { + for _, feed := range feeds { + i.feeds.Set(feed.ID(), feed) + } +} + type PostMessageHandler interface { Handle(i *Instance, funcType message.FuncType, feed Feed) } func (i *Instance) HandleMessage(msg string, postHandler PostMessageHandler) string { caller := string(std.GetOrigCaller()) - if i.whitelist != nil { - // Check that the caller is whitelisted. - if _, ok := i.whitelist.Get(caller); !ok { - panic("caller not whitelisted") - } - } funcType, msg := message.ParseFunc(msg) switch funcType { case message.FuncTypeRequest: - return i.RequestTasks() + return i.GetFeedDefinitions(caller) default: id, msg := message.ParseID(msg) feed := i.getFeed(id) + if !addressWhitelisted(i.hasAddressWhitelisted(caller), caller, feed) { + panic("caller not whitelisted") + } + feed.Ingest(funcType, msg, caller) if postHandler != nil { @@ -48,6 +74,33 @@ func (i *Instance) HandleMessage(msg string, postHandler PostMessageHandler) str return "" } +func (i *Instance) RemoveFeed(id string) { + i.feeds.Remove(id) +} + +// TODO: test this. + +// addressWhiteListed returns true if: +// - the feed has a white list and the address is whitelisted, or +// - the feed has no white list and the instance has a white list and the address is whitelisted, or +// - the feed has no white list and the instance has no white list. +func addressWhitelisted(isInstanceWhitelisted bool, address string, feed Feed) bool { + // A feed whitelist takes priority, so it will return false if the feed has a whitelist and the caller is + // not a part of it. An empty whitelist defers to the instance whitelist. + var isWhitelisted, hasWhitelist bool + if isWhitelisted, hasWhitelist = feed.HasAddressWhitelisted(address); !isWhitelisted && hasWhitelist { + return false + } + + return (isWhitelisted && hasWhitelist) || isInstanceWhitelisted +} + +// hasAddressWhitelisted returns true if the address is whitelisted for the instance or if the instance has +// no whitelist. +func (i *Instance) hasAddressWhitelisted(address string) bool { + return i.whitelist == nil || i.whitelist.Has(address) +} + func (i *Instance) getFeed(id string) Feed { untypedFeed, ok := i.feeds.Get(id) if !ok { @@ -66,22 +119,25 @@ func (i *Instance) GetFeedValue(id string) (feed.Value, string, bool) { return i.getFeed(id).Value() } -func (i *Instance) RequestTasks() string { +func (i *Instance) GetFeedDefinitions(forAddress string) string { + instanceHasAddressWhitelisted := i.hasAddressWhitelisted(forAddress) + buf := new(strings.Builder) buf.WriteString("[") first := true i.feeds.Iterate("", "", func(_ string, value interface{}) bool { - if !first { - buf.WriteString(",") - } - - task, ok := value.(Feed) + feed, ok := value.(Feed) if !ok { panic("invalid task type") } - taskBytes, err := task.MarshalJSON() + // Skip feeds the address is not whitelisted for. + if !addressWhitelisted(instanceHasAddressWhitelisted, forAddress, feed) { + return true + } + + taskBytes, err := feed.MarshalJSON() if err != nil { panic(err) } @@ -92,6 +148,10 @@ func (i *Instance) RequestTasks() string { return true } + if !first { + buf.WriteString(",") + } + first = false buf.Write(taskBytes) return true diff --git a/agent2/p/orkle/storage/simple.go b/agent2/p/orkle/storage/simple.go index 4e71dd53f02..77cb80a43e5 100644 --- a/agent2/p/orkle/storage/simple.go +++ b/agent2/p/orkle/storage/simple.go @@ -11,9 +11,15 @@ type Simple struct { maxValues int } +func NewSimple(maxValues int) *Simple { + return &Simple{ + maxValues: maxValues, + } +} + func (s *Simple) Put(value string) { s.values = append(s.values, feed.Value{String: value, Time: time.Now()}) - if len(s.values) > s.maxValues { + if len(s.values) > s.maxValues && !(len(s.values) == 1 && s.maxValues == 0) { s.values = s.values[1:] } } diff --git a/agent2/r/gh/contract.go b/agent2/r/gh/contract.go new file mode 100644 index 00000000000..eb5fb4e568d --- /dev/null +++ b/agent2/r/gh/contract.go @@ -0,0 +1,75 @@ +package gh + +import ( + "github.com/gnolang/gno/agent2/p/orkle" + "github.com/gnolang/gno/agent2/p/orkle/feed/static" + "github.com/gnolang/gno/agent2/p/orkle/feed/tasks/ghverify" + "github.com/gnolang/gno/agent2/p/orkle/message" + "gno.land/p/demo/avl" + "gno.land/p/demo/std" +) + +const ( + verifiedResult = "OK" + whitelistedAgentAddress = "..." +) + +var ( + oracle orkle.Instance + postHandler postOrkleMessageHandler + + handleToAddressMap = avl.NewTree() + addressToHandleMap = avl.NewTree() +) + +type postOrkleMessageHandler struct{} + +func (h postOrkleMessageHandler) Handle(i *orkle.Instance, funcType message.FuncType, feed orkle.Feed) { + if funcType != message.FuncTypeIngest { + return + } + + result, _, consumable := feed.Value() + if !consumable { + return + } + + defer oracle.RemoveFeed(feed.ID()) + + if result.String != verifiedResult { + return + } + + feedTasks := feed.Tasks() + if len(feedTasks) != 1 { + panic("expected feed to have exactly one task") + } + + task, ok := feedTasks[0].(*ghverify.Task) + if !ok { + panic("expected ghverify task") + } + + handleToAddressMap.Set(task.GithubHandle(), task.GnoAddress()) + addressToHandleMap.Set(task.GnoAddress(), task.GithubHandle()) +} + +func init() { + oracle = *orkle.NewInstance(string(std.GetOrigCaller())). + WithWhitelist(whitelistedAgentAddress) +} + +func RequestVerification(githubHandle string) { + oracle.AddFeeds( + static.NewSingleValueFeed( + githubHandle, + "string", + nil, + ghverify.NewTask(string(std.GetOrigCaller()), githubHandle), + ), + ) +} + +func OrkleEntrypoint(message string) string { + return oracle.HandleMessage(message, postHandler) +} From c5dcc10d4d11a1b26c6b9bc4abf2fa6c9f9fe42d Mon Sep 17 00:00:00 2001 From: deelawn Date: Fri, 19 Jan 2024 14:23:05 -0800 Subject: [PATCH 11/48] exclude inactive feeds from agent requests --- agent2/p/orkle/feed.go | 1 + agent2/p/orkle/feed/static/feed.go | 4 ++++ agent2/p/orkle/instance.go | 7 ++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/agent2/p/orkle/feed.go b/agent2/p/orkle/feed.go index 97c745d687f..bab0fd270ba 100644 --- a/agent2/p/orkle/feed.go +++ b/agent2/p/orkle/feed.go @@ -13,4 +13,5 @@ type Feed interface { MarshalJSON() ([]byte, error) HasAddressWhitelisted(address string) (isWhitelisted, feedHasWhitelist bool) Tasks() []feed.Task + IsActive() bool } diff --git a/agent2/p/orkle/feed/static/feed.go b/agent2/p/orkle/feed/static/feed.go index f148ccd8f4c..22e0b334a3c 100644 --- a/agent2/p/orkle/feed/static/feed.go +++ b/agent2/p/orkle/feed/static/feed.go @@ -142,3 +142,7 @@ func (f *Feed) HasAddressWhitelisted(address string) (isWhitelisted, feedHasWhit func (f *Feed) Tasks() []feed.Task { return f.tasks } + +func (f *Feed) IsActive() bool { + return !f.isLocked +} diff --git a/agent2/p/orkle/instance.go b/agent2/p/orkle/instance.go index 62287bac85e..fb205b6af64 100644 --- a/agent2/p/orkle/instance.go +++ b/agent2/p/orkle/instance.go @@ -129,7 +129,12 @@ func (i *Instance) GetFeedDefinitions(forAddress string) string { i.feeds.Iterate("", "", func(_ string, value interface{}) bool { feed, ok := value.(Feed) if !ok { - panic("invalid task type") + panic("invalid feed type") + } + + // Don't give agents the ability to try to publish to inactive feeds. + if !feed.IsActive() { + return true } // Skip feeds the address is not whitelisted for. From 03958de052b898716a77719e704e4db2cff20cde Mon Sep 17 00:00:00 2001 From: deelawn Date: Mon, 22 Jan 2024 20:41:50 -0800 Subject: [PATCH 12/48] tried to implement whitelisting effectively --- agent2/p/orkle/agent/whitelist.go | 41 ++++++++++++ agent2/p/orkle/feed.go | 6 +- agent2/p/orkle/instance.go | 102 +++++++++++++++--------------- agent2/p/orkle/whitelist.go | 79 +++++++++++++++++++++++ agent2/r/gh/contract.go | 12 ++-- 5 files changed, 181 insertions(+), 59 deletions(-) create mode 100644 agent2/p/orkle/agent/whitelist.go create mode 100644 agent2/p/orkle/whitelist.go diff --git a/agent2/p/orkle/agent/whitelist.go b/agent2/p/orkle/agent/whitelist.go new file mode 100644 index 00000000000..d69004a1091 --- /dev/null +++ b/agent2/p/orkle/agent/whitelist.go @@ -0,0 +1,41 @@ +package agent + +import "gno.land/p/demo/avl" + +type Whitelist struct { + store *avl.Tree +} + +func (m *Whitelist) ClearAddresses() { + m.store = nil +} + +func (m *Whitelist) AddAddresses(addresses []string) { + if m.store == nil { + m.store = avl.NewTree() + } + + for _, address := range addresses { + m.store.Set(address, struct{}{}) + } +} + +func (m *Whitelist) RemoveAddress(address string) { + if m.store == nil { + return + } + + m.store.Remove(address) +} + +func (m Whitelist) HasDefinition() bool { + return m.store != nil +} + +func (m Whitelist) HasAddress(address string) bool { + if m.store == nil { + return false + } + + return m.store.Has(address) +} diff --git a/agent2/p/orkle/feed.go b/agent2/p/orkle/feed.go index bab0fd270ba..0f25efe7d11 100644 --- a/agent2/p/orkle/feed.go +++ b/agent2/p/orkle/feed.go @@ -11,7 +11,11 @@ type Feed interface { Value() (value feed.Value, dataType string, consumable bool) Ingest(funcType message.FuncType, rawMessage, providerAddress string) MarshalJSON() ([]byte, error) - HasAddressWhitelisted(address string) (isWhitelisted, feedHasWhitelist bool) Tasks() []feed.Task IsActive() bool } + +type FeedWithWhitelist struct { + Feed + Whitelist +} diff --git a/agent2/p/orkle/instance.go b/agent2/p/orkle/instance.go index fb205b6af64..3014509cecc 100644 --- a/agent2/p/orkle/instance.go +++ b/agent2/p/orkle/instance.go @@ -3,6 +3,7 @@ package orkle import ( "strings" + "github.com/gnolang/gno/agent2/p/orkle/agent" "github.com/gnolang/gno/agent2/p/orkle/feed" "github.com/gnolang/gno/agent2/p/orkle/message" "gno.land/p/demo/avl" @@ -10,39 +11,49 @@ import ( ) type Instance struct { - feeds *avl.Tree - whitelist *avl.Tree - ownerAddress string + feeds *avl.Tree + whitelist agent.Whitelist } -func NewInstance(ownerAddress string) *Instance { +func NewInstance() *Instance { return &Instance{ - ownerAddress: ownerAddress, + feeds: avl.NewTree(), } } -func (i *Instance) WithWhitelist(addresses ...string) *Instance { - i.whitelist = avl.NewTree() - for _, address := range addresses { - i.whitelist.Set(address, struct{}{}) +func assertNonEmptyString(s string) { + if len(s) == 0 { + panic("feed ids cannot be empty") } - return i } -func (i *Instance) WithFeeds(feeds ...Feed) *Instance { - i.feeds = avl.NewTree() +func (i *Instance) AddFeeds(feeds ...Feed) { for _, feed := range feeds { - i.feeds.Set(feed.ID(), feed) + assertNonEmptyString(feed.ID()) + i.feeds.Set( + feed.ID(), + FeedWithWhitelist{Feed: feed}, + ) } - return i } -func (i *Instance) AddFeeds(feeds ...Feed) { +func (i *Instance) AddFeedsWithWhitelists(feeds ...FeedWithWhitelist) { for _, feed := range feeds { - i.feeds.Set(feed.ID(), feed) + assertNonEmptyString(feed.ID()) + i.feeds.Set( + feed.ID(), + FeedWithWhitelist{ + Whitelist: feed.Whitelist, + Feed: feed, + }, + ) } } +func (i *Instance) RemoveFeed(id string) { + i.feeds.Remove(id) +} + type PostMessageHandler interface { Handle(i *Instance, funcType message.FuncType, feed Feed) } @@ -58,49 +69,22 @@ func (i *Instance) HandleMessage(msg string, postHandler PostMessageHandler) str default: id, msg := message.ParseID(msg) - feed := i.getFeed(id) + feedWithWhitelist := i.getFeedWithWhitelist(id) - if !addressWhitelisted(i.hasAddressWhitelisted(caller), caller, feed) { + if addressIsWhitelisted(&i.whitelist, feedWithWhitelist, caller, nil) { panic("caller not whitelisted") } - feed.Ingest(funcType, msg, caller) + feedWithWhitelist.Ingest(funcType, msg, caller) if postHandler != nil { - postHandler.Handle(i, funcType, feed) + postHandler.Handle(i, funcType, feedWithWhitelist) } } return "" } -func (i *Instance) RemoveFeed(id string) { - i.feeds.Remove(id) -} - -// TODO: test this. - -// addressWhiteListed returns true if: -// - the feed has a white list and the address is whitelisted, or -// - the feed has no white list and the instance has a white list and the address is whitelisted, or -// - the feed has no white list and the instance has no white list. -func addressWhitelisted(isInstanceWhitelisted bool, address string, feed Feed) bool { - // A feed whitelist takes priority, so it will return false if the feed has a whitelist and the caller is - // not a part of it. An empty whitelist defers to the instance whitelist. - var isWhitelisted, hasWhitelist bool - if isWhitelisted, hasWhitelist = feed.HasAddressWhitelisted(address); !isWhitelisted && hasWhitelist { - return false - } - - return (isWhitelisted && hasWhitelist) || isInstanceWhitelisted -} - -// hasAddressWhitelisted returns true if the address is whitelisted for the instance or if the instance has -// no whitelist. -func (i *Instance) hasAddressWhitelisted(address string) bool { - return i.whitelist == nil || i.whitelist.Has(address) -} - func (i *Instance) getFeed(id string) Feed { untypedFeed, ok := i.feeds.Get(id) if !ok { @@ -115,34 +99,48 @@ func (i *Instance) getFeed(id string) Feed { return feed } +func (i *Instance) getFeedWithWhitelist(id string) FeedWithWhitelist { + untypedFeedWithWhitelist, ok := i.feeds.Get(id) + if !ok { + panic("invalid ingest id: " + id) + } + + feedWithWhitelist, ok := untypedFeedWithWhitelist.(FeedWithWhitelist) + if !ok { + panic("invalid feed with whitelist type") + } + + return feedWithWhitelist +} + func (i *Instance) GetFeedValue(id string) (feed.Value, string, bool) { return i.getFeed(id).Value() } func (i *Instance) GetFeedDefinitions(forAddress string) string { - instanceHasAddressWhitelisted := i.hasAddressWhitelisted(forAddress) + instanceHasAddressWhitelisted := !i.whitelist.HasDefinition() || i.whitelist.HasAddress(forAddress) buf := new(strings.Builder) buf.WriteString("[") first := true i.feeds.Iterate("", "", func(_ string, value interface{}) bool { - feed, ok := value.(Feed) + feedWithWhitelist, ok := value.(FeedWithWhitelist) if !ok { panic("invalid feed type") } // Don't give agents the ability to try to publish to inactive feeds. - if !feed.IsActive() { + if !feedWithWhitelist.IsActive() { return true } // Skip feeds the address is not whitelisted for. - if !addressWhitelisted(instanceHasAddressWhitelisted, forAddress, feed) { + if !addressIsWhitelisted(&i.whitelist, feedWithWhitelist, forAddress, &instanceHasAddressWhitelisted) { return true } - taskBytes, err := feed.MarshalJSON() + taskBytes, err := feedWithWhitelist.Feed.MarshalJSON() if err != nil { panic(err) } diff --git a/agent2/p/orkle/whitelist.go b/agent2/p/orkle/whitelist.go new file mode 100644 index 00000000000..62fa8fa3b34 --- /dev/null +++ b/agent2/p/orkle/whitelist.go @@ -0,0 +1,79 @@ +package orkle + +type Whitelist interface { + ClearAddresses() + AddAddresses(addresses []string) + RemoveAddress(address string) + HasDefinition() bool + HasAddress(address string) bool +} + +func (i *Instance) ClearWhitelist(feedID string) { + if feedID == "" { + i.whitelist.ClearAddresses() + return + } + + feedWithWhitelist := i.getFeedWithWhitelist(feedID) + feedWithWhitelist.ClearAddresses() +} + +func (i *Instance) AddToWhitelist(feedID string, addresses []string) { + if feedID == "" { + i.whitelist.AddAddresses(addresses) + return + } + + feedWithWhitelist := i.getFeedWithWhitelist(feedID) + feedWithWhitelist.AddAddresses(addresses) +} + +func (i *Instance) RemoveFromWhitelist(feedID string, address string) { + if feedID == "" { + i.whitelist.RemoveAddress(address) + return + } + + feedWithWhitelist := i.getFeedWithWhitelist(feedID) + feedWithWhitelist.RemoveAddress(address) +} + +// TODO: test this. + +// addressWhiteListed returns true if: +// - the feed has a white list and the address is whitelisted, or +// - the feed has no white list and the instance has a white list and the address is whitelisted, or +// - the feed has no white list and the instance has no white list. +func addressIsWhitelisted(instanceWhitelist, feedWhitelist Whitelist, address string, instanceWhitelistedOverride *bool) bool { + // A feed whitelist takes priority, so it will return false if the feed has a whitelist and the caller is + // not a part of it. An empty whitelist defers to the instance whitelist. + if feedWhitelist != nil { + if feedWhitelist.HasDefinition() && !feedWhitelist.HasAddress(address) { + return false + } + + // Getting to this point means that one of the following is true: + // - the feed has no defined whitelist (so it can't possibly have the address whitelisted) + // - the feed has a defined whitelist and the caller is a part of it + // + // In this case, we can be sure that the boolean indicating whether the feed has this address whitelisted + // is equivalent to the boolean indicating whether the feed has a defined whitelist. + if feedWhitelist.HasDefinition() { + return true + } + } + + if instanceWhitelistedOverride != nil { + return *instanceWhitelistedOverride + } + + // We were unable able to determine whether this address is allowed after looking at the feed whitelist, + // so fall back to the instance whitelist. A complete absence of values in the instance whitelist means + // that the instance has no whitelist so we can return true because everything is allowed by default. + if instanceWhitelist == nil || !instanceWhitelist.HasDefinition() { + return true + } + + // The instance whitelist is defined so it the address is present then it is allowed. + return instanceWhitelist.HasAddress(address) +} diff --git a/agent2/r/gh/contract.go b/agent2/r/gh/contract.go index eb5fb4e568d..f114be4ada6 100644 --- a/agent2/r/gh/contract.go +++ b/agent2/r/gh/contract.go @@ -15,13 +15,18 @@ const ( ) var ( - oracle orkle.Instance + oracle *orkle.Instance postHandler postOrkleMessageHandler handleToAddressMap = avl.NewTree() addressToHandleMap = avl.NewTree() ) +func init() { + oracle = orkle.NewInstance() + oracle.AddToWhitelist("", []string{whitelistedAgentAddress}) +} + type postOrkleMessageHandler struct{} func (h postOrkleMessageHandler) Handle(i *orkle.Instance, funcType message.FuncType, feed orkle.Feed) { @@ -54,11 +59,6 @@ func (h postOrkleMessageHandler) Handle(i *orkle.Instance, funcType message.Func addressToHandleMap.Set(task.GnoAddress(), task.GithubHandle()) } -func init() { - oracle = *orkle.NewInstance(string(std.GetOrigCaller())). - WithWhitelist(whitelistedAgentAddress) -} - func RequestVerification(githubHandle string) { oracle.AddFeeds( static.NewSingleValueFeed( From fc3d750436e0c053533c42733fa14fb480206ee3 Mon Sep 17 00:00:00 2001 From: deelawn Date: Mon, 22 Jan 2024 20:52:17 -0800 Subject: [PATCH 13/48] moved the gh verification task to the realm --- agent2/r/gh/contract.go | 5 ++--- .../{p/orkle/feed/tasks/ghverify => r/gh}/task.go | 14 +++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) rename agent2/{p/orkle/feed/tasks/ghverify => r/gh}/task.go (55%) diff --git a/agent2/r/gh/contract.go b/agent2/r/gh/contract.go index f114be4ada6..c56023dc2aa 100644 --- a/agent2/r/gh/contract.go +++ b/agent2/r/gh/contract.go @@ -3,7 +3,6 @@ package gh import ( "github.com/gnolang/gno/agent2/p/orkle" "github.com/gnolang/gno/agent2/p/orkle/feed/static" - "github.com/gnolang/gno/agent2/p/orkle/feed/tasks/ghverify" "github.com/gnolang/gno/agent2/p/orkle/message" "gno.land/p/demo/avl" "gno.land/p/demo/std" @@ -50,7 +49,7 @@ func (h postOrkleMessageHandler) Handle(i *orkle.Instance, funcType message.Func panic("expected feed to have exactly one task") } - task, ok := feedTasks[0].(*ghverify.Task) + task, ok := feedTasks[0].(*verificationTask) if !ok { panic("expected ghverify task") } @@ -65,7 +64,7 @@ func RequestVerification(githubHandle string) { githubHandle, "string", nil, - ghverify.NewTask(string(std.GetOrigCaller()), githubHandle), + NewVerificationTask(string(std.GetOrigCaller()), githubHandle), ), ) } diff --git a/agent2/p/orkle/feed/tasks/ghverify/task.go b/agent2/r/gh/task.go similarity index 55% rename from agent2/p/orkle/feed/tasks/ghverify/task.go rename to agent2/r/gh/task.go index 157c3b6dee2..d7f74828455 100644 --- a/agent2/p/orkle/feed/tasks/ghverify/task.go +++ b/agent2/r/gh/task.go @@ -1,23 +1,23 @@ -package ghverify +package gh import ( "bufio" "bytes" ) -type Task struct { +type verificationTask struct { gnoAddress string githubHandle string } -func NewTask(gnoAddress, githubHandle string) *Task { - return &Task{ +func NewVerificationTask(gnoAddress, githubHandle string) *verificationTask { + return &verificationTask{ gnoAddress: gnoAddress, githubHandle: githubHandle, } } -func (t *Task) MarshalToJSON() ([]byte, error) { +func (t *verificationTask) MarshalToJSON() ([]byte, error) { buf := new(bytes.Buffer) w := bufio.NewWriter(buf) @@ -29,10 +29,10 @@ func (t *Task) MarshalToJSON() ([]byte, error) { return buf.Bytes(), nil } -func (t *Task) GnoAddress() string { +func (t *verificationTask) GnoAddress() string { return t.gnoAddress } -func (t *Task) GithubHandle() string { +func (t *verificationTask) GithubHandle() string { return t.githubHandle } From d00ffaa4cbc7625044585afa2bdc947106d9c3eb Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 23 Jan 2024 10:52:27 -0800 Subject: [PATCH 14/48] delete code and move new version to the gno p and r folders --- agent/devmock/avl/node.go | 463 ------------------ agent/devmock/avl/tree.go | 82 ---- agent/devmock/go.mod | 1 - agent/devmock/std/std.go | 7 - agent/p/agent/agent.go | 133 ----- .../github/verification/task/definition.go | 30 -- agent/p/agent/pricefeed/task/definition.go | 6 - agent/p/agent/random/task/aggregator.go | 31 -- agent/p/agent/random/task/definition.go | 32 -- agent/p/agent/task.go | 12 - agent/p/agent/task/aggregator.go | 8 - agent/p/agent/task/definition.go | 6 - agent/p/agent/task/history.go | 16 - agent/p/agent/task/result.go | 8 - agent/p/agent/tasks/continuous/task.go | 124 ----- agent/p/agent/tasks/singular/task.go | 88 ---- agent/r/gh/contract.go | 90 ---- agent2/devmock/avl/node.go | 463 ------------------ agent2/devmock/avl/tree.go | 82 ---- agent2/devmock/go.mod | 1 - agent2/devmock/std/std.go | 7 - .../p/demo/agent/aatask/aggregator.gno | 8 - .../p/demo/agent/aatask/definition.gno | 6 - examples/gno.land/p/demo/agent/aatask/gno.mod | 1 - .../gno.land/p/demo/agent/aatask/history.gno | 16 - .../gno.land/p/demo/agent/aatask/result.gno | 8 - .../gno.land/p/demo/agent/agent/agent.gno | 148 ------ examples/gno.land/p/demo/agent/agent/gno.mod | 1 - examples/gno.land/p/demo/agent/agent/task.gno | 12 - .../github/verification/task/definition.gno | 30 -- .../agent/github/verification/task/gno.mod | 1 - .../demo/agent/pricefeed/task/definition.gno | 6 - .../p/demo/agent/pricefeed/task/gno.mod | 1 - .../p/demo/agent/random/task/aggregator.gno | 35 -- .../p/demo/agent/random/task/definition.gno | 32 -- .../gno.land/p/demo/agent/random/task/gno.mod | 1 - .../p/demo/agent/tasks/continuous/gno.mod | 1 - .../p/demo/agent/tasks/continuous/task.gno | 124 ----- .../p/demo/agent/tasks/singular/gno.mod | 1 - .../p/demo/agent/tasks/singular/task.gno | 88 ---- .../gno.land/p/demo/orkle/v1/agent/gno.mod | 3 + .../p/demo/orkle/v1/agent/whitelist.gno | 0 .../gno.land/p/demo/orkle/v1/feed/gno.mod | 1 + .../gno.land/p/demo/orkle/v1/feed/task.gno | 0 .../gno.land/p/demo/orkle/v1/feed/type.gno | 0 .../gno.land/p/demo/orkle/v1/feed/value.gno | 0 .../p/demo/orkle/v1/feeds/static/feed.gno | 40 +- .../p/demo/orkle/v1/feeds/static/gno.mod | 10 + .../gno.land/p/demo/orkle/v1/ingester/gno.mod | 1 + .../p/demo/orkle/v1/ingester/type.gno | 0 .../p/demo/orkle/v1/ingesters/single/gno.mod | 6 + .../orkle/v1/ingesters/single/ingester.gno | 4 +- .../gno.land/p/demo/orkle/v1/message/gno.mod | 1 + .../p/demo/orkle/v1/message/parse.gno | 8 - .../gno.land/p/demo/orkle/v1/message/type.gno | 9 + .../gno.land/p/demo/orkle/v1/orkle/feed.gno | 4 +- .../gno.land/p/demo/orkle/v1/orkle/gno.mod | 9 + .../p/demo/orkle/v1/orkle/ingester.gno | 2 +- .../p/demo/orkle/v1/orkle/instance.gno | 8 +- .../p/demo/orkle/v1/orkle/storage.gno | 2 +- .../p/demo/orkle/v1/orkle/whitelist.gno | 0 .../p/demo/orkle/v1/storage/simple/gno.mod | 3 + .../demo/orkle/v1/storage/simple/storage.gno | 16 +- examples/gno.land/r/gnoland/gh/contract.gno | 105 ---- examples/gno.land/r/gnoland/gh/gno.mod | 1 - .../gno.land/r/gnoland/ghverify/contract.gno | 11 +- examples/gno.land/r/gnoland/ghverify/gno.mod | 8 + .../gno.land/r/gnoland/ghverify/task.gno | 2 +- go.mod | 4 - 69 files changed, 85 insertions(+), 2383 deletions(-) delete mode 100644 agent/devmock/avl/node.go delete mode 100644 agent/devmock/avl/tree.go delete mode 100644 agent/devmock/go.mod delete mode 100644 agent/devmock/std/std.go delete mode 100644 agent/p/agent/agent.go delete mode 100644 agent/p/agent/github/verification/task/definition.go delete mode 100644 agent/p/agent/pricefeed/task/definition.go delete mode 100644 agent/p/agent/random/task/aggregator.go delete mode 100644 agent/p/agent/random/task/definition.go delete mode 100644 agent/p/agent/task.go delete mode 100644 agent/p/agent/task/aggregator.go delete mode 100644 agent/p/agent/task/definition.go delete mode 100644 agent/p/agent/task/history.go delete mode 100644 agent/p/agent/task/result.go delete mode 100644 agent/p/agent/tasks/continuous/task.go delete mode 100644 agent/p/agent/tasks/singular/task.go delete mode 100644 agent/r/gh/contract.go delete mode 100644 agent2/devmock/avl/node.go delete mode 100644 agent2/devmock/avl/tree.go delete mode 100644 agent2/devmock/go.mod delete mode 100644 agent2/devmock/std/std.go delete mode 100644 examples/gno.land/p/demo/agent/aatask/aggregator.gno delete mode 100644 examples/gno.land/p/demo/agent/aatask/definition.gno delete mode 100644 examples/gno.land/p/demo/agent/aatask/gno.mod delete mode 100644 examples/gno.land/p/demo/agent/aatask/history.gno delete mode 100644 examples/gno.land/p/demo/agent/aatask/result.gno delete mode 100644 examples/gno.land/p/demo/agent/agent/agent.gno delete mode 100644 examples/gno.land/p/demo/agent/agent/gno.mod delete mode 100644 examples/gno.land/p/demo/agent/agent/task.gno delete mode 100644 examples/gno.land/p/demo/agent/github/verification/task/definition.gno delete mode 100644 examples/gno.land/p/demo/agent/github/verification/task/gno.mod delete mode 100644 examples/gno.land/p/demo/agent/pricefeed/task/definition.gno delete mode 100644 examples/gno.land/p/demo/agent/pricefeed/task/gno.mod delete mode 100644 examples/gno.land/p/demo/agent/random/task/aggregator.gno delete mode 100644 examples/gno.land/p/demo/agent/random/task/definition.gno delete mode 100644 examples/gno.land/p/demo/agent/random/task/gno.mod delete mode 100644 examples/gno.land/p/demo/agent/tasks/continuous/gno.mod delete mode 100644 examples/gno.land/p/demo/agent/tasks/continuous/task.gno delete mode 100644 examples/gno.land/p/demo/agent/tasks/singular/gno.mod delete mode 100644 examples/gno.land/p/demo/agent/tasks/singular/task.gno create mode 100644 examples/gno.land/p/demo/orkle/v1/agent/gno.mod rename agent2/p/orkle/agent/whitelist.go => examples/gno.land/p/demo/orkle/v1/agent/whitelist.gno (100%) create mode 100644 examples/gno.land/p/demo/orkle/v1/feed/gno.mod rename agent2/p/orkle/feed/task.go => examples/gno.land/p/demo/orkle/v1/feed/task.gno (100%) rename agent2/p/orkle/feed/type.go => examples/gno.land/p/demo/orkle/v1/feed/type.gno (100%) rename agent2/p/orkle/feed/value.go => examples/gno.land/p/demo/orkle/v1/feed/value.gno (100%) rename agent2/p/orkle/feed/static/feed.go => examples/gno.land/p/demo/orkle/v1/feeds/static/feed.gno (74%) create mode 100644 examples/gno.land/p/demo/orkle/v1/feeds/static/gno.mod create mode 100644 examples/gno.land/p/demo/orkle/v1/ingester/gno.mod rename agent2/p/orkle/ingester/type.go => examples/gno.land/p/demo/orkle/v1/ingester/type.gno (100%) create mode 100644 examples/gno.land/p/demo/orkle/v1/ingesters/single/gno.mod rename agent2/p/orkle/ingester/single/ingester.go => examples/gno.land/p/demo/orkle/v1/ingesters/single/ingester.gno (80%) create mode 100644 examples/gno.land/p/demo/orkle/v1/message/gno.mod rename agent2/p/orkle/message/parse.go => examples/gno.land/p/demo/orkle/v1/message/parse.gno (74%) create mode 100644 examples/gno.land/p/demo/orkle/v1/message/type.gno rename agent2/p/orkle/feed.go => examples/gno.land/p/demo/orkle/v1/orkle/feed.gno (78%) create mode 100644 examples/gno.land/p/demo/orkle/v1/orkle/gno.mod rename agent2/p/orkle/ingester.go => examples/gno.land/p/demo/orkle/v1/orkle/ingester.gno (76%) rename agent2/p/orkle/instance.go => examples/gno.land/p/demo/orkle/v1/orkle/instance.gno (95%) rename agent2/p/orkle/storage.go => examples/gno.land/p/demo/orkle/v1/orkle/storage.gno (68%) rename agent2/p/orkle/whitelist.go => examples/gno.land/p/demo/orkle/v1/orkle/whitelist.gno (100%) create mode 100644 examples/gno.land/p/demo/orkle/v1/storage/simple/gno.mod rename agent2/p/orkle/storage/simple.go => examples/gno.land/p/demo/orkle/v1/storage/simple/storage.gno (59%) delete mode 100644 examples/gno.land/r/gnoland/gh/contract.gno delete mode 100644 examples/gno.land/r/gnoland/gh/gno.mod rename agent2/r/gh/contract.go => examples/gno.land/r/gnoland/ghverify/contract.gno (88%) create mode 100644 examples/gno.land/r/gnoland/ghverify/gno.mod rename agent2/r/gh/task.go => examples/gno.land/r/gnoland/ghverify/task.gno (97%) diff --git a/agent/devmock/avl/node.go b/agent/devmock/avl/node.go deleted file mode 100644 index a49dba00ae1..00000000000 --- a/agent/devmock/avl/node.go +++ /dev/null @@ -1,463 +0,0 @@ -package avl - -//---------------------------------------- -// Node - -type Node struct { - key string - value interface{} - height int8 - size int - leftNode *Node - rightNode *Node -} - -func NewNode(key string, value interface{}) *Node { - return &Node{ - key: key, - value: value, - height: 0, - size: 1, - } -} - -func (node *Node) Size() int { - if node == nil { - return 0 - } - return node.size -} - -func (node *Node) IsLeaf() bool { - return node.height == 0 -} - -func (node *Node) Key() string { - return node.key -} - -func (node *Node) Value() interface{} { - return node.value -} - -func (node *Node) _copy() *Node { - if node.height == 0 { - panic("Why are you copying a value node?") - } - return &Node{ - key: node.key, - height: node.height, - size: node.size, - leftNode: node.leftNode, - rightNode: node.rightNode, - } -} - -func (node *Node) Has(key string) (has bool) { - if node == nil { - return false - } - if node.key == key { - return true - } - if node.height == 0 { - return false - } else { - if key < node.key { - return node.getLeftNode().Has(key) - } else { - return node.getRightNode().Has(key) - } - } -} - -func (node *Node) Get(key string) (index int, value interface{}, exists bool) { - if node == nil { - return 0, nil, false - } - if node.height == 0 { - if node.key == key { - return 0, node.value, true - } else if node.key < key { - return 1, nil, false - } else { - return 0, nil, false - } - } else { - if key < node.key { - return node.getLeftNode().Get(key) - } else { - rightNode := node.getRightNode() - index, value, exists = rightNode.Get(key) - index += node.size - rightNode.size - return index, value, exists - } - } -} - -func (node *Node) GetByIndex(index int) (key string, value interface{}) { - if node.height == 0 { - if index == 0 { - return node.key, node.value - } else { - panic("GetByIndex asked for invalid index") - return "", nil - } - } else { - // TODO: could improve this by storing the sizes - leftNode := node.getLeftNode() - if index < leftNode.size { - return leftNode.GetByIndex(index) - } else { - return node.getRightNode().GetByIndex(index - leftNode.size) - } - } -} - -// XXX consider a better way to do this... perhaps split Node from Node. -func (node *Node) Set(key string, value interface{}) (newSelf *Node, updated bool) { - if node == nil { - return NewNode(key, value), false - } - if node.height == 0 { - if key < node.key { - return &Node{ - key: node.key, - height: 1, - size: 2, - leftNode: NewNode(key, value), - rightNode: node, - }, false - } else if key == node.key { - return NewNode(key, value), true - } else { - return &Node{ - key: key, - height: 1, - size: 2, - leftNode: node, - rightNode: NewNode(key, value), - }, false - } - } else { - node = node._copy() - if key < node.key { - node.leftNode, updated = node.getLeftNode().Set(key, value) - } else { - node.rightNode, updated = node.getRightNode().Set(key, value) - } - if updated { - return node, updated - } else { - node.calcHeightAndSize() - return node.balance(), updated - } - } -} - -// newNode: The new node to replace node after remove. -// newKey: new leftmost leaf key for node after successfully removing 'key' if changed. -// value: removed value. -func (node *Node) Remove(key string) ( - newNode *Node, newKey string, value interface{}, removed bool, -) { - if node == nil { - return nil, "", nil, false - } - if node.height == 0 { - if key == node.key { - return nil, "", node.value, true - } else { - return node, "", nil, false - } - } else { - if key < node.key { - var newLeftNode *Node - newLeftNode, newKey, value, removed = node.getLeftNode().Remove(key) - if !removed { - return node, "", value, false - } else if newLeftNode == nil { // left node held value, was removed - return node.rightNode, node.key, value, true - } - node = node._copy() - node.leftNode = newLeftNode - node.calcHeightAndSize() - node = node.balance() - return node, newKey, value, true - } else { - var newRightNode *Node - newRightNode, newKey, value, removed = node.getRightNode().Remove(key) - if !removed { - return node, "", value, false - } else if newRightNode == nil { // right node held value, was removed - return node.leftNode, "", value, true - } - node = node._copy() - node.rightNode = newRightNode - if newKey != "" { - node.key = newKey - } - node.calcHeightAndSize() - node = node.balance() - return node, "", value, true - } - } -} - -func (node *Node) getLeftNode() *Node { - return node.leftNode -} - -func (node *Node) getRightNode() *Node { - return node.rightNode -} - -// NOTE: overwrites node -// TODO: optimize balance & rotate -func (node *Node) rotateRight() *Node { - node = node._copy() - l := node.getLeftNode() - _l := l._copy() - - _lrCached := _l.rightNode - _l.rightNode = node - node.leftNode = _lrCached - - node.calcHeightAndSize() - _l.calcHeightAndSize() - - return _l -} - -// NOTE: overwrites node -// TODO: optimize balance & rotate -func (node *Node) rotateLeft() *Node { - node = node._copy() - r := node.getRightNode() - _r := r._copy() - - _rlCached := _r.leftNode - _r.leftNode = node - node.rightNode = _rlCached - - node.calcHeightAndSize() - _r.calcHeightAndSize() - - return _r -} - -// NOTE: mutates height and size -func (node *Node) calcHeightAndSize() { - node.height = maxInt8(node.getLeftNode().height, node.getRightNode().height) + 1 - node.size = node.getLeftNode().size + node.getRightNode().size -} - -func (node *Node) calcBalance() int { - return int(node.getLeftNode().height) - int(node.getRightNode().height) -} - -// NOTE: assumes that node can be modified -// TODO: optimize balance & rotate -func (node *Node) balance() (newSelf *Node) { - balance := node.calcBalance() - if balance > 1 { - if node.getLeftNode().calcBalance() >= 0 { - // Left Left Case - return node.rotateRight() - } else { - // Left Right Case - // node = node._copy() - left := node.getLeftNode() - node.leftNode = left.rotateLeft() - // node.calcHeightAndSize() - return node.rotateRight() - } - } - if balance < -1 { - if node.getRightNode().calcBalance() <= 0 { - // Right Right Case - return node.rotateLeft() - } else { - // Right Left Case - // node = node._copy() - right := node.getRightNode() - node.rightNode = right.rotateRight() - // node.calcHeightAndSize() - return node.rotateLeft() - } - } - // Nothing changed - return node -} - -// Shortcut for TraverseInRange. -func (node *Node) Iterate(start, end string, cb func(*Node) bool) bool { - return node.TraverseInRange(start, end, true, true, cb) -} - -// Shortcut for TraverseInRange. -func (node *Node) ReverseIterate(start, end string, cb func(*Node) bool) bool { - return node.TraverseInRange(start, end, false, true, cb) -} - -// TraverseInRange traverses all nodes, including inner nodes. -// Start is inclusive and end is exclusive when ascending, -// Start and end are inclusive when descending. -// Empty start and empty end denote no start and no end. -// If leavesOnly is true, only visit leaf nodes. -// NOTE: To simulate an exclusive reverse traversal, -// just append 0x00 to start. -func (node *Node) TraverseInRange(start, end string, ascending bool, leavesOnly bool, cb func(*Node) bool) bool { - if node == nil { - return false - } - afterStart := (start == "" || start < node.key) - startOrAfter := (start == "" || start <= node.key) - beforeEnd := false - if ascending { - beforeEnd = (end == "" || node.key < end) - } else { - beforeEnd = (end == "" || node.key <= end) - } - - // Run callback per inner/leaf node. - stop := false - if (!node.IsLeaf() && !leavesOnly) || - (node.IsLeaf() && startOrAfter && beforeEnd) { - stop = cb(node) - if stop { - return stop - } - } - if node.IsLeaf() { - return stop - } - - if ascending { - // check lower nodes, then higher - if afterStart { - stop = node.getLeftNode().TraverseInRange(start, end, ascending, leavesOnly, cb) - } - if stop { - return stop - } - if beforeEnd { - stop = node.getRightNode().TraverseInRange(start, end, ascending, leavesOnly, cb) - } - } else { - // check the higher nodes first - if beforeEnd { - stop = node.getRightNode().TraverseInRange(start, end, ascending, leavesOnly, cb) - } - if stop { - return stop - } - if afterStart { - stop = node.getLeftNode().TraverseInRange(start, end, ascending, leavesOnly, cb) - } - } - - return stop -} - -// TraverseByOffset traverses all nodes, including inner nodes. -// A limit of math.MaxInt means no limit. -func (node *Node) TraverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool { - if node == nil { - return false - } - - // fast paths. these happen only if TraverseByOffset is called directly on a leaf. - if limit <= 0 || offset >= node.size { - return false - } - if node.IsLeaf() { - if offset > 0 { - return false - } - return cb(node) - } - - // go to the actual recursive function. - return node.traverseByOffset(offset, limit, descending, leavesOnly, cb) -} - -func (node *Node) traverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool { - // caller guarantees: offset < node.size; limit > 0. - - if !leavesOnly { - if cb(node) { - return true - } - } - first, second := node.getLeftNode(), node.getRightNode() - if descending { - first, second = second, first - } - if first.IsLeaf() { - // either run or skip, based on offset - if offset > 0 { - offset-- - } else { - cb(first) - limit-- - if limit <= 0 { - return false - } - } - } else { - // possible cases: - // 1 the offset given skips the first node entirely - // 2 the offset skips none or part of the first node, but the limit requires some of the second node. - // 3 the offset skips none or part of the first node, and the limit stops our search on the first node. - if offset >= first.size { - offset -= first.size // 1 - } else { - if first.traverseByOffset(offset, limit, descending, leavesOnly, cb) { - return true - } - // number of leaves which could actually be called from inside - delta := first.size - offset - offset = 0 - if delta >= limit { - return true // 3 - } - limit -= delta // 2 - } - } - - // because of the caller guarantees and the way we handle the first node, - // at this point we know that limit > 0 and there must be some values in - // this second node that we include. - - // => if the second node is a leaf, it has to be included. - if second.IsLeaf() { - return cb(second) - } - // => if it is not a leaf, it will still be enough to recursively call this - // function with the updated offset and limit - return second.traverseByOffset(offset, limit, descending, leavesOnly, cb) -} - -// Only used in testing... -func (node *Node) lmd() *Node { - if node.height == 0 { - return node - } - return node.getLeftNode().lmd() -} - -// Only used in testing... -func (node *Node) rmd() *Node { - if node.height == 0 { - return node - } - return node.getRightNode().rmd() -} - -func maxInt8(a, b int8) int8 { - if a > b { - return a - } - return b -} diff --git a/agent/devmock/avl/tree.go b/agent/devmock/avl/tree.go deleted file mode 100644 index 7b33d28fbe3..00000000000 --- a/agent/devmock/avl/tree.go +++ /dev/null @@ -1,82 +0,0 @@ -package avl - -type IterCbFn func(key string, value interface{}) bool - -//---------------------------------------- -// Tree - -// The zero struct can be used as an empty tree. -type Tree struct { - node *Node -} - -func NewTree() *Tree { - return &Tree{ - node: nil, - } -} - -func (tree *Tree) Size() int { - return tree.node.Size() -} - -func (tree *Tree) Has(key string) (has bool) { - return tree.node.Has(key) -} - -func (tree *Tree) Get(key string) (value interface{}, exists bool) { - _, value, exists = tree.node.Get(key) - return -} - -func (tree *Tree) GetByIndex(index int) (key string, value interface{}) { - return tree.node.GetByIndex(index) -} - -func (tree *Tree) Set(key string, value interface{}) (updated bool) { - newnode, updated := tree.node.Set(key, value) - tree.node = newnode - return updated -} - -func (tree *Tree) Remove(key string) (value interface{}, removed bool) { - newnode, _, value, removed := tree.node.Remove(key) - tree.node = newnode - return value, removed -} - -// Shortcut for TraverseInRange. -func (tree *Tree) Iterate(start, end string, cb IterCbFn) bool { - return tree.node.TraverseInRange(start, end, true, true, - func(node *Node) bool { - return cb(node.Key(), node.Value()) - }, - ) -} - -// Shortcut for TraverseInRange. -func (tree *Tree) ReverseIterate(start, end string, cb IterCbFn) bool { - return tree.node.TraverseInRange(start, end, false, true, - func(node *Node) bool { - return cb(node.Key(), node.Value()) - }, - ) -} - -// Shortcut for TraverseByOffset. -func (tree *Tree) IterateByOffset(offset int, count int, cb IterCbFn) bool { - return tree.node.TraverseByOffset(offset, count, true, true, - func(node *Node) bool { - return cb(node.Key(), node.Value()) - }, - ) -} - -// Shortcut for TraverseByOffset. -func (tree *Tree) ReverseIterateByOffset(offset int, count int, cb IterCbFn) bool { - return tree.node.TraverseByOffset(offset, count, false, true, - func(node *Node) bool { - return cb(node.Key(), node.Value()) - }, - ) -} diff --git a/agent/devmock/go.mod b/agent/devmock/go.mod deleted file mode 100644 index 6c7ce04c7da..00000000000 --- a/agent/devmock/go.mod +++ /dev/null @@ -1 +0,0 @@ -package devmock \ No newline at end of file diff --git a/agent/devmock/std/std.go b/agent/devmock/std/std.go deleted file mode 100644 index 06cdd59952f..00000000000 --- a/agent/devmock/std/std.go +++ /dev/null @@ -1,7 +0,0 @@ -package std - -type Address string - -func GetOrigCaller() Address { - return "" -} diff --git a/agent/p/agent/agent.go b/agent/p/agent/agent.go deleted file mode 100644 index 8a4b214217d..00000000000 --- a/agent/p/agent/agent.go +++ /dev/null @@ -1,133 +0,0 @@ -package agent - -import ( - // TODO: replace with std - - "strings" - - "gno.land/p/demo/avl" - "gno.land/p/demo/std" -) - -var ( - tasks *avl.Tree - - // Not sure this is necessary. - adminAddress string -) - -const ( - FunctionFinish = "finish" - FunctionRequest = "request" - FunctionSubmit = "submit" -) - -type PostRequestAction func(function string, task Task) - -func HandleRequest(payload string, post PostRequestAction) string { - payloadParts := strings.SplitN(payload, ",", 1) - if len(payloadParts) != 2 { - panic("invalid agent payload") - } - - switch function := payloadParts[0]; function { - case FunctionFinish: - task := FinishTask(payloadParts[1]) - if post != nil { - post(function, task) - } - case FunctionRequest: - return RequestTasks() - case FunctionSubmit: - submitArgs := strings.SplitN(payloadParts[1], ",", 1) - if len(submitArgs) != 2 { - panic("invalid agent submission payload") - } - - SubmitTaskValue(submitArgs[0], submitArgs[1]) - if post != nil { - post(function, nil) - } - default: - panic("unknown function " + function) - } - - return "" -} - -func FinishTask(id string) Task { - task, ok := tasks.Get(id) - if !ok { - panic("task not found") - } - - task.(Task).Finish(string(std.GetOrigCaller())) - return task.(Task) -} - -func RequestTasks() string { - buf := new(strings.Builder) - buf.WriteString("[") - first := true - tasks.Iterate("", "", func(_ string, value interface{}) bool { - if !first { - buf.WriteString(",") - } - - task := value.(Task) - taskBytes, err := task.MarshalJSON() - if err != nil { - panic(err) - } - - // Guard against any tasks that shouldn't be returned; maybe they are not active because they have - // already been completed. - if len(taskBytes) == 0 { - return true - } - - first = false - buf.Write(taskBytes) - return true - }) - buf.WriteString("]") - return buf.String() -} - -func SubmitTaskValue(id, value string) Task { - task, ok := tasks.Get(id) - if !ok { - panic("task not found") - } - - task.(Task).SubmitResult(string(std.GetOrigCaller()), value) - return task.(Task) -} - -func AddTask(task Task) { - if tasks.Has(task.ID()) { - panic("task id " + task.ID() + " already exists") - } - - tasks.Set(task.ID(), task) -} - -func RemoveTask(id string) { - if _, removed := tasks.Remove(id); !removed { - panic("task id " + id + " not found") - } -} - -func Init(admin std.Address, newTasks ...Task) { - if tasks != nil { - panic("already initialized") - } - - adminAddress = string(admin) - tasks = avl.NewTree() - for _, task := range newTasks { - if updated := tasks.Set(task.ID(), task); updated { - panic("task id " + task.ID() + " already exists") - } - } -} diff --git a/agent/p/agent/github/verification/task/definition.go b/agent/p/agent/github/verification/task/definition.go deleted file mode 100644 index 87c918027ca..00000000000 --- a/agent/p/agent/github/verification/task/definition.go +++ /dev/null @@ -1,30 +0,0 @@ -package task - -import ( - "bufio" - "bytes" -) - -const Type string = "gh-verification" - -type Definition struct { - Handle string - Address string -} - -func (d Definition) MarshalJSON() ([]byte, error) { - buf := new(bytes.Buffer) - w := bufio.NewWriter(buf) - - w.WriteString( - `{"handle":"` + d.Handle + - `","address":"` + d.Address + `"}`, - ) - - w.Flush() - return buf.Bytes(), nil -} - -func (d Definition) Type() string { - return Type -} diff --git a/agent/p/agent/pricefeed/task/definition.go b/agent/p/agent/pricefeed/task/definition.go deleted file mode 100644 index 8aef6ee02a5..00000000000 --- a/agent/p/agent/pricefeed/task/definition.go +++ /dev/null @@ -1,6 +0,0 @@ -package task - -type Definition struct { - Pair string // USD/CAD - AcceptedDeviation float64 -} diff --git a/agent/p/agent/random/task/aggregator.go b/agent/p/agent/random/task/aggregator.go deleted file mode 100644 index 111f0e14a52..00000000000 --- a/agent/p/agent/random/task/aggregator.go +++ /dev/null @@ -1,31 +0,0 @@ -package task - -import ( - "math" - "strconv" -) - -type Aggregator struct { - sum uint64 - taskDefinition *Definition -} - -func (a *Aggregator) Aggregate() string { - randomValue := a.taskDefinition.RangeStart + a.sum%a.taskDefinition.RangeEnd - a.sum = 0 - return strconv.FormatUint(randomValue, 10) -} - -func (a *Aggregator) AddValue(value string) { - intValue, err := strconv.ParseUint(value, 10, 64) - if err != nil { - panic("value needs to be type uint64: " + err.Error()) - } - - // Account for overflow. - if diff := math.MaxUint64 - a.sum; diff < intValue { - a.sum = intValue - diff - } else { - a.sum += intValue - } -} diff --git a/agent/p/agent/random/task/definition.go b/agent/p/agent/random/task/definition.go deleted file mode 100644 index b0165832103..00000000000 --- a/agent/p/agent/random/task/definition.go +++ /dev/null @@ -1,32 +0,0 @@ -package task - -import ( - "bufio" - "bytes" - "strconv" -) - -const Type string = "random" - -// Input in this range. -type Definition struct { - RangeStart uint64 - RangeEnd uint64 -} - -func (d Definition) MarshalJSON() ([]byte, error) { - buf := new(bytes.Buffer) - w := bufio.NewWriter(buf) - - w.WriteString( - `{"start":` + strconv.FormatUint(d.RangeStart, 10) + - `,"end":` + strconv.FormatUint(d.RangeEnd, 10) + `}`, - ) - - w.Flush() - return buf.Bytes(), nil -} - -func (d Definition) Type() string { - return Type -} diff --git a/agent/p/agent/task.go b/agent/p/agent/task.go deleted file mode 100644 index 6815b8a40db..00000000000 --- a/agent/p/agent/task.go +++ /dev/null @@ -1,12 +0,0 @@ -package agent - -import "github.com/gnolang/gno/agent/p/agent/task" - -type Task interface { - Definition() task.Definition - Finish(origCaller string) - GetResult() (result task.Result, hasResult bool) - ID() string - MarshalJSON() ([]byte, error) - SubmitResult(origCaller, value string) -} diff --git a/agent/p/agent/task/aggregator.go b/agent/p/agent/task/aggregator.go deleted file mode 100644 index 35d8013d284..00000000000 --- a/agent/p/agent/task/aggregator.go +++ /dev/null @@ -1,8 +0,0 @@ -package task - -type Aggregator interface { - // Aggregate runs an aggregation on all values written using the AddValue method. - // It returns the aggregated value as a string. - Aggregate() string - AddValue(value string) -} diff --git a/agent/p/agent/task/definition.go b/agent/p/agent/task/definition.go deleted file mode 100644 index 39f40eeb026..00000000000 --- a/agent/p/agent/task/definition.go +++ /dev/null @@ -1,6 +0,0 @@ -package task - -type Definition interface { - MarshalJSON() ([]byte, error) - Type() string -} diff --git a/agent/p/agent/task/history.go b/agent/p/agent/task/history.go deleted file mode 100644 index 02ec63b5754..00000000000 --- a/agent/p/agent/task/history.go +++ /dev/null @@ -1,16 +0,0 @@ -package task - -type History struct { - Size int - NumRemoved int - Results []Result -} - -func (h *History) AddResult(result Result) { - h.Results = append(h.Results, result) - if numResults := len(h.Results); numResults > h.Size { - numToRemove := numResults - h.Size - h.Results = h.Results[numToRemove:] - h.NumRemoved += numToRemove - } -} diff --git a/agent/p/agent/task/result.go b/agent/p/agent/task/result.go deleted file mode 100644 index 8816870104a..00000000000 --- a/agent/p/agent/task/result.go +++ /dev/null @@ -1,8 +0,0 @@ -package task - -import "time" - -type Result struct { - Value string - Time time.Time -} diff --git a/agent/p/agent/tasks/continuous/task.go b/agent/p/agent/tasks/continuous/task.go deleted file mode 100644 index 820336d4082..00000000000 --- a/agent/p/agent/tasks/continuous/task.go +++ /dev/null @@ -1,124 +0,0 @@ -package continuous - -import ( - "bufio" - "bytes" - "strconv" - "strings" - "time" - - "github.com/gnolang/gno/agent/p/agent/task" - "gno.land/p/demo/avl" -) - -type Task struct { - id string - Era int - NextDue time.Time - Interval time.Duration - Aggregator task.Aggregator - definition task.Definition - RespondentWhiteList *avl.Tree - Respondents *avl.Tree - History task.History -} - -func (t Task) Definition() task.Definition { - return t.definition -} - -func (t *Task) Finish(origCaller string) { - now := time.Now() - if now.Before(t.NextDue) { - panic("era can not be transitioned until " + t.NextDue.String()) - } - - if t.RespondentWhiteList != nil { - if !t.RespondentWhiteList.Has(origCaller) { - panic("caller not in whitelist") - } - } - - // Handle the task state transitions. - t.Era++ - t.NextDue = now.Add(t.Interval) - t.Respondents = avl.NewTree() - - resultValue := t.Aggregator.Aggregate() - t.History.AddResult(task.Result{Value: resultValue, Time: now}) - - return -} - -func (t Task) GetResult() (result task.Result, hasResult bool) { - if len(t.History.Results) == 0 { - return - } - - return t.History.Results[len(t.History.Results)-1], true -} - -func (t Task) ID() string { - return t.id -} - -func (t Task) MarshalJSON() ([]byte, error) { - buf := new(bytes.Buffer) - w := bufio.NewWriter(buf) - w.WriteString( - `{"id":"` + t.id + - `","type":"` + t.definition.Type() + - `","era":` + strconv.Itoa(t.Era) + - `,"next_due":` + strconv.FormatInt(t.NextDue.Unix(), 10) + - `,"interval":` + strconv.FormatInt(int64(t.Interval/time.Second), 10) + - `,"definition":`, - ) - taskDefinitionBytes, err := t.definition.MarshalJSON() - if err != nil { - return nil, err - } - - w.Write(taskDefinitionBytes) - w.WriteString("}") - w.Flush() - return buf.Bytes(), nil -} - -func (t *Task) SubmitResult(origCaller, value string) { - var era int - valueParts := strings.SplitN(value, ",", 1) - if len(valueParts) != 2 { - panic("invalid result value; must be prefixed with era + ','") - } - - era, err := strconv.Atoi(valueParts[0]) - if err != nil { - panic(valueParts[0] + " is not a valid era") - } - - value = valueParts[1] - - // Check that the era is for the next expected result. - if era != t.Era { - panic("expected era of " + strconv.Itoa(t.Era) + ", got " + strconv.Itoa(era)) - } - - // Check that the window to write has not ended. - if time.Now().After(t.NextDue) { - panic("era " + strconv.Itoa(t.Era) + " has ended; call Finish to transition to the next era") - } - - if t.RespondentWhiteList != nil { - if !t.RespondentWhiteList.Has(origCaller) { - panic("caller not in whitelist") - } - } - - // Each agent can only respond once during each era. - if t.Respondents.Has(origCaller) { - panic("response already sent for this era") - } - - t.Aggregator.AddValue(value) - t.Respondents.Set(origCaller, struct{}{}) -} diff --git a/agent/p/agent/tasks/singular/task.go b/agent/p/agent/tasks/singular/task.go deleted file mode 100644 index 75949bbd47d..00000000000 --- a/agent/p/agent/tasks/singular/task.go +++ /dev/null @@ -1,88 +0,0 @@ -package singular - -import ( - "bufio" - "bytes" - "time" - - "github.com/gnolang/gno/agent/p/agent/task" -) - -type Task struct { - id string - definition task.Definition - - isInactive bool - result *task.Result - authorizedRespondent string -} - -func NewTask(id string, definition task.Definition, authorizedRespondent string) *Task { - return &Task{ - id: id, - definition: definition, - authorizedRespondent: authorizedRespondent, - } -} - -func (t Task) Definition() task.Definition { - return t.definition -} - -func (t Task) Finish(_ string) { - panic("singular tasks are implicitly finished when a result is submitted") -} - -func (t Task) GetResult() (result task.Result, hasResult bool) { - if t.result == nil { - return - } - - return *t.result, true -} - -func (t Task) ID() string { - return t.id -} - -func (t Task) MarshalJSON() ([]byte, error) { - if !t.isInactive { - return nil, nil - } - - buf := new(bytes.Buffer) - w := bufio.NewWriter(buf) - w.WriteString( - `{"id":"` + t.id + - `","type":"` + t.definition.Type() + - `","definition":`, - ) - - taskDefinitionBytes, err := t.definition.MarshalJSON() - if err != nil { - return nil, err - } - - w.Write(taskDefinitionBytes) - w.WriteString("}") - w.Flush() - return buf.Bytes(), nil -} - -func (t *Task) SubmitResult(origCaller, value string) { - if t.isInactive { - panic("task is inactive") - } - - if t.authorizedRespondent != origCaller { - panic("caller not authorized to submit result") - } - - t.result = &task.Result{ - Value: value, - Time: time.Now(), - } - - t.isInactive = true - return -} diff --git a/agent/r/gh/contract.go b/agent/r/gh/contract.go deleted file mode 100644 index 9dc1e258eb5..00000000000 --- a/agent/r/gh/contract.go +++ /dev/null @@ -1,90 +0,0 @@ -package gh - -import ( - "bufio" - "bytes" - - "github.com/gnolang/gno/agent/p/agent" - ghTask "github.com/gnolang/gno/agent/p/agent/github/verification/task" - "github.com/gnolang/gno/agent/p/agent/tasks/singular" - "gno.land/p/demo/avl" - "gno.land/p/demo/std" -) - -const ( - authorizedAgentAddress string = "" - verified = "OK" -) - -var ( - handleToAddress = avl.NewTree() - addressToHandle = avl.NewTree() -) - -func init() { - agent.Init(std.GetOrigCaller()) -} - -func updateVerifiedGHData(function string, task agent.Task) { - if function != agent.FunctionSubmit { - return - } - - result, hasResult := task.GetResult() - if !hasResult { - return - } - - if result.Value != verified { - return - } - - definition, ok := task.Definition().(ghTask.Definition) - if !ok { - panic("unexpected task definition of type " + definition.Type()) - } - - handleToAddress.Set(definition.Handle, definition.Address) - addressToHandle.Set(definition.Address, definition.Handle) - - // It's been verified so clean it up. - agent.RemoveTask(task.ID()) -} - -func OrkleAgentSubmitAction(payload string) string { - return agent.HandleRequest(payload, updateVerifiedGHData) -} - -func RequestVerification(handle, address string) { - if address == "" { - address = string(std.GetOrigCaller()) - } - - agent.AddTask( - singular.NewTask( - handle, - ghTask.Definition{Handle: handle, Address: address}, - authorizedAgentAddress, - ), - ) -} - -func Render(_ string) string { - buf := new(bytes.Buffer) - w := bufio.NewWriter(buf) - w.WriteString(`{"verified":{`) - first := true - handleToAddress.Iterate("", "", func(key string, value interface{}) bool { - if !first { - w.WriteString(",") - } - - w.WriteString(`"` + key + `":"` + value.(string) + `"`) - first = false - return true - }) - - w.WriteString(`}}`) - w.Flush() - return buf.String() -} diff --git a/agent2/devmock/avl/node.go b/agent2/devmock/avl/node.go deleted file mode 100644 index a49dba00ae1..00000000000 --- a/agent2/devmock/avl/node.go +++ /dev/null @@ -1,463 +0,0 @@ -package avl - -//---------------------------------------- -// Node - -type Node struct { - key string - value interface{} - height int8 - size int - leftNode *Node - rightNode *Node -} - -func NewNode(key string, value interface{}) *Node { - return &Node{ - key: key, - value: value, - height: 0, - size: 1, - } -} - -func (node *Node) Size() int { - if node == nil { - return 0 - } - return node.size -} - -func (node *Node) IsLeaf() bool { - return node.height == 0 -} - -func (node *Node) Key() string { - return node.key -} - -func (node *Node) Value() interface{} { - return node.value -} - -func (node *Node) _copy() *Node { - if node.height == 0 { - panic("Why are you copying a value node?") - } - return &Node{ - key: node.key, - height: node.height, - size: node.size, - leftNode: node.leftNode, - rightNode: node.rightNode, - } -} - -func (node *Node) Has(key string) (has bool) { - if node == nil { - return false - } - if node.key == key { - return true - } - if node.height == 0 { - return false - } else { - if key < node.key { - return node.getLeftNode().Has(key) - } else { - return node.getRightNode().Has(key) - } - } -} - -func (node *Node) Get(key string) (index int, value interface{}, exists bool) { - if node == nil { - return 0, nil, false - } - if node.height == 0 { - if node.key == key { - return 0, node.value, true - } else if node.key < key { - return 1, nil, false - } else { - return 0, nil, false - } - } else { - if key < node.key { - return node.getLeftNode().Get(key) - } else { - rightNode := node.getRightNode() - index, value, exists = rightNode.Get(key) - index += node.size - rightNode.size - return index, value, exists - } - } -} - -func (node *Node) GetByIndex(index int) (key string, value interface{}) { - if node.height == 0 { - if index == 0 { - return node.key, node.value - } else { - panic("GetByIndex asked for invalid index") - return "", nil - } - } else { - // TODO: could improve this by storing the sizes - leftNode := node.getLeftNode() - if index < leftNode.size { - return leftNode.GetByIndex(index) - } else { - return node.getRightNode().GetByIndex(index - leftNode.size) - } - } -} - -// XXX consider a better way to do this... perhaps split Node from Node. -func (node *Node) Set(key string, value interface{}) (newSelf *Node, updated bool) { - if node == nil { - return NewNode(key, value), false - } - if node.height == 0 { - if key < node.key { - return &Node{ - key: node.key, - height: 1, - size: 2, - leftNode: NewNode(key, value), - rightNode: node, - }, false - } else if key == node.key { - return NewNode(key, value), true - } else { - return &Node{ - key: key, - height: 1, - size: 2, - leftNode: node, - rightNode: NewNode(key, value), - }, false - } - } else { - node = node._copy() - if key < node.key { - node.leftNode, updated = node.getLeftNode().Set(key, value) - } else { - node.rightNode, updated = node.getRightNode().Set(key, value) - } - if updated { - return node, updated - } else { - node.calcHeightAndSize() - return node.balance(), updated - } - } -} - -// newNode: The new node to replace node after remove. -// newKey: new leftmost leaf key for node after successfully removing 'key' if changed. -// value: removed value. -func (node *Node) Remove(key string) ( - newNode *Node, newKey string, value interface{}, removed bool, -) { - if node == nil { - return nil, "", nil, false - } - if node.height == 0 { - if key == node.key { - return nil, "", node.value, true - } else { - return node, "", nil, false - } - } else { - if key < node.key { - var newLeftNode *Node - newLeftNode, newKey, value, removed = node.getLeftNode().Remove(key) - if !removed { - return node, "", value, false - } else if newLeftNode == nil { // left node held value, was removed - return node.rightNode, node.key, value, true - } - node = node._copy() - node.leftNode = newLeftNode - node.calcHeightAndSize() - node = node.balance() - return node, newKey, value, true - } else { - var newRightNode *Node - newRightNode, newKey, value, removed = node.getRightNode().Remove(key) - if !removed { - return node, "", value, false - } else if newRightNode == nil { // right node held value, was removed - return node.leftNode, "", value, true - } - node = node._copy() - node.rightNode = newRightNode - if newKey != "" { - node.key = newKey - } - node.calcHeightAndSize() - node = node.balance() - return node, "", value, true - } - } -} - -func (node *Node) getLeftNode() *Node { - return node.leftNode -} - -func (node *Node) getRightNode() *Node { - return node.rightNode -} - -// NOTE: overwrites node -// TODO: optimize balance & rotate -func (node *Node) rotateRight() *Node { - node = node._copy() - l := node.getLeftNode() - _l := l._copy() - - _lrCached := _l.rightNode - _l.rightNode = node - node.leftNode = _lrCached - - node.calcHeightAndSize() - _l.calcHeightAndSize() - - return _l -} - -// NOTE: overwrites node -// TODO: optimize balance & rotate -func (node *Node) rotateLeft() *Node { - node = node._copy() - r := node.getRightNode() - _r := r._copy() - - _rlCached := _r.leftNode - _r.leftNode = node - node.rightNode = _rlCached - - node.calcHeightAndSize() - _r.calcHeightAndSize() - - return _r -} - -// NOTE: mutates height and size -func (node *Node) calcHeightAndSize() { - node.height = maxInt8(node.getLeftNode().height, node.getRightNode().height) + 1 - node.size = node.getLeftNode().size + node.getRightNode().size -} - -func (node *Node) calcBalance() int { - return int(node.getLeftNode().height) - int(node.getRightNode().height) -} - -// NOTE: assumes that node can be modified -// TODO: optimize balance & rotate -func (node *Node) balance() (newSelf *Node) { - balance := node.calcBalance() - if balance > 1 { - if node.getLeftNode().calcBalance() >= 0 { - // Left Left Case - return node.rotateRight() - } else { - // Left Right Case - // node = node._copy() - left := node.getLeftNode() - node.leftNode = left.rotateLeft() - // node.calcHeightAndSize() - return node.rotateRight() - } - } - if balance < -1 { - if node.getRightNode().calcBalance() <= 0 { - // Right Right Case - return node.rotateLeft() - } else { - // Right Left Case - // node = node._copy() - right := node.getRightNode() - node.rightNode = right.rotateRight() - // node.calcHeightAndSize() - return node.rotateLeft() - } - } - // Nothing changed - return node -} - -// Shortcut for TraverseInRange. -func (node *Node) Iterate(start, end string, cb func(*Node) bool) bool { - return node.TraverseInRange(start, end, true, true, cb) -} - -// Shortcut for TraverseInRange. -func (node *Node) ReverseIterate(start, end string, cb func(*Node) bool) bool { - return node.TraverseInRange(start, end, false, true, cb) -} - -// TraverseInRange traverses all nodes, including inner nodes. -// Start is inclusive and end is exclusive when ascending, -// Start and end are inclusive when descending. -// Empty start and empty end denote no start and no end. -// If leavesOnly is true, only visit leaf nodes. -// NOTE: To simulate an exclusive reverse traversal, -// just append 0x00 to start. -func (node *Node) TraverseInRange(start, end string, ascending bool, leavesOnly bool, cb func(*Node) bool) bool { - if node == nil { - return false - } - afterStart := (start == "" || start < node.key) - startOrAfter := (start == "" || start <= node.key) - beforeEnd := false - if ascending { - beforeEnd = (end == "" || node.key < end) - } else { - beforeEnd = (end == "" || node.key <= end) - } - - // Run callback per inner/leaf node. - stop := false - if (!node.IsLeaf() && !leavesOnly) || - (node.IsLeaf() && startOrAfter && beforeEnd) { - stop = cb(node) - if stop { - return stop - } - } - if node.IsLeaf() { - return stop - } - - if ascending { - // check lower nodes, then higher - if afterStart { - stop = node.getLeftNode().TraverseInRange(start, end, ascending, leavesOnly, cb) - } - if stop { - return stop - } - if beforeEnd { - stop = node.getRightNode().TraverseInRange(start, end, ascending, leavesOnly, cb) - } - } else { - // check the higher nodes first - if beforeEnd { - stop = node.getRightNode().TraverseInRange(start, end, ascending, leavesOnly, cb) - } - if stop { - return stop - } - if afterStart { - stop = node.getLeftNode().TraverseInRange(start, end, ascending, leavesOnly, cb) - } - } - - return stop -} - -// TraverseByOffset traverses all nodes, including inner nodes. -// A limit of math.MaxInt means no limit. -func (node *Node) TraverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool { - if node == nil { - return false - } - - // fast paths. these happen only if TraverseByOffset is called directly on a leaf. - if limit <= 0 || offset >= node.size { - return false - } - if node.IsLeaf() { - if offset > 0 { - return false - } - return cb(node) - } - - // go to the actual recursive function. - return node.traverseByOffset(offset, limit, descending, leavesOnly, cb) -} - -func (node *Node) traverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool { - // caller guarantees: offset < node.size; limit > 0. - - if !leavesOnly { - if cb(node) { - return true - } - } - first, second := node.getLeftNode(), node.getRightNode() - if descending { - first, second = second, first - } - if first.IsLeaf() { - // either run or skip, based on offset - if offset > 0 { - offset-- - } else { - cb(first) - limit-- - if limit <= 0 { - return false - } - } - } else { - // possible cases: - // 1 the offset given skips the first node entirely - // 2 the offset skips none or part of the first node, but the limit requires some of the second node. - // 3 the offset skips none or part of the first node, and the limit stops our search on the first node. - if offset >= first.size { - offset -= first.size // 1 - } else { - if first.traverseByOffset(offset, limit, descending, leavesOnly, cb) { - return true - } - // number of leaves which could actually be called from inside - delta := first.size - offset - offset = 0 - if delta >= limit { - return true // 3 - } - limit -= delta // 2 - } - } - - // because of the caller guarantees and the way we handle the first node, - // at this point we know that limit > 0 and there must be some values in - // this second node that we include. - - // => if the second node is a leaf, it has to be included. - if second.IsLeaf() { - return cb(second) - } - // => if it is not a leaf, it will still be enough to recursively call this - // function with the updated offset and limit - return second.traverseByOffset(offset, limit, descending, leavesOnly, cb) -} - -// Only used in testing... -func (node *Node) lmd() *Node { - if node.height == 0 { - return node - } - return node.getLeftNode().lmd() -} - -// Only used in testing... -func (node *Node) rmd() *Node { - if node.height == 0 { - return node - } - return node.getRightNode().rmd() -} - -func maxInt8(a, b int8) int8 { - if a > b { - return a - } - return b -} diff --git a/agent2/devmock/avl/tree.go b/agent2/devmock/avl/tree.go deleted file mode 100644 index 7b33d28fbe3..00000000000 --- a/agent2/devmock/avl/tree.go +++ /dev/null @@ -1,82 +0,0 @@ -package avl - -type IterCbFn func(key string, value interface{}) bool - -//---------------------------------------- -// Tree - -// The zero struct can be used as an empty tree. -type Tree struct { - node *Node -} - -func NewTree() *Tree { - return &Tree{ - node: nil, - } -} - -func (tree *Tree) Size() int { - return tree.node.Size() -} - -func (tree *Tree) Has(key string) (has bool) { - return tree.node.Has(key) -} - -func (tree *Tree) Get(key string) (value interface{}, exists bool) { - _, value, exists = tree.node.Get(key) - return -} - -func (tree *Tree) GetByIndex(index int) (key string, value interface{}) { - return tree.node.GetByIndex(index) -} - -func (tree *Tree) Set(key string, value interface{}) (updated bool) { - newnode, updated := tree.node.Set(key, value) - tree.node = newnode - return updated -} - -func (tree *Tree) Remove(key string) (value interface{}, removed bool) { - newnode, _, value, removed := tree.node.Remove(key) - tree.node = newnode - return value, removed -} - -// Shortcut for TraverseInRange. -func (tree *Tree) Iterate(start, end string, cb IterCbFn) bool { - return tree.node.TraverseInRange(start, end, true, true, - func(node *Node) bool { - return cb(node.Key(), node.Value()) - }, - ) -} - -// Shortcut for TraverseInRange. -func (tree *Tree) ReverseIterate(start, end string, cb IterCbFn) bool { - return tree.node.TraverseInRange(start, end, false, true, - func(node *Node) bool { - return cb(node.Key(), node.Value()) - }, - ) -} - -// Shortcut for TraverseByOffset. -func (tree *Tree) IterateByOffset(offset int, count int, cb IterCbFn) bool { - return tree.node.TraverseByOffset(offset, count, true, true, - func(node *Node) bool { - return cb(node.Key(), node.Value()) - }, - ) -} - -// Shortcut for TraverseByOffset. -func (tree *Tree) ReverseIterateByOffset(offset int, count int, cb IterCbFn) bool { - return tree.node.TraverseByOffset(offset, count, false, true, - func(node *Node) bool { - return cb(node.Key(), node.Value()) - }, - ) -} diff --git a/agent2/devmock/go.mod b/agent2/devmock/go.mod deleted file mode 100644 index 6c7ce04c7da..00000000000 --- a/agent2/devmock/go.mod +++ /dev/null @@ -1 +0,0 @@ -package devmock \ No newline at end of file diff --git a/agent2/devmock/std/std.go b/agent2/devmock/std/std.go deleted file mode 100644 index 06cdd59952f..00000000000 --- a/agent2/devmock/std/std.go +++ /dev/null @@ -1,7 +0,0 @@ -package std - -type Address string - -func GetOrigCaller() Address { - return "" -} diff --git a/examples/gno.land/p/demo/agent/aatask/aggregator.gno b/examples/gno.land/p/demo/agent/aatask/aggregator.gno deleted file mode 100644 index 7a0eb541d7a..00000000000 --- a/examples/gno.land/p/demo/agent/aatask/aggregator.gno +++ /dev/null @@ -1,8 +0,0 @@ -package aatask - -type Aggregator interface { - // Aggregate runs an aggregation on all values written using the AddValue method. - // It returns the aggregated value as a string. - Aggregate() string - AddValue(value string) -} diff --git a/examples/gno.land/p/demo/agent/aatask/definition.gno b/examples/gno.land/p/demo/agent/aatask/definition.gno deleted file mode 100644 index aeb6cff8dbb..00000000000 --- a/examples/gno.land/p/demo/agent/aatask/definition.gno +++ /dev/null @@ -1,6 +0,0 @@ -package aatask - -type Definition interface { - MarshalJSON() ([]byte, error) - Type() string -} diff --git a/examples/gno.land/p/demo/agent/aatask/gno.mod b/examples/gno.land/p/demo/agent/aatask/gno.mod deleted file mode 100644 index 518feb52405..00000000000 --- a/examples/gno.land/p/demo/agent/aatask/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/agent/aatask diff --git a/examples/gno.land/p/demo/agent/aatask/history.gno b/examples/gno.land/p/demo/agent/aatask/history.gno deleted file mode 100644 index 5e460ccca57..00000000000 --- a/examples/gno.land/p/demo/agent/aatask/history.gno +++ /dev/null @@ -1,16 +0,0 @@ -package aatask - -type History struct { - Size int - NumRemoved int - Results []Result -} - -func (h *History) AddResult(result Result) { - h.Results = append(h.Results, result) - if numResults := len(h.Results); numResults > h.Size { - numToRemove := numResults - h.Size - h.Results = h.Results[numToRemove:] - h.NumRemoved += numToRemove - } -} diff --git a/examples/gno.land/p/demo/agent/aatask/result.gno b/examples/gno.land/p/demo/agent/aatask/result.gno deleted file mode 100644 index 64848f08ade..00000000000 --- a/examples/gno.land/p/demo/agent/aatask/result.gno +++ /dev/null @@ -1,8 +0,0 @@ -package aatask - -import "time" - -type Result struct { - Value string - Time time.Time -} diff --git a/examples/gno.land/p/demo/agent/agent/agent.gno b/examples/gno.land/p/demo/agent/agent/agent.gno deleted file mode 100644 index 42bf5ba31b7..00000000000 --- a/examples/gno.land/p/demo/agent/agent/agent.gno +++ /dev/null @@ -1,148 +0,0 @@ -package agent - -import ( - // TODO: replace with std - - "std" - "strings" - - "gno.land/p/demo/avl" -) - -type Instance struct { - tasks *avl.Tree - - // Not sure this is necessary. - adminAddress string -} - -const ( - FunctionFinish = "finish" - FunctionRequest = "request" - FunctionSubmit = "submit" -) - -type PostRequestAction func(function string, task Task) - -func (i *Instance) HandleRequest(payload string, post PostRequestAction) string { - payloadParts := strings.SplitN(payload, ",", 2) - if len(payloadParts) == 0 { - panic("invalid empty agent payload") - } - - switch function := payloadParts[0]; function { - case FunctionRequest: - return i.RequestTasks() - case FunctionFinish: - if len(payloadParts) != 2 { - panic("invalid agent finish payload") - } - - task := i.FinishTask(payloadParts[1]) - if post != nil { - post(function, task) - } - case FunctionSubmit: - if len(payloadParts) != 2 { - panic("invalid agent submission payload") - } - - submitArgs := strings.SplitN(payloadParts[1], ",", 2) - if len(submitArgs) != 2 { - panic("invalid agent submission args") - } - - task := i.SubmitTaskValue(submitArgs[0], submitArgs[1]) - if post != nil { - post(function, task) - } - default: - panic("unknown agent payload function " + function) - } - - return "" -} - -func (i *Instance) FinishTask(id string) Task { - task, ok := i.tasks.Get(id) - if !ok { - panic("task not found") - } - - task.(Task).Finish(string(std.GetOrigCaller())) - return task.(Task) -} - -func (i *Instance) RequestTasks() string { - buf := new(strings.Builder) - buf.WriteString("[") - first := true - - i.tasks.Iterate("", "", func(_ string, value interface{}) bool { - if !first { - buf.WriteString(",") - } - - task, ok := value.(Task) - if !ok { - panic("invalid task type") - } - - taskBytes, err := task.MarshalJSON() - if err != nil { - panic(err) - } - - // Guard against any tasks that shouldn't be returned; maybe they are not active because they have - // already been completed. - if len(taskBytes) == 0 { - return true - } - - first = false - buf.Write(taskBytes) - return true - }) - buf.WriteString("]") - return buf.String() -} - -func (i *Instance) SubmitTaskValue(id, value string) Task { - task, ok := i.tasks.Get(id) - if !ok { - panic("task not found") - } - - task.(Task).SubmitResult(string(std.GetOrigCaller()), value) - return task.(Task) -} - -func (i *Instance) AddTask(task Task) { - if i.tasks.Has(task.ID()) { - panic("task id " + task.ID() + " already exists") - } - - i.tasks.Set(task.ID(), task) -} - -func (i *Instance) RemoveTask(id string) { - if _, removed := i.tasks.Remove(id); !removed { - panic("task id " + id + " not found") - } -} - -func Init(admin std.Address, newTasks ...Task) *Instance { - i := &Instance{ - adminAddress: string(admin), - } - - tasks := avl.NewTree() - for _, task := range newTasks { - if updated := tasks.Set(task.ID(), task); updated { - panic("task id " + task.ID() + " already exists") - } - } - - i.tasks = tasks - return i -} diff --git a/examples/gno.land/p/demo/agent/agent/gno.mod b/examples/gno.land/p/demo/agent/agent/gno.mod deleted file mode 100644 index 1bb323c9b5b..00000000000 --- a/examples/gno.land/p/demo/agent/agent/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/agent/agent diff --git a/examples/gno.land/p/demo/agent/agent/task.gno b/examples/gno.land/p/demo/agent/agent/task.gno deleted file mode 100644 index 5fdbd18f83e..00000000000 --- a/examples/gno.land/p/demo/agent/agent/task.gno +++ /dev/null @@ -1,12 +0,0 @@ -package agent - -import "gno.land/p/demo/agent/aatask" - -type Task interface { - Definition() aatask.Definition - Finish(origCaller string) - GetResult() (result aatask.Result, hasResult bool) - ID() string - MarshalJSON() ([]byte, error) - SubmitResult(origCaller, value string) -} diff --git a/examples/gno.land/p/demo/agent/github/verification/task/definition.gno b/examples/gno.land/p/demo/agent/github/verification/task/definition.gno deleted file mode 100644 index 87c918027ca..00000000000 --- a/examples/gno.land/p/demo/agent/github/verification/task/definition.gno +++ /dev/null @@ -1,30 +0,0 @@ -package task - -import ( - "bufio" - "bytes" -) - -const Type string = "gh-verification" - -type Definition struct { - Handle string - Address string -} - -func (d Definition) MarshalJSON() ([]byte, error) { - buf := new(bytes.Buffer) - w := bufio.NewWriter(buf) - - w.WriteString( - `{"handle":"` + d.Handle + - `","address":"` + d.Address + `"}`, - ) - - w.Flush() - return buf.Bytes(), nil -} - -func (d Definition) Type() string { - return Type -} diff --git a/examples/gno.land/p/demo/agent/github/verification/task/gno.mod b/examples/gno.land/p/demo/agent/github/verification/task/gno.mod deleted file mode 100644 index 526238926b7..00000000000 --- a/examples/gno.land/p/demo/agent/github/verification/task/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/agent/github/verification/task diff --git a/examples/gno.land/p/demo/agent/pricefeed/task/definition.gno b/examples/gno.land/p/demo/agent/pricefeed/task/definition.gno deleted file mode 100644 index 8aef6ee02a5..00000000000 --- a/examples/gno.land/p/demo/agent/pricefeed/task/definition.gno +++ /dev/null @@ -1,6 +0,0 @@ -package task - -type Definition struct { - Pair string // USD/CAD - AcceptedDeviation float64 -} diff --git a/examples/gno.land/p/demo/agent/pricefeed/task/gno.mod b/examples/gno.land/p/demo/agent/pricefeed/task/gno.mod deleted file mode 100644 index 16b38cd9e7f..00000000000 --- a/examples/gno.land/p/demo/agent/pricefeed/task/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/agent/pricefeed/task diff --git a/examples/gno.land/p/demo/agent/random/task/aggregator.gno b/examples/gno.land/p/demo/agent/random/task/aggregator.gno deleted file mode 100644 index 05a44eb5f75..00000000000 --- a/examples/gno.land/p/demo/agent/random/task/aggregator.gno +++ /dev/null @@ -1,35 +0,0 @@ -package task - -import ( - "math" - "strconv" -) - -type Aggregator struct { - sum uint64 - taskDefinition *Definition -} - -func (a *Aggregator) Aggregate() string { - randomValue := a.taskDefinition.RangeStart + a.sum%a.taskDefinition.RangeEnd - a.sum = 0 - return strconv.FormatUint(randomValue, 10) -} - -func (a *Aggregator) AddValue(value string) { - intValue, err := strconv.Atoi(value) - if err != nil { - panic("value needs to be type uint64: " + err.Error()) - } - - a.sum += uint64(intValue) - - // No need to check for overflow currently because ParseUint is not supported. - - // Account for overflow. - // if diff := math.MaxUint64 - a.sum; diff < intValue { - // a.sum = intValue - diff - // } else { - // a.sum += intValue - // } -} diff --git a/examples/gno.land/p/demo/agent/random/task/definition.gno b/examples/gno.land/p/demo/agent/random/task/definition.gno deleted file mode 100644 index b0165832103..00000000000 --- a/examples/gno.land/p/demo/agent/random/task/definition.gno +++ /dev/null @@ -1,32 +0,0 @@ -package task - -import ( - "bufio" - "bytes" - "strconv" -) - -const Type string = "random" - -// Input in this range. -type Definition struct { - RangeStart uint64 - RangeEnd uint64 -} - -func (d Definition) MarshalJSON() ([]byte, error) { - buf := new(bytes.Buffer) - w := bufio.NewWriter(buf) - - w.WriteString( - `{"start":` + strconv.FormatUint(d.RangeStart, 10) + - `,"end":` + strconv.FormatUint(d.RangeEnd, 10) + `}`, - ) - - w.Flush() - return buf.Bytes(), nil -} - -func (d Definition) Type() string { - return Type -} diff --git a/examples/gno.land/p/demo/agent/random/task/gno.mod b/examples/gno.land/p/demo/agent/random/task/gno.mod deleted file mode 100644 index 946fd320649..00000000000 --- a/examples/gno.land/p/demo/agent/random/task/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/agent/random/task diff --git a/examples/gno.land/p/demo/agent/tasks/continuous/gno.mod b/examples/gno.land/p/demo/agent/tasks/continuous/gno.mod deleted file mode 100644 index e305a9640c5..00000000000 --- a/examples/gno.land/p/demo/agent/tasks/continuous/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/agent/tasks/continuous diff --git a/examples/gno.land/p/demo/agent/tasks/continuous/task.gno b/examples/gno.land/p/demo/agent/tasks/continuous/task.gno deleted file mode 100644 index 3021c7f60d8..00000000000 --- a/examples/gno.land/p/demo/agent/tasks/continuous/task.gno +++ /dev/null @@ -1,124 +0,0 @@ -package continuous - -import ( - "bufio" - "bytes" - "strconv" - "strings" - "time" - - "gno.land/p/demo/agent/aatask" - "gno.land/p/demo/avl" -) - -type Task struct { - id string - Era int - NextDue time.Time - Interval time.Duration - Aggregator aatask.Aggregator - definition aatask.Definition - RespondentWhiteList *avl.Tree - Respondents *avl.Tree - History aatask.History -} - -func (t Task) Definition() aatask.Definition { - return t.definition -} - -func (t *Task) Finish(origCaller string) { - now := time.Now() - if now.Before(t.NextDue) { - panic("era can not be transitioned until " + t.NextDue.String()) - } - - if t.RespondentWhiteList != nil { - if !t.RespondentWhiteList.Has(origCaller) { - panic("caller not in whitelist") - } - } - - // Handle the task state transitions. - t.Era++ - t.NextDue = now.Add(t.Interval) - t.Respondents = avl.NewTree() - - resultValue := t.Aggregator.Aggregate() - t.History.AddResult(aatask.Result{Value: resultValue, Time: now}) - - return -} - -func (t Task) GetResult() (result aatask.Result, hasResult bool) { - if len(t.History.Results) == 0 { - return - } - - return t.History.Results[len(t.History.Results)-1], true -} - -func (t Task) ID() string { - return t.id -} - -func (t Task) MarshalJSON() ([]byte, error) { - buf := new(bytes.Buffer) - w := bufio.NewWriter(buf) - w.WriteString( - `{"id":"` + t.id + - `","type":"` + t.definition.Type() + - `","era":` + strconv.Itoa(t.Era) + - `,"next_due":` + strconv.FormatInt(t.NextDue.Unix(), 10) + - `,"interval":` + strconv.FormatInt(int64(t.Interval/time.Second), 10) + - `,"definition":`, - ) - taskDefinitionBytes, err := t.definition.MarshalJSON() - if err != nil { - return nil, err - } - - w.Write(taskDefinitionBytes) - w.WriteString("}") - w.Flush() - return buf.Bytes(), nil -} - -func (t *Task) SubmitResult(origCaller, value string) { - var era int - valueParts := strings.SplitN(value, ",", 1) - if len(valueParts) != 2 { - panic("invalid result value; must be prefixed with era + ','") - } - - era, err := strconv.Atoi(valueParts[0]) - if err != nil { - panic(valueParts[0] + " is not a valid era") - } - - value = valueParts[1] - - // Check that the era is for the next expected result. - if era != t.Era { - panic("expected era of " + strconv.Itoa(t.Era) + ", got " + strconv.Itoa(era)) - } - - // Check that the window to write has not ended. - if time.Now().After(t.NextDue) { - panic("era " + strconv.Itoa(t.Era) + " has ended; call Finish to transition to the next era") - } - - if t.RespondentWhiteList != nil { - if !t.RespondentWhiteList.Has(origCaller) { - panic("caller not in whitelist") - } - } - - // Each agent can only respond once during each era. - if t.Respondents.Has(origCaller) { - panic("response already sent for this era") - } - - t.Aggregator.AddValue(value) - t.Respondents.Set(origCaller, struct{}{}) -} diff --git a/examples/gno.land/p/demo/agent/tasks/singular/gno.mod b/examples/gno.land/p/demo/agent/tasks/singular/gno.mod deleted file mode 100644 index 18fe86643c2..00000000000 --- a/examples/gno.land/p/demo/agent/tasks/singular/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/agent/tasks/singular diff --git a/examples/gno.land/p/demo/agent/tasks/singular/task.gno b/examples/gno.land/p/demo/agent/tasks/singular/task.gno deleted file mode 100644 index 69167aad553..00000000000 --- a/examples/gno.land/p/demo/agent/tasks/singular/task.gno +++ /dev/null @@ -1,88 +0,0 @@ -package singular - -import ( - "bufio" - "bytes" - "time" - - "gno.land/p/demo/agent/aatask" -) - -type Task struct { - id string - definition aatask.Definition - - isInactive bool - result *aatask.Result - authorizedRespondent string -} - -func NewTask(id string, definition aatask.Definition, authorizedRespondent string) *Task { - return &Task{ - id: id, - definition: definition, - authorizedRespondent: authorizedRespondent, - } -} - -func (t Task) Definition() aatask.Definition { - return t.definition -} - -func (t Task) Finish(_ string) { - panic("singular tasks are implicitly finished when a result is submitted") -} - -func (t Task) GetResult() (result aatask.Result, hasResult bool) { - if t.result == nil { - return - } - - return *t.result, true -} - -func (t Task) ID() string { - return t.id -} - -func (t Task) MarshalJSON() ([]byte, error) { - if t.isInactive { - return nil, nil - } - - buf := new(bytes.Buffer) - w := bufio.NewWriter(buf) - w.WriteString( - `{"id":"` + t.id + - `","type":"` + t.definition.Type() + - `","definition":`, - ) - - taskDefinitionBytes, err := t.definition.MarshalJSON() - if err != nil { - return nil, err - } - - w.Write(taskDefinitionBytes) - w.WriteString("}") - w.Flush() - return buf.Bytes(), nil -} - -func (t *Task) SubmitResult(origCaller, value string) { - if t.isInactive { - panic("task is inactive") - } - - if t.authorizedRespondent != origCaller { - panic("caller not authorized to submit result") - } - - t.result = &aatask.Result{ - Value: value, - Time: time.Now(), - } - - t.isInactive = true - return -} diff --git a/examples/gno.land/p/demo/orkle/v1/agent/gno.mod b/examples/gno.land/p/demo/orkle/v1/agent/gno.mod new file mode 100644 index 00000000000..72a562ee19f --- /dev/null +++ b/examples/gno.land/p/demo/orkle/v1/agent/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/orkle/v1/agent + +require gno.land/p/demo/avl v0.0.0-latest \ No newline at end of file diff --git a/agent2/p/orkle/agent/whitelist.go b/examples/gno.land/p/demo/orkle/v1/agent/whitelist.gno similarity index 100% rename from agent2/p/orkle/agent/whitelist.go rename to examples/gno.land/p/demo/orkle/v1/agent/whitelist.gno diff --git a/examples/gno.land/p/demo/orkle/v1/feed/gno.mod b/examples/gno.land/p/demo/orkle/v1/feed/gno.mod new file mode 100644 index 00000000000..6ac03a167c9 --- /dev/null +++ b/examples/gno.land/p/demo/orkle/v1/feed/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/orkle/v1/feed \ No newline at end of file diff --git a/agent2/p/orkle/feed/task.go b/examples/gno.land/p/demo/orkle/v1/feed/task.gno similarity index 100% rename from agent2/p/orkle/feed/task.go rename to examples/gno.land/p/demo/orkle/v1/feed/task.gno diff --git a/agent2/p/orkle/feed/type.go b/examples/gno.land/p/demo/orkle/v1/feed/type.gno similarity index 100% rename from agent2/p/orkle/feed/type.go rename to examples/gno.land/p/demo/orkle/v1/feed/type.gno diff --git a/agent2/p/orkle/feed/value.go b/examples/gno.land/p/demo/orkle/v1/feed/value.gno similarity index 100% rename from agent2/p/orkle/feed/value.go rename to examples/gno.land/p/demo/orkle/v1/feed/value.gno diff --git a/agent2/p/orkle/feed/static/feed.go b/examples/gno.land/p/demo/orkle/v1/feeds/static/feed.gno similarity index 74% rename from agent2/p/orkle/feed/static/feed.go rename to examples/gno.land/p/demo/orkle/v1/feeds/static/feed.gno index 22e0b334a3c..e3af0eedad0 100644 --- a/agent2/p/orkle/feed/static/feed.go +++ b/examples/gno.land/p/demo/orkle/v1/feeds/static/feed.gno @@ -3,21 +3,19 @@ package static import ( "bufio" "bytes" - ufmt "fmt" - - "github.com/gnolang/gno/agent2/p/orkle" - "github.com/gnolang/gno/agent2/p/orkle/feed" - "github.com/gnolang/gno/agent2/p/orkle/ingester/single" - "github.com/gnolang/gno/agent2/p/orkle/message" - "github.com/gnolang/gno/agent2/p/orkle/storage" - "gno.land/p/demo/avl" + + "gno.land/p/demo/orkle/v1/feed" + "gno.land/p/demo/orkle/v1/ingesters/single" + "gno.land/p/demo/orkle/v1/message" + "gno.land/p/demo/orkle/v1/orkle" + "gno.land/p/demo/orkle/v1/storage/simple" + "gno.land/p/demo/ufmt" ) type Feed struct { id string isLocked bool valueDataType string - whitelist *avl.Tree ingester orkle.Ingester storage orkle.Storage tasks []feed.Task @@ -26,41 +24,29 @@ type Feed struct { func NewFeed( id string, valueDataType string, - whitelist []string, ingester orkle.Ingester, storage orkle.Storage, tasks ...feed.Task, ) *Feed { - feed := &Feed{ + return &Feed{ id: id, valueDataType: valueDataType, ingester: ingester, storage: storage, tasks: tasks, } - - if len(whitelist) != 0 { - feed.whitelist = avl.NewTree() - for _, address := range whitelist { - feed.whitelist.Set(address, struct{}{}) - } - } - - return feed } func NewSingleValueFeed( id string, valueDataType string, - whitelist []string, tasks ...feed.Task, ) *Feed { return NewFeed( id, valueDataType, - whitelist, &single.ValueIngester{}, - storage.NewSimple(1), + simple.NewStorage(1), tasks..., ) } @@ -131,14 +117,6 @@ func (f *Feed) MarshalJSON() ([]byte, error) { return buf.Bytes(), nil } -func (f *Feed) HasAddressWhitelisted(address string) (isWhitelisted, feedHasWhitelist bool) { - if f.whitelist == nil { - return true, false - } - - return f.whitelist.Has(address), true -} - func (f *Feed) Tasks() []feed.Task { return f.tasks } diff --git a/examples/gno.land/p/demo/orkle/v1/feeds/static/gno.mod b/examples/gno.land/p/demo/orkle/v1/feeds/static/gno.mod new file mode 100644 index 00000000000..29ea1410d22 --- /dev/null +++ b/examples/gno.land/p/demo/orkle/v1/feeds/static/gno.mod @@ -0,0 +1,10 @@ +module gno.land/p/demo/orkle/v1/feeds/static + +require ( + gno.land/p/demo/orkle/v1/feed v0.0.0-latest + gno.land/p/demo/orkle/v1/ingesters/single v0.0.0-latest + gno.land/p/demo/orkle/v1/message v0.0.0-latest + gno.land/p/demo/orkle/v1/orkle v0.0.0-latest + gno.land/p/demo/orkle/v1/storage/simple v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) \ No newline at end of file diff --git a/examples/gno.land/p/demo/orkle/v1/ingester/gno.mod b/examples/gno.land/p/demo/orkle/v1/ingester/gno.mod new file mode 100644 index 00000000000..ac83d75ac91 --- /dev/null +++ b/examples/gno.land/p/demo/orkle/v1/ingester/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/orkle/v1/ingester diff --git a/agent2/p/orkle/ingester/type.go b/examples/gno.land/p/demo/orkle/v1/ingester/type.gno similarity index 100% rename from agent2/p/orkle/ingester/type.go rename to examples/gno.land/p/demo/orkle/v1/ingester/type.gno diff --git a/examples/gno.land/p/demo/orkle/v1/ingesters/single/gno.mod b/examples/gno.land/p/demo/orkle/v1/ingesters/single/gno.mod new file mode 100644 index 00000000000..6dc93c64c74 --- /dev/null +++ b/examples/gno.land/p/demo/orkle/v1/ingesters/single/gno.mod @@ -0,0 +1,6 @@ +module gno.land/p/demo/orkle/v1/ingesters/single + +require ( + gno.land/p/demo/orkle/v1/ingester v0.0.0-latest + gno.land/p/demo/orkle/v1/orkle v0.0.0-latest +) \ No newline at end of file diff --git a/agent2/p/orkle/ingester/single/ingester.go b/examples/gno.land/p/demo/orkle/v1/ingesters/single/ingester.gno similarity index 80% rename from agent2/p/orkle/ingester/single/ingester.go rename to examples/gno.land/p/demo/orkle/v1/ingesters/single/ingester.gno index 86763c83164..a82e2f80620 100644 --- a/agent2/p/orkle/ingester/single/ingester.go +++ b/examples/gno.land/p/demo/orkle/v1/ingesters/single/ingester.gno @@ -1,8 +1,8 @@ package single import ( - "github.com/gnolang/gno/agent2/p/orkle" - "github.com/gnolang/gno/agent2/p/orkle/ingester" + "gno.land/p/demo/orkle/v1/ingester" + "gno.land/p/demo/orkle/v1/orkle" ) type ValueIngester struct { diff --git a/examples/gno.land/p/demo/orkle/v1/message/gno.mod b/examples/gno.land/p/demo/orkle/v1/message/gno.mod new file mode 100644 index 00000000000..917e73a312e --- /dev/null +++ b/examples/gno.land/p/demo/orkle/v1/message/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/orkle/v1/message diff --git a/agent2/p/orkle/message/parse.go b/examples/gno.land/p/demo/orkle/v1/message/parse.gno similarity index 74% rename from agent2/p/orkle/message/parse.go rename to examples/gno.land/p/demo/orkle/v1/message/parse.gno index ec54caf03ab..c459ce32f46 100644 --- a/agent2/p/orkle/message/parse.go +++ b/examples/gno.land/p/demo/orkle/v1/message/parse.gno @@ -2,14 +2,6 @@ package message import "strings" -type FuncType string - -const ( - FuncTypeIngest FuncType = "ingest" - FuncTypeCommit FuncType = "commit" - FuncTypeRequest FuncType = "request" -) - func ParseFunc(rawMsg string) (FuncType, string) { msgParts := strings.SplitN(rawMsg, ",", 2) if len(msgParts) < 2 { diff --git a/examples/gno.land/p/demo/orkle/v1/message/type.gno b/examples/gno.land/p/demo/orkle/v1/message/type.gno new file mode 100644 index 00000000000..252a6fbd532 --- /dev/null +++ b/examples/gno.land/p/demo/orkle/v1/message/type.gno @@ -0,0 +1,9 @@ +package message + +type FuncType string + +const ( + FuncTypeIngest FuncType = "ingest" + FuncTypeCommit FuncType = "commit" + FuncTypeRequest FuncType = "request" +) diff --git a/agent2/p/orkle/feed.go b/examples/gno.land/p/demo/orkle/v1/orkle/feed.gno similarity index 78% rename from agent2/p/orkle/feed.go rename to examples/gno.land/p/demo/orkle/v1/orkle/feed.gno index 0f25efe7d11..d805f4ec759 100644 --- a/agent2/p/orkle/feed.go +++ b/examples/gno.land/p/demo/orkle/v1/orkle/feed.gno @@ -1,8 +1,8 @@ package orkle import ( - "github.com/gnolang/gno/agent2/p/orkle/feed" - "github.com/gnolang/gno/agent2/p/orkle/message" + "gno.land/p/demo/orkle/v1/feed" + "gno.land/p/demo/orkle/v1/message" ) type Feed interface { diff --git a/examples/gno.land/p/demo/orkle/v1/orkle/gno.mod b/examples/gno.land/p/demo/orkle/v1/orkle/gno.mod new file mode 100644 index 00000000000..89b253d7159 --- /dev/null +++ b/examples/gno.land/p/demo/orkle/v1/orkle/gno.mod @@ -0,0 +1,9 @@ +module gno.land/p/demo/orkle/v1/orkle + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/orkle/v1/agent v0.0.0-latest + gno.land/p/demo/orkle/v1/feed v0.0.0-latest + gno.land/p/demo/orkle/v1/ingester v0.0.0-latest + gno.land/p/demo/orkle/v1/message v0.0.0-latest +) \ No newline at end of file diff --git a/agent2/p/orkle/ingester.go b/examples/gno.land/p/demo/orkle/v1/orkle/ingester.gno similarity index 76% rename from agent2/p/orkle/ingester.go rename to examples/gno.land/p/demo/orkle/v1/orkle/ingester.gno index 3b36ef4a60e..270999dd9e6 100644 --- a/agent2/p/orkle/ingester.go +++ b/examples/gno.land/p/demo/orkle/v1/orkle/ingester.gno @@ -1,6 +1,6 @@ package orkle -import "github.com/gnolang/gno/agent2/p/orkle/ingester" +import "gno.land/p/demo/orkle/v1/ingester" type Ingester interface { Type() ingester.Type diff --git a/agent2/p/orkle/instance.go b/examples/gno.land/p/demo/orkle/v1/orkle/instance.gno similarity index 95% rename from agent2/p/orkle/instance.go rename to examples/gno.land/p/demo/orkle/v1/orkle/instance.gno index 3014509cecc..0844f69b31d 100644 --- a/agent2/p/orkle/instance.go +++ b/examples/gno.land/p/demo/orkle/v1/orkle/instance.gno @@ -1,13 +1,13 @@ package orkle import ( + "std" "strings" - "github.com/gnolang/gno/agent2/p/orkle/agent" - "github.com/gnolang/gno/agent2/p/orkle/feed" - "github.com/gnolang/gno/agent2/p/orkle/message" "gno.land/p/demo/avl" - "gno.land/p/demo/std" + "gno.land/p/demo/orkle/v1/agent" + "gno.land/p/demo/orkle/v1/feed" + "gno.land/p/demo/orkle/v1/message" ) type Instance struct { diff --git a/agent2/p/orkle/storage.go b/examples/gno.land/p/demo/orkle/v1/orkle/storage.gno similarity index 68% rename from agent2/p/orkle/storage.go rename to examples/gno.land/p/demo/orkle/v1/orkle/storage.gno index 258128158f6..1d3fc3b4f81 100644 --- a/agent2/p/orkle/storage.go +++ b/examples/gno.land/p/demo/orkle/v1/orkle/storage.gno @@ -1,6 +1,6 @@ package orkle -import "github.com/gnolang/gno/agent2/p/orkle/feed" +import "gno.land/p/demo/orkle/v1/feed" type Storage interface { Put(value string) diff --git a/agent2/p/orkle/whitelist.go b/examples/gno.land/p/demo/orkle/v1/orkle/whitelist.gno similarity index 100% rename from agent2/p/orkle/whitelist.go rename to examples/gno.land/p/demo/orkle/v1/orkle/whitelist.gno diff --git a/examples/gno.land/p/demo/orkle/v1/storage/simple/gno.mod b/examples/gno.land/p/demo/orkle/v1/storage/simple/gno.mod new file mode 100644 index 00000000000..1d363727d88 --- /dev/null +++ b/examples/gno.land/p/demo/orkle/v1/storage/simple/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/orkle/v1/storage/simple + +require gno.land/p/demo/orkle/v1/feed v0.0.0-latest \ No newline at end of file diff --git a/agent2/p/orkle/storage/simple.go b/examples/gno.land/p/demo/orkle/v1/storage/simple/storage.gno similarity index 59% rename from agent2/p/orkle/storage/simple.go rename to examples/gno.land/p/demo/orkle/v1/storage/simple/storage.gno index 77cb80a43e5..6a4de1f7450 100644 --- a/agent2/p/orkle/storage/simple.go +++ b/examples/gno.land/p/demo/orkle/v1/storage/simple/storage.gno @@ -1,30 +1,30 @@ -package storage +package simple import ( "time" - "github.com/gnolang/gno/agent2/p/orkle/feed" + "gno.land/p/demo/orkle/v1/feed" ) -type Simple struct { +type Storage struct { values []feed.Value maxValues int } -func NewSimple(maxValues int) *Simple { - return &Simple{ +func NewStorage(maxValues int) *Storage { + return &Storage{ maxValues: maxValues, } } -func (s *Simple) Put(value string) { +func (s *Storage) Put(value string) { s.values = append(s.values, feed.Value{String: value, Time: time.Now()}) if len(s.values) > s.maxValues && !(len(s.values) == 1 && s.maxValues == 0) { s.values = s.values[1:] } } -func (s *Simple) GetLatest() feed.Value { +func (s *Storage) GetLatest() feed.Value { if len(s.values) == 0 { return feed.Value{} } @@ -32,6 +32,6 @@ func (s *Simple) GetLatest() feed.Value { return s.values[len(s.values)-1] } -func (s *Simple) GetHistory() []feed.Value { +func (s *Storage) GetHistory() []feed.Value { return s.values } diff --git a/examples/gno.land/r/gnoland/gh/contract.gno b/examples/gno.land/r/gnoland/gh/contract.gno deleted file mode 100644 index 425de5bc329..00000000000 --- a/examples/gno.land/r/gnoland/gh/contract.gno +++ /dev/null @@ -1,105 +0,0 @@ -package gh - -import ( - "bufio" - "bytes" - "std" - - "gno.land/p/demo/agent/agent" - ghTask "gno.land/p/demo/agent/github/verification/task" - "gno.land/p/demo/agent/tasks/singular" - "gno.land/p/demo/avl" -) - -const ( - // TODO: set this to the address of the gnorkle agent that is authorized to submit verification tasks. - authorizedAgentAddress string = "" - verified = "OK" -) - -var ( - handleToAddress = avl.NewTree() - addressToHandle = avl.NewTree() - gnorkleInstance *agent.Instance -) - -func init() { - gnorkleInstance = agent.Init(std.GetOrigCaller()) -} - -// This is a callback function that is invoked at the end of the GnorkleAgentSubmitAction execution. -// It only cares about submissions because we want to save the result of the verification and clean -// up the finished task. -func updateVerifiedGHData(function string, task agent.Task) { - if function != agent.FunctionSubmit { - return - } - - result, hasResult := task.GetResult() - if !hasResult { - return - } - - if result.Value != verified { - return - } - - definition, ok := task.Definition().(ghTask.Definition) - if !ok { - panic("unexpected task definition of type " + definition.Type()) - } - - handleToAddress.Set(definition.Handle, definition.Address) - addressToHandle.Set(definition.Address, definition.Handle) - - // It's been verified so clean it up. - gnorkleInstance.RemoveTask(task.ID()) - - // TODO: should it also clean up tasks that failed to verify? -} - -// This is called by the gnorkle agent. The payload contains a function and optional data. -// In the case of this contract, it should only be called with payloads of: -// - request (returns all verification tasks in the queue) -// - submit,, (submits a verification result for the given task id where data is "OK" or not) -func GnorkleAgentSubmitAction(payload string) string { - return gnorkleInstance.HandleRequest(payload, updateVerifiedGHData) -} - -// RequestVerification will request that the gnorkle agent verify the given handle/address pair. -// A new tasks is created to be processed by the agent and verify there is a github repo of -// github.com//gno-verification with a file "address.txt" that contains the given address. -func RequestVerification(handle, address string) { - if address == "" { - address = string(std.GetOrigCaller()) - } - - gnorkleInstance.AddTask( - singular.NewTask( - handle, - ghTask.Definition{Handle: handle, Address: address}, - authorizedAgentAddress, - ), - ) -} - -// Render will return all of the verified handle -> gno address pairs. -func Render(_ string) string { - buf := new(bytes.Buffer) - w := bufio.NewWriter(buf) - w.WriteString(`{"verified":{`) - first := true - handleToAddress.Iterate("", "", func(key string, value interface{}) bool { - if !first { - w.WriteString(",") - } - - w.WriteString(`"` + key + `":"` + value.(string) + `"`) - first = false - return true - }) - - w.WriteString(`}}`) - w.Flush() - return buf.String() -} diff --git a/examples/gno.land/r/gnoland/gh/gno.mod b/examples/gno.land/r/gnoland/gh/gno.mod deleted file mode 100644 index b5b87fd8459..00000000000 --- a/examples/gno.land/r/gnoland/gh/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/r/gnoland/gh diff --git a/agent2/r/gh/contract.go b/examples/gno.land/r/gnoland/ghverify/contract.gno similarity index 88% rename from agent2/r/gh/contract.go rename to examples/gno.land/r/gnoland/ghverify/contract.gno index c56023dc2aa..73fcff7152b 100644 --- a/agent2/r/gh/contract.go +++ b/examples/gno.land/r/gnoland/ghverify/contract.gno @@ -1,11 +1,12 @@ -package gh +package ghverify import ( - "github.com/gnolang/gno/agent2/p/orkle" - "github.com/gnolang/gno/agent2/p/orkle/feed/static" - "github.com/gnolang/gno/agent2/p/orkle/message" + "std" + "gno.land/p/demo/avl" - "gno.land/p/demo/std" + "gno.land/p/demo/orkle/v1/feeds/static" + "gno.land/p/demo/orkle/v1/message" + "gno.land/p/demo/orkle/v1/orkle" ) const ( diff --git a/examples/gno.land/r/gnoland/ghverify/gno.mod b/examples/gno.land/r/gnoland/ghverify/gno.mod new file mode 100644 index 00000000000..e414233092a --- /dev/null +++ b/examples/gno.land/r/gnoland/ghverify/gno.mod @@ -0,0 +1,8 @@ +module gno.land/r/gnoland/ghverify + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/orkle/v1/feeds/static v0.0.0-latest + gno.land/p/demo/orkle/v1/message v0.0.0-latest + gno.land/p/demo/orkle/v1/orkle v0.0.0-latest +) \ No newline at end of file diff --git a/agent2/r/gh/task.go b/examples/gno.land/r/gnoland/ghverify/task.gno similarity index 97% rename from agent2/r/gh/task.go rename to examples/gno.land/r/gnoland/ghverify/task.gno index d7f74828455..f49a174fd12 100644 --- a/agent2/r/gh/task.go +++ b/examples/gno.land/r/gnoland/ghverify/task.gno @@ -1,4 +1,4 @@ -package gh +package ghverify import ( "bufio" diff --git a/go.mod b/go.mod index 3719fb3b361..9681e15f241 100644 --- a/go.mod +++ b/go.mod @@ -71,7 +71,3 @@ require ( golang.org/x/text v0.14.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) - -replace ( - gno.land/p/demo => ./agent/devmock -) From cd63af02397b2875242861d2c0a24a774333821a Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 23 Jan 2024 10:56:23 -0800 Subject: [PATCH 15/48] tidy go mod --- go.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/go.mod b/go.mod index 9681e15f241..599396eee99 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,6 @@ require ( github.com/rs/cors v1.10.1 github.com/stretchr/testify v1.8.4 github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c - gno.land/p/demo v0.0.0-00010101000000-000000000000 go.etcd.io/bbolt v1.3.8 go.uber.org/multierr v1.9.0 golang.org/x/crypto v0.15.0 From c91084d8a6d3291d3387a822747cdaa8562ae6c0 Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 23 Jan 2024 13:13:19 -0800 Subject: [PATCH 16/48] rename orkle -> gnorkle --- .../demo/{orkle => gnorkle}/v1/agent/gno.mod | 2 +- .../{orkle => gnorkle}/v1/agent/whitelist.gno | 0 .../gno.land/p/demo/gnorkle/v1/feed/gno.mod | 1 + .../demo/{orkle => gnorkle}/v1/feed/task.gno | 0 .../demo/{orkle => gnorkle}/v1/feed/type.gno | 0 .../demo/{orkle => gnorkle}/v1/feed/value.gno | 0 .../v1/feeds/static/feed.gno | 18 ++++++++-------- .../p/demo/gnorkle/v1/feeds/static/gno.mod | 10 +++++++++ .../v1/orkle => gnorkle/v1/gnorkle}/feed.gno | 6 +++--- .../p/demo/gnorkle/v1/gnorkle/gno.mod | 9 ++++++++ .../orkle => gnorkle/v1/gnorkle}/ingester.gno | 4 ++-- .../orkle => gnorkle/v1/gnorkle}/instance.gno | 8 +++---- .../orkle => gnorkle/v1/gnorkle}/storage.gno | 4 ++-- .../v1/gnorkle}/whitelist.gno | 2 +- .../p/demo/gnorkle/v1/ingester/gno.mod | 1 + .../{orkle => gnorkle}/v1/ingester/type.gno | 0 .../demo/gnorkle/v1/ingesters/single/gno.mod | 6 ++++++ .../v1/ingesters/single/ingester.gno | 6 +++--- .../p/demo/gnorkle/v1/message/gno.mod | 1 + .../{orkle => gnorkle}/v1/message/parse.gno | 0 .../{orkle => gnorkle}/v1/message/type.gno | 0 .../p/demo/gnorkle/v1/storage/simple/gno.mod | 3 +++ .../v1/storage/simple/storage.gno | 2 +- .../gno.land/p/demo/orkle/v1/feed/gno.mod | 1 - .../p/demo/orkle/v1/feeds/static/gno.mod | 10 --------- .../gno.land/p/demo/orkle/v1/ingester/gno.mod | 1 - .../p/demo/orkle/v1/ingesters/single/gno.mod | 6 ------ .../gno.land/p/demo/orkle/v1/message/gno.mod | 1 - .../gno.land/p/demo/orkle/v1/orkle/gno.mod | 9 -------- .../p/demo/orkle/v1/storage/simple/gno.mod | 3 --- .../gno.land/r/gnoland/ghverify/contract.gno | 21 +++++++++++-------- examples/gno.land/r/gnoland/ghverify/gno.mod | 6 +++--- 32 files changed, 72 insertions(+), 69 deletions(-) rename examples/gno.land/p/demo/{orkle => gnorkle}/v1/agent/gno.mod (51%) rename examples/gno.land/p/demo/{orkle => gnorkle}/v1/agent/whitelist.gno (100%) create mode 100644 examples/gno.land/p/demo/gnorkle/v1/feed/gno.mod rename examples/gno.land/p/demo/{orkle => gnorkle}/v1/feed/task.gno (100%) rename examples/gno.land/p/demo/{orkle => gnorkle}/v1/feed/type.gno (100%) rename examples/gno.land/p/demo/{orkle => gnorkle}/v1/feed/value.gno (100%) rename examples/gno.land/p/demo/{orkle => gnorkle}/v1/feeds/static/feed.gno (86%) create mode 100644 examples/gno.land/p/demo/gnorkle/v1/feeds/static/gno.mod rename examples/gno.land/p/demo/{orkle/v1/orkle => gnorkle/v1/gnorkle}/feed.gno (79%) create mode 100644 examples/gno.land/p/demo/gnorkle/v1/gnorkle/gno.mod rename examples/gno.land/p/demo/{orkle/v1/orkle => gnorkle/v1/gnorkle}/ingester.gno (73%) rename examples/gno.land/p/demo/{orkle/v1/orkle => gnorkle/v1/gnorkle}/instance.gno (96%) rename examples/gno.land/p/demo/{orkle/v1/orkle => gnorkle/v1/gnorkle}/storage.gno (63%) rename examples/gno.land/p/demo/{orkle/v1/orkle => gnorkle/v1/gnorkle}/whitelist.gno (99%) create mode 100644 examples/gno.land/p/demo/gnorkle/v1/ingester/gno.mod rename examples/gno.land/p/demo/{orkle => gnorkle}/v1/ingester/type.gno (100%) create mode 100644 examples/gno.land/p/demo/gnorkle/v1/ingesters/single/gno.mod rename examples/gno.land/p/demo/{orkle => gnorkle}/v1/ingesters/single/ingester.gno (62%) create mode 100644 examples/gno.land/p/demo/gnorkle/v1/message/gno.mod rename examples/gno.land/p/demo/{orkle => gnorkle}/v1/message/parse.gno (100%) rename examples/gno.land/p/demo/{orkle => gnorkle}/v1/message/type.gno (100%) create mode 100644 examples/gno.land/p/demo/gnorkle/v1/storage/simple/gno.mod rename examples/gno.land/p/demo/{orkle => gnorkle}/v1/storage/simple/storage.gno (94%) delete mode 100644 examples/gno.land/p/demo/orkle/v1/feed/gno.mod delete mode 100644 examples/gno.land/p/demo/orkle/v1/feeds/static/gno.mod delete mode 100644 examples/gno.land/p/demo/orkle/v1/ingester/gno.mod delete mode 100644 examples/gno.land/p/demo/orkle/v1/ingesters/single/gno.mod delete mode 100644 examples/gno.land/p/demo/orkle/v1/message/gno.mod delete mode 100644 examples/gno.land/p/demo/orkle/v1/orkle/gno.mod delete mode 100644 examples/gno.land/p/demo/orkle/v1/storage/simple/gno.mod diff --git a/examples/gno.land/p/demo/orkle/v1/agent/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/agent/gno.mod similarity index 51% rename from examples/gno.land/p/demo/orkle/v1/agent/gno.mod rename to examples/gno.land/p/demo/gnorkle/v1/agent/gno.mod index 72a562ee19f..1e1a03862d5 100644 --- a/examples/gno.land/p/demo/orkle/v1/agent/gno.mod +++ b/examples/gno.land/p/demo/gnorkle/v1/agent/gno.mod @@ -1,3 +1,3 @@ -module gno.land/p/demo/orkle/v1/agent +module gno.land/p/demo/gnorkle/v1/agent require gno.land/p/demo/avl v0.0.0-latest \ No newline at end of file diff --git a/examples/gno.land/p/demo/orkle/v1/agent/whitelist.gno b/examples/gno.land/p/demo/gnorkle/v1/agent/whitelist.gno similarity index 100% rename from examples/gno.land/p/demo/orkle/v1/agent/whitelist.gno rename to examples/gno.land/p/demo/gnorkle/v1/agent/whitelist.gno diff --git a/examples/gno.land/p/demo/gnorkle/v1/feed/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/feed/gno.mod new file mode 100644 index 00000000000..3ae0ac98619 --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/v1/feed/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/gnorkle/v1/feed \ No newline at end of file diff --git a/examples/gno.land/p/demo/orkle/v1/feed/task.gno b/examples/gno.land/p/demo/gnorkle/v1/feed/task.gno similarity index 100% rename from examples/gno.land/p/demo/orkle/v1/feed/task.gno rename to examples/gno.land/p/demo/gnorkle/v1/feed/task.gno diff --git a/examples/gno.land/p/demo/orkle/v1/feed/type.gno b/examples/gno.land/p/demo/gnorkle/v1/feed/type.gno similarity index 100% rename from examples/gno.land/p/demo/orkle/v1/feed/type.gno rename to examples/gno.land/p/demo/gnorkle/v1/feed/type.gno diff --git a/examples/gno.land/p/demo/orkle/v1/feed/value.gno b/examples/gno.land/p/demo/gnorkle/v1/feed/value.gno similarity index 100% rename from examples/gno.land/p/demo/orkle/v1/feed/value.gno rename to examples/gno.land/p/demo/gnorkle/v1/feed/value.gno diff --git a/examples/gno.land/p/demo/orkle/v1/feeds/static/feed.gno b/examples/gno.land/p/demo/gnorkle/v1/feeds/static/feed.gno similarity index 86% rename from examples/gno.land/p/demo/orkle/v1/feeds/static/feed.gno rename to examples/gno.land/p/demo/gnorkle/v1/feeds/static/feed.gno index e3af0eedad0..d01d4db1cee 100644 --- a/examples/gno.land/p/demo/orkle/v1/feeds/static/feed.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/feeds/static/feed.gno @@ -4,11 +4,11 @@ import ( "bufio" "bytes" - "gno.land/p/demo/orkle/v1/feed" - "gno.land/p/demo/orkle/v1/ingesters/single" - "gno.land/p/demo/orkle/v1/message" - "gno.land/p/demo/orkle/v1/orkle" - "gno.land/p/demo/orkle/v1/storage/simple" + "gno.land/p/demo/gnorkle/v1/feed" + "gno.land/p/demo/gnorkle/v1/gnorkle" + "gno.land/p/demo/gnorkle/v1/ingesters/single" + "gno.land/p/demo/gnorkle/v1/message" + "gno.land/p/demo/gnorkle/v1/storage/simple" "gno.land/p/demo/ufmt" ) @@ -16,16 +16,16 @@ type Feed struct { id string isLocked bool valueDataType string - ingester orkle.Ingester - storage orkle.Storage + ingester gnorkle.Ingester + storage gnorkle.Storage tasks []feed.Task } func NewFeed( id string, valueDataType string, - ingester orkle.Ingester, - storage orkle.Storage, + ingester gnorkle.Ingester, + storage gnorkle.Storage, tasks ...feed.Task, ) *Feed { return &Feed{ diff --git a/examples/gno.land/p/demo/gnorkle/v1/feeds/static/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/feeds/static/gno.mod new file mode 100644 index 00000000000..c0012b5a689 --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/v1/feeds/static/gno.mod @@ -0,0 +1,10 @@ +module gno.land/p/demo/gnorkle/v1/feeds/static + +require ( + gno.land/p/demo/gnorkle/v1/feed v0.0.0-latest + gno.land/p/demo/gnorkle/v1/ingesters/single v0.0.0-latest + gno.land/p/demo/gnorkle/v1/message v0.0.0-latest + gno.land/p/demo/gnorkle/v1/gnorkle v0.0.0-latest + gno.land/p/demo/gnorkle/v1/storage/simple v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) \ No newline at end of file diff --git a/examples/gno.land/p/demo/orkle/v1/orkle/feed.gno b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/feed.gno similarity index 79% rename from examples/gno.land/p/demo/orkle/v1/orkle/feed.gno rename to examples/gno.land/p/demo/gnorkle/v1/gnorkle/feed.gno index d805f4ec759..63a0c19dfd9 100644 --- a/examples/gno.land/p/demo/orkle/v1/orkle/feed.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/feed.gno @@ -1,8 +1,8 @@ -package orkle +package gnorkle import ( - "gno.land/p/demo/orkle/v1/feed" - "gno.land/p/demo/orkle/v1/message" + "gno.land/p/demo/gnorkle/v1/feed" + "gno.land/p/demo/gnorkle/v1/message" ) type Feed interface { diff --git a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/gno.mod new file mode 100644 index 00000000000..2877b3e23e3 --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/gno.mod @@ -0,0 +1,9 @@ +module gno.land/p/demo/gnorkle/v1/gnorkle + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/gnorkle/v1/agent v0.0.0-latest + gno.land/p/demo/gnorkle/v1/feed v0.0.0-latest + gno.land/p/demo/gnorkle/v1/ingester v0.0.0-latest + gno.land/p/demo/gnorkle/v1/message v0.0.0-latest +) \ No newline at end of file diff --git a/examples/gno.land/p/demo/orkle/v1/orkle/ingester.gno b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/ingester.gno similarity index 73% rename from examples/gno.land/p/demo/orkle/v1/orkle/ingester.gno rename to examples/gno.land/p/demo/gnorkle/v1/gnorkle/ingester.gno index 270999dd9e6..d55101335ab 100644 --- a/examples/gno.land/p/demo/orkle/v1/orkle/ingester.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/ingester.gno @@ -1,6 +1,6 @@ -package orkle +package gnorkle -import "gno.land/p/demo/orkle/v1/ingester" +import "gno.land/p/demo/gnorkle/v1/ingester" type Ingester interface { Type() ingester.Type diff --git a/examples/gno.land/p/demo/orkle/v1/orkle/instance.gno b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/instance.gno similarity index 96% rename from examples/gno.land/p/demo/orkle/v1/orkle/instance.gno rename to examples/gno.land/p/demo/gnorkle/v1/gnorkle/instance.gno index 0844f69b31d..a694078132c 100644 --- a/examples/gno.land/p/demo/orkle/v1/orkle/instance.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/instance.gno @@ -1,13 +1,13 @@ -package orkle +package gnorkle import ( "std" "strings" "gno.land/p/demo/avl" - "gno.land/p/demo/orkle/v1/agent" - "gno.land/p/demo/orkle/v1/feed" - "gno.land/p/demo/orkle/v1/message" + "gno.land/p/demo/gnorkle/v1/agent" + "gno.land/p/demo/gnorkle/v1/feed" + "gno.land/p/demo/gnorkle/v1/message" ) type Instance struct { diff --git a/examples/gno.land/p/demo/orkle/v1/orkle/storage.gno b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/storage.gno similarity index 63% rename from examples/gno.land/p/demo/orkle/v1/orkle/storage.gno rename to examples/gno.land/p/demo/gnorkle/v1/gnorkle/storage.gno index 1d3fc3b4f81..c32d2e4dcae 100644 --- a/examples/gno.land/p/demo/orkle/v1/orkle/storage.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/storage.gno @@ -1,6 +1,6 @@ -package orkle +package gnorkle -import "gno.land/p/demo/orkle/v1/feed" +import "gno.land/p/demo/gnorkle/v1/feed" type Storage interface { Put(value string) diff --git a/examples/gno.land/p/demo/orkle/v1/orkle/whitelist.gno b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/whitelist.gno similarity index 99% rename from examples/gno.land/p/demo/orkle/v1/orkle/whitelist.gno rename to examples/gno.land/p/demo/gnorkle/v1/gnorkle/whitelist.gno index 62fa8fa3b34..fa1d08b2d34 100644 --- a/examples/gno.land/p/demo/orkle/v1/orkle/whitelist.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/whitelist.gno @@ -1,4 +1,4 @@ -package orkle +package gnorkle type Whitelist interface { ClearAddresses() diff --git a/examples/gno.land/p/demo/gnorkle/v1/ingester/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/ingester/gno.mod new file mode 100644 index 00000000000..cde40e6f3df --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/v1/ingester/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/gnorkle/v1/ingester diff --git a/examples/gno.land/p/demo/orkle/v1/ingester/type.gno b/examples/gno.land/p/demo/gnorkle/v1/ingester/type.gno similarity index 100% rename from examples/gno.land/p/demo/orkle/v1/ingester/type.gno rename to examples/gno.land/p/demo/gnorkle/v1/ingester/type.gno diff --git a/examples/gno.land/p/demo/gnorkle/v1/ingesters/single/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/ingesters/single/gno.mod new file mode 100644 index 00000000000..2dde83c1ccd --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/v1/ingesters/single/gno.mod @@ -0,0 +1,6 @@ +module gno.land/p/demo/gnorkle/v1/ingesters/single + +require ( + gno.land/p/demo/gnorkle/v1/ingester v0.0.0-latest + gno.land/p/demo/gnorkle/v1/gnorkle v0.0.0-latest +) \ No newline at end of file diff --git a/examples/gno.land/p/demo/orkle/v1/ingesters/single/ingester.gno b/examples/gno.land/p/demo/gnorkle/v1/ingesters/single/ingester.gno similarity index 62% rename from examples/gno.land/p/demo/orkle/v1/ingesters/single/ingester.gno rename to examples/gno.land/p/demo/gnorkle/v1/ingesters/single/ingester.gno index a82e2f80620..de754f8cda8 100644 --- a/examples/gno.land/p/demo/orkle/v1/ingesters/single/ingester.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/ingesters/single/ingester.gno @@ -1,8 +1,8 @@ package single import ( - "gno.land/p/demo/orkle/v1/ingester" - "gno.land/p/demo/orkle/v1/orkle" + "gno.land/p/demo/gnorkle/v1/gnorkle" + "gno.land/p/demo/gnorkle/v1/ingester" ) type ValueIngester struct { @@ -18,6 +18,6 @@ func (i *ValueIngester) Ingest(value, providerAddress string) bool { return true } -func (i *ValueIngester) CommitValue(valueStorer orkle.Storage, providerAddress string) { +func (i *ValueIngester) CommitValue(valueStorer gnorkle.Storage, providerAddress string) { valueStorer.Put(i.value) } diff --git a/examples/gno.land/p/demo/gnorkle/v1/message/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/message/gno.mod new file mode 100644 index 00000000000..7ae80e4cc35 --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/v1/message/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/gnorkle/v1/message diff --git a/examples/gno.land/p/demo/orkle/v1/message/parse.gno b/examples/gno.land/p/demo/gnorkle/v1/message/parse.gno similarity index 100% rename from examples/gno.land/p/demo/orkle/v1/message/parse.gno rename to examples/gno.land/p/demo/gnorkle/v1/message/parse.gno diff --git a/examples/gno.land/p/demo/orkle/v1/message/type.gno b/examples/gno.land/p/demo/gnorkle/v1/message/type.gno similarity index 100% rename from examples/gno.land/p/demo/orkle/v1/message/type.gno rename to examples/gno.land/p/demo/gnorkle/v1/message/type.gno diff --git a/examples/gno.land/p/demo/gnorkle/v1/storage/simple/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/storage/simple/gno.mod new file mode 100644 index 00000000000..06cdfd723be --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/v1/storage/simple/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/gnorkle/v1/storage/simple + +require gno.land/p/demo/gnorkle/v1/feed v0.0.0-latest \ No newline at end of file diff --git a/examples/gno.land/p/demo/orkle/v1/storage/simple/storage.gno b/examples/gno.land/p/demo/gnorkle/v1/storage/simple/storage.gno similarity index 94% rename from examples/gno.land/p/demo/orkle/v1/storage/simple/storage.gno rename to examples/gno.land/p/demo/gnorkle/v1/storage/simple/storage.gno index 6a4de1f7450..ddfc2b8db73 100644 --- a/examples/gno.land/p/demo/orkle/v1/storage/simple/storage.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/storage/simple/storage.gno @@ -3,7 +3,7 @@ package simple import ( "time" - "gno.land/p/demo/orkle/v1/feed" + "gno.land/p/demo/gnorkle/v1/feed" ) type Storage struct { diff --git a/examples/gno.land/p/demo/orkle/v1/feed/gno.mod b/examples/gno.land/p/demo/orkle/v1/feed/gno.mod deleted file mode 100644 index 6ac03a167c9..00000000000 --- a/examples/gno.land/p/demo/orkle/v1/feed/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/orkle/v1/feed \ No newline at end of file diff --git a/examples/gno.land/p/demo/orkle/v1/feeds/static/gno.mod b/examples/gno.land/p/demo/orkle/v1/feeds/static/gno.mod deleted file mode 100644 index 29ea1410d22..00000000000 --- a/examples/gno.land/p/demo/orkle/v1/feeds/static/gno.mod +++ /dev/null @@ -1,10 +0,0 @@ -module gno.land/p/demo/orkle/v1/feeds/static - -require ( - gno.land/p/demo/orkle/v1/feed v0.0.0-latest - gno.land/p/demo/orkle/v1/ingesters/single v0.0.0-latest - gno.land/p/demo/orkle/v1/message v0.0.0-latest - gno.land/p/demo/orkle/v1/orkle v0.0.0-latest - gno.land/p/demo/orkle/v1/storage/simple v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) \ No newline at end of file diff --git a/examples/gno.land/p/demo/orkle/v1/ingester/gno.mod b/examples/gno.land/p/demo/orkle/v1/ingester/gno.mod deleted file mode 100644 index ac83d75ac91..00000000000 --- a/examples/gno.land/p/demo/orkle/v1/ingester/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/orkle/v1/ingester diff --git a/examples/gno.land/p/demo/orkle/v1/ingesters/single/gno.mod b/examples/gno.land/p/demo/orkle/v1/ingesters/single/gno.mod deleted file mode 100644 index 6dc93c64c74..00000000000 --- a/examples/gno.land/p/demo/orkle/v1/ingesters/single/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/p/demo/orkle/v1/ingesters/single - -require ( - gno.land/p/demo/orkle/v1/ingester v0.0.0-latest - gno.land/p/demo/orkle/v1/orkle v0.0.0-latest -) \ No newline at end of file diff --git a/examples/gno.land/p/demo/orkle/v1/message/gno.mod b/examples/gno.land/p/demo/orkle/v1/message/gno.mod deleted file mode 100644 index 917e73a312e..00000000000 --- a/examples/gno.land/p/demo/orkle/v1/message/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/orkle/v1/message diff --git a/examples/gno.land/p/demo/orkle/v1/orkle/gno.mod b/examples/gno.land/p/demo/orkle/v1/orkle/gno.mod deleted file mode 100644 index 89b253d7159..00000000000 --- a/examples/gno.land/p/demo/orkle/v1/orkle/gno.mod +++ /dev/null @@ -1,9 +0,0 @@ -module gno.land/p/demo/orkle/v1/orkle - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/orkle/v1/agent v0.0.0-latest - gno.land/p/demo/orkle/v1/feed v0.0.0-latest - gno.land/p/demo/orkle/v1/ingester v0.0.0-latest - gno.land/p/demo/orkle/v1/message v0.0.0-latest -) \ No newline at end of file diff --git a/examples/gno.land/p/demo/orkle/v1/storage/simple/gno.mod b/examples/gno.land/p/demo/orkle/v1/storage/simple/gno.mod deleted file mode 100644 index 1d363727d88..00000000000 --- a/examples/gno.land/p/demo/orkle/v1/storage/simple/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/p/demo/orkle/v1/storage/simple - -require gno.land/p/demo/orkle/v1/feed v0.0.0-latest \ No newline at end of file diff --git a/examples/gno.land/r/gnoland/ghverify/contract.gno b/examples/gno.land/r/gnoland/ghverify/contract.gno index 73fcff7152b..0207434e5e6 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract.gno @@ -4,9 +4,9 @@ import ( "std" "gno.land/p/demo/avl" - "gno.land/p/demo/orkle/v1/feeds/static" - "gno.land/p/demo/orkle/v1/message" - "gno.land/p/demo/orkle/v1/orkle" + "gno.land/p/demo/gnorkle/v1/feeds/static" + "gno.land/p/demo/gnorkle/v1/gnorkle" + "gno.land/p/demo/gnorkle/v1/message" ) const ( @@ -15,21 +15,21 @@ const ( ) var ( - oracle *orkle.Instance - postHandler postOrkleMessageHandler + oracle *gnorkle.Instance + postHandler postGnorkleMessageHandler handleToAddressMap = avl.NewTree() addressToHandleMap = avl.NewTree() ) func init() { - oracle = orkle.NewInstance() + oracle = gnorkle.NewInstance() oracle.AddToWhitelist("", []string{whitelistedAgentAddress}) } -type postOrkleMessageHandler struct{} +type postGnorkleMessageHandler struct{} -func (h postOrkleMessageHandler) Handle(i *orkle.Instance, funcType message.FuncType, feed orkle.Feed) { +func (h postGnorkleMessageHandler) Handle(i *gnorkle.Instance, funcType message.FuncType, feed gnorkle.Feed) { if funcType != message.FuncTypeIngest { return } @@ -70,6 +70,9 @@ func RequestVerification(githubHandle string) { ) } -func OrkleEntrypoint(message string) string { +func GnorkleEntrypoint(message string) string { return oracle.HandleMessage(message, postHandler) } + +func Render(_ string) string { +} diff --git a/examples/gno.land/r/gnoland/ghverify/gno.mod b/examples/gno.land/r/gnoland/ghverify/gno.mod index e414233092a..6f81566c614 100644 --- a/examples/gno.land/r/gnoland/ghverify/gno.mod +++ b/examples/gno.land/r/gnoland/ghverify/gno.mod @@ -2,7 +2,7 @@ module gno.land/r/gnoland/ghverify require ( gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/orkle/v1/feeds/static v0.0.0-latest - gno.land/p/demo/orkle/v1/message v0.0.0-latest - gno.land/p/demo/orkle/v1/orkle v0.0.0-latest + gno.land/p/demo/gnorkle/v1/feeds/static v0.0.0-latest + gno.land/p/demo/gnorkle/v1/message v0.0.0-latest + gno.land/p/demo/gnorkle/v1/gnorkle v0.0.0-latest ) \ No newline at end of file From cab8a038fa5e21cd9943bf65c7bf4ba7f8ee4e13 Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 23 Jan 2024 14:26:14 -0800 Subject: [PATCH 17/48] bug fixes and realm ownership --- .../p/demo/gnorkle/v1/feeds/static/feed.gno | 8 ++-- .../p/demo/gnorkle/v1/gnorkle/instance.gno | 7 +++- .../gno.land/r/gnoland/ghverify/contract.gno | 39 ++++++++++++++++--- examples/gno.land/r/gnoland/ghverify/gno.mod | 1 + 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/examples/gno.land/p/demo/gnorkle/v1/feeds/static/feed.gno b/examples/gno.land/p/demo/gnorkle/v1/feeds/static/feed.gno index d01d4db1cee..8d0640c4a31 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/feeds/static/feed.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/feeds/static/feed.gno @@ -26,7 +26,7 @@ func NewFeed( valueDataType string, ingester gnorkle.Ingester, storage gnorkle.Storage, - tasks ...feed.Task, + tasks []feed.Task, ) *Feed { return &Feed{ id: id, @@ -40,14 +40,14 @@ func NewFeed( func NewSingleValueFeed( id string, valueDataType string, - tasks ...feed.Task, + tasks []feed.Task, ) *Feed { return NewFeed( id, valueDataType, &single.ValueIngester{}, simple.NewStorage(1), - tasks..., + tasks, ) } @@ -92,7 +92,7 @@ func (f *Feed) MarshalJSON() ([]byte, error) { w.Write([]byte( `{"id":"` + f.id + - `","type":"` + ufmt.Sprintf("%d", f.Type()) + + `","type":"` + ufmt.Sprintf("%d", int(f.Type())) + `","value_type":"` + f.valueDataType + `","tasks":[`), ) diff --git a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/instance.gno b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/instance.gno index a694078132c..5c8f76ad074 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/instance.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/instance.gno @@ -32,7 +32,10 @@ func (i *Instance) AddFeeds(feeds ...Feed) { assertNonEmptyString(feed.ID()) i.feeds.Set( feed.ID(), - FeedWithWhitelist{Feed: feed}, + FeedWithWhitelist{ + Whitelist: new(agent.Whitelist), + Feed: feed, + }, ) } } @@ -71,7 +74,7 @@ func (i *Instance) HandleMessage(msg string, postHandler PostMessageHandler) str id, msg := message.ParseID(msg) feedWithWhitelist := i.getFeedWithWhitelist(id) - if addressIsWhitelisted(&i.whitelist, feedWithWhitelist, caller, nil) { + if !addressIsWhitelisted(&i.whitelist, feedWithWhitelist, caller, nil) { panic("caller not whitelisted") } diff --git a/examples/gno.land/r/gnoland/ghverify/contract.gno b/examples/gno.land/r/gnoland/ghverify/contract.gno index 0207434e5e6..30effb260ec 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract.gno @@ -4,17 +4,20 @@ import ( "std" "gno.land/p/demo/avl" + "gno.land/p/demo/gnorkle/v1/feed" "gno.land/p/demo/gnorkle/v1/feeds/static" "gno.land/p/demo/gnorkle/v1/gnorkle" "gno.land/p/demo/gnorkle/v1/message" ) const ( - verifiedResult = "OK" - whitelistedAgentAddress = "..." + verifiedResult = "OK" ) var ( + oracle *gnorkle.Instance + postHandler postGnorkleMessageHandler + ownerAddress = string(std.GetOrigCaller()) oracle *gnorkle.Instance postHandler postGnorkleMessageHandler @@ -24,7 +27,7 @@ var ( func init() { oracle = gnorkle.NewInstance() - oracle.AddToWhitelist("", []string{whitelistedAgentAddress}) + oracle.AddToWhitelist("", []string{ownerAddress}) } type postGnorkleMessageHandler struct{} @@ -64,8 +67,7 @@ func RequestVerification(githubHandle string) { static.NewSingleValueFeed( githubHandle, "string", - nil, - NewVerificationTask(string(std.GetOrigCaller()), githubHandle), + []feed.Task{NewVerificationTask(string(std.GetOrigCaller()), githubHandle)}, ), ) } @@ -74,5 +76,32 @@ func GnorkleEntrypoint(message string) string { return oracle.HandleMessage(message, postHandler) } +func SetOwner(owner string) { + if ownerAddress != string(std.GetOrigCaller()) { + panic("only the owner can set a new owner") + } + + ownerAddress = owner + + // In the context of this contract, the owner is the only one that can + // add new feeds to the oracle. + oracle.ClearWhitelist("") + oracle.AddToWhitelist("", []string{ownerAddress}) +} + func Render(_ string) string { + result := "{" + var appendComma bool + handleToAddressMap.Iterate("", "", func(handle string, address interface{}) bool { + if appendComma { + result += "," + } + + result += `"` + handle + `": "` + address.(string) + `"` + appendComma = true + + return true + }) + + return result + "}" } diff --git a/examples/gno.land/r/gnoland/ghverify/gno.mod b/examples/gno.land/r/gnoland/ghverify/gno.mod index 6f81566c614..19538911167 100644 --- a/examples/gno.land/r/gnoland/ghverify/gno.mod +++ b/examples/gno.land/r/gnoland/ghverify/gno.mod @@ -2,6 +2,7 @@ module gno.land/r/gnoland/ghverify require ( gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/gnorkle/v1/feed v0.0.0-latest gno.land/p/demo/gnorkle/v1/feeds/static v0.0.0-latest gno.land/p/demo/gnorkle/v1/message v0.0.0-latest gno.land/p/demo/gnorkle/v1/gnorkle v0.0.0-latest From 1ac9af80106ac68b299fbf65aaf77e6c150e0fb7 Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 23 Jan 2024 14:48:51 -0800 Subject: [PATCH 18/48] try to fix weird vcs sync issue --- examples/gno.land/r/gnoland/ghverify/contract.gno | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/gno.land/r/gnoland/ghverify/contract.gno b/examples/gno.land/r/gnoland/ghverify/contract.gno index 30effb260ec..8f80e0214cb 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract.gno @@ -15,9 +15,8 @@ const ( ) var ( - oracle *gnorkle.Instance - postHandler postGnorkleMessageHandler ownerAddress = string(std.GetOrigCaller()) + oracle *gnorkle.Instance postHandler postGnorkleMessageHandler From f73a94b8d7c5acd04f08e1352beb8dc5a4e80bc9 Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 23 Jan 2024 15:08:28 -0800 Subject: [PATCH 19/48] gno mod tidy --- examples/gno.land/p/demo/gnorkle/v1/agent/gno.mod | 2 +- examples/gno.land/p/demo/gnorkle/v1/feed/gno.mod | 2 +- examples/gno.land/p/demo/gnorkle/v1/feeds/static/gno.mod | 4 ++-- examples/gno.land/p/demo/gnorkle/v1/gnorkle/gno.mod | 2 +- examples/gno.land/p/demo/gnorkle/v1/ingesters/single/gno.mod | 4 ++-- examples/gno.land/p/demo/gnorkle/v1/storage/simple/gno.mod | 2 +- examples/gno.land/r/gnoland/ghverify/contract.gno | 5 ++--- examples/gno.land/r/gnoland/ghverify/gno.mod | 4 ++-- 8 files changed, 12 insertions(+), 13 deletions(-) diff --git a/examples/gno.land/p/demo/gnorkle/v1/agent/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/agent/gno.mod index 1e1a03862d5..3e8720ba385 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/agent/gno.mod +++ b/examples/gno.land/p/demo/gnorkle/v1/agent/gno.mod @@ -1,3 +1,3 @@ module gno.land/p/demo/gnorkle/v1/agent -require gno.land/p/demo/avl v0.0.0-latest \ No newline at end of file +require gno.land/p/demo/avl v0.0.0-latest diff --git a/examples/gno.land/p/demo/gnorkle/v1/feed/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/feed/gno.mod index 3ae0ac98619..a8bd934d666 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/feed/gno.mod +++ b/examples/gno.land/p/demo/gnorkle/v1/feed/gno.mod @@ -1 +1 @@ -module gno.land/p/demo/gnorkle/v1/feed \ No newline at end of file +module gno.land/p/demo/gnorkle/v1/feed diff --git a/examples/gno.land/p/demo/gnorkle/v1/feeds/static/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/feeds/static/gno.mod index c0012b5a689..60299be1e6d 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/feeds/static/gno.mod +++ b/examples/gno.land/p/demo/gnorkle/v1/feeds/static/gno.mod @@ -2,9 +2,9 @@ module gno.land/p/demo/gnorkle/v1/feeds/static require ( gno.land/p/demo/gnorkle/v1/feed v0.0.0-latest + gno.land/p/demo/gnorkle/v1/gnorkle v0.0.0-latest gno.land/p/demo/gnorkle/v1/ingesters/single v0.0.0-latest gno.land/p/demo/gnorkle/v1/message v0.0.0-latest - gno.land/p/demo/gnorkle/v1/gnorkle v0.0.0-latest gno.land/p/demo/gnorkle/v1/storage/simple v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest -) \ No newline at end of file +) diff --git a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/gno.mod index 2877b3e23e3..7ef1e02f659 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/gno.mod +++ b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/gno.mod @@ -6,4 +6,4 @@ require ( gno.land/p/demo/gnorkle/v1/feed v0.0.0-latest gno.land/p/demo/gnorkle/v1/ingester v0.0.0-latest gno.land/p/demo/gnorkle/v1/message v0.0.0-latest -) \ No newline at end of file +) diff --git a/examples/gno.land/p/demo/gnorkle/v1/ingesters/single/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/ingesters/single/gno.mod index 2dde83c1ccd..e803e22630b 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/ingesters/single/gno.mod +++ b/examples/gno.land/p/demo/gnorkle/v1/ingesters/single/gno.mod @@ -1,6 +1,6 @@ module gno.land/p/demo/gnorkle/v1/ingesters/single require ( - gno.land/p/demo/gnorkle/v1/ingester v0.0.0-latest gno.land/p/demo/gnorkle/v1/gnorkle v0.0.0-latest -) \ No newline at end of file + gno.land/p/demo/gnorkle/v1/ingester v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/gnorkle/v1/storage/simple/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/storage/simple/gno.mod index 06cdfd723be..eb593b72d4b 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/storage/simple/gno.mod +++ b/examples/gno.land/p/demo/gnorkle/v1/storage/simple/gno.mod @@ -1,3 +1,3 @@ module gno.land/p/demo/gnorkle/v1/storage/simple -require gno.land/p/demo/gnorkle/v1/feed v0.0.0-latest \ No newline at end of file +require gno.land/p/demo/gnorkle/v1/feed v0.0.0-latest diff --git a/examples/gno.land/r/gnoland/ghverify/contract.gno b/examples/gno.land/r/gnoland/ghverify/contract.gno index 8f80e0214cb..fc479905e9f 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract.gno @@ -16,9 +16,8 @@ const ( var ( ownerAddress = string(std.GetOrigCaller()) - - oracle *gnorkle.Instance - postHandler postGnorkleMessageHandler + oracle *gnorkle.Instance + postHandler postGnorkleMessageHandler handleToAddressMap = avl.NewTree() addressToHandleMap = avl.NewTree() diff --git a/examples/gno.land/r/gnoland/ghverify/gno.mod b/examples/gno.land/r/gnoland/ghverify/gno.mod index 19538911167..48098e2d907 100644 --- a/examples/gno.land/r/gnoland/ghverify/gno.mod +++ b/examples/gno.land/r/gnoland/ghverify/gno.mod @@ -4,6 +4,6 @@ require ( gno.land/p/demo/avl v0.0.0-latest gno.land/p/demo/gnorkle/v1/feed v0.0.0-latest gno.land/p/demo/gnorkle/v1/feeds/static v0.0.0-latest - gno.land/p/demo/gnorkle/v1/message v0.0.0-latest gno.land/p/demo/gnorkle/v1/gnorkle v0.0.0-latest -) \ No newline at end of file + gno.land/p/demo/gnorkle/v1/message v0.0.0-latest +) From 10e52defce92edf166e2658ee603455d84bd1b31 Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 23 Jan 2024 16:46:02 -0800 Subject: [PATCH 20/48] added godocs, comments, and minor changes --- .../p/demo/gnorkle/v1/agent/whitelist.gno | 9 +++++ .../gno.land/p/demo/gnorkle/v1/feed/task.gno | 2 ++ .../gno.land/p/demo/gnorkle/v1/feed/type.gno | 7 ++++ .../gno.land/p/demo/gnorkle/v1/feed/value.gno | 1 + .../p/demo/gnorkle/v1/feeds/static/feed.gno | 17 ++++++++++ .../p/demo/gnorkle/v1/gnorkle/feed.gno | 3 ++ .../p/demo/gnorkle/v1/gnorkle/ingester.gno | 2 ++ .../p/demo/gnorkle/v1/gnorkle/instance.gno | 34 +++++++++++++++---- .../p/demo/gnorkle/v1/gnorkle/storage.gno | 2 ++ .../p/demo/gnorkle/v1/gnorkle/whitelist.gno | 4 +++ .../p/demo/gnorkle/v1/ingester/type.gno | 3 ++ .../gnorkle/v1/ingesters/single/ingester.gno | 4 +++ .../p/demo/gnorkle/v1/message/parse.gno | 4 +++ .../p/demo/gnorkle/v1/message/type.gno | 10 ++++-- .../gnorkle/v1/storage/simple/storage.gno | 18 ++++++++-- 15 files changed, 109 insertions(+), 11 deletions(-) diff --git a/examples/gno.land/p/demo/gnorkle/v1/agent/whitelist.gno b/examples/gno.land/p/demo/gnorkle/v1/agent/whitelist.gno index d69004a1091..74d0d89853c 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/agent/whitelist.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/agent/whitelist.gno @@ -2,14 +2,18 @@ package agent import "gno.land/p/demo/avl" +// Whitelist manages whitelisted agent addresses. type Whitelist struct { store *avl.Tree } +// ClearAddresses removes all addresses from the whitelist and puts into a state +// that indicates it is moot and has no whitelist defined. func (m *Whitelist) ClearAddresses() { m.store = nil } +// AddAddresses adds the given addresses to the whitelist. func (m *Whitelist) AddAddresses(addresses []string) { if m.store == nil { m.store = avl.NewTree() @@ -20,6 +24,7 @@ func (m *Whitelist) AddAddresses(addresses []string) { } } +// RemoveAddress removes the given address from the whitelist if it exists. func (m *Whitelist) RemoveAddress(address string) { if m.store == nil { return @@ -28,10 +33,14 @@ func (m *Whitelist) RemoveAddress(address string) { m.store.Remove(address) } +// HasDefinition returns true if the whitelist has a definition. It retuns false if +// `ClearAddresses` has been called without any subsequent `AddAddresses` calls, or +// if `AddAddresses` has never been called. func (m Whitelist) HasDefinition() bool { return m.store != nil } +// HasAddress returns true if the given address is in the whitelist. func (m Whitelist) HasAddress(address string) bool { if m.store == nil { return false diff --git a/examples/gno.land/p/demo/gnorkle/v1/feed/task.gno b/examples/gno.land/p/demo/gnorkle/v1/feed/task.gno index e47cd3fca35..06dc52eca61 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/feed/task.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/feed/task.gno @@ -1,5 +1,7 @@ package feed +// Task is a unit of work that can be part of a `Feed` definition. Tasks +// are executed by agents. type Task interface { MarshalToJSON() ([]byte, error) } diff --git a/examples/gno.land/p/demo/gnorkle/v1/feed/type.gno b/examples/gno.land/p/demo/gnorkle/v1/feed/type.gno index aa6cd518a86..54db00b5d1b 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/feed/type.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/feed/type.gno @@ -1,9 +1,16 @@ package feed +// Type indicates the type of a feed. type Type int const ( + // TypeStatic indicates a feed cannot be changed once the first value is committed. TypeStatic Type = iota + // TypeContinuous indicates a feed can continuously ingest values and will publish + // a new value on request using the values it has ingested. TypeContinuous + // TypePeriodic indicates a feed can accept one or more values within a certain period + // and will proceed to commit these values at the end up each period to produce an + // aggregate value before starting a new period. TypePeriodic ) diff --git a/examples/gno.land/p/demo/gnorkle/v1/feed/value.gno b/examples/gno.land/p/demo/gnorkle/v1/feed/value.gno index 66e6b2fcef0..cad1221b116 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/feed/value.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/feed/value.gno @@ -2,6 +2,7 @@ package feed import "time" +// Value represents a value published by a feed. The `Time` is when the value was published. type Value struct { String string Time time.Time diff --git a/examples/gno.land/p/demo/gnorkle/v1/feeds/static/feed.gno b/examples/gno.land/p/demo/gnorkle/v1/feeds/static/feed.gno index 8d0640c4a31..f9eba3cb7bc 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/feeds/static/feed.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/feeds/static/feed.gno @@ -12,6 +12,7 @@ import ( "gno.land/p/demo/ufmt" ) +// Feed is a static feed. type Feed struct { id string isLocked bool @@ -21,6 +22,7 @@ type Feed struct { tasks []feed.Task } +// NewFeed creates a new static feed. func NewFeed( id string, valueDataType string, @@ -37,6 +39,8 @@ func NewFeed( } } +// NewSingleValueFeed is a convenience function for creating a static feed +// that autocommits a value after a single ingestion. func NewSingleValueFeed( id string, valueDataType string, @@ -51,14 +55,18 @@ func NewSingleValueFeed( ) } +// ID returns the feed's ID. func (f *Feed) ID() string { return f.id } +// Type returns the feed's type. func (f *Feed) Type() feed.Type { return feed.TypeStatic } +// Ingest ingests a message into the feed. It either adds the value to the ingester's +// pending values or commits the value to the storage. func (f *Feed) Ingest(funcType message.FuncType, msg, providerAddress string) { if f.isLocked { panic("feed locked") @@ -82,10 +90,16 @@ func (f *Feed) Ingest(funcType message.FuncType, msg, providerAddress string) { } } +// Value returns the feed's latest value, it's data type, and whether or not it can +// be safely consumed. In this case it uses `f.isLocked` because, this being a static +// feed, it will only ever have one value; once that value is committed the feed is locked +// and there is a valid, non-empty value to consume. func (f *Feed) Value() (feed.Value, string, bool) { return f.storage.GetLatest(), f.valueDataType, f.isLocked } +// MarshalJSON marshals the components of the feed that are needed for +// an agent to execute tasks and send values for ingestion. func (f *Feed) MarshalJSON() ([]byte, error) { buf := new(bytes.Buffer) w := bufio.NewWriter(buf) @@ -117,10 +131,13 @@ func (f *Feed) MarshalJSON() ([]byte, error) { return buf.Bytes(), nil } +// Tasks returns the feed's tasks. This allows task consumers to extract task +// contents without having to marshal the entire feed. func (f *Feed) Tasks() []feed.Task { return f.tasks } +// IsActive returns true if the feed is accepting ingestion requests from agents. func (f *Feed) IsActive() bool { return !f.isLocked } diff --git a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/feed.gno b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/feed.gno index 63a0c19dfd9..7357bf588ff 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/feed.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/feed.gno @@ -5,6 +5,8 @@ import ( "gno.land/p/demo/gnorkle/v1/message" ) +// Feed is an abstraction used by a gnorkle `Instance` to ingest data from +// agents and provide data feeds to consumers. type Feed interface { ID() string // necessary? Type() feed.Type @@ -15,6 +17,7 @@ type Feed interface { IsActive() bool } +// FeedWithWhitelist associates a `Whitelist` with a `Feed`. type FeedWithWhitelist struct { Feed Whitelist diff --git a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/ingester.gno b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/ingester.gno index d55101335ab..d4dbac73f60 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/ingester.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/ingester.gno @@ -2,6 +2,8 @@ package gnorkle import "gno.land/p/demo/gnorkle/v1/ingester" +// Ingester is the abstraction that allows a `Feed` to ingest data from agents +// and commit it to storage using zero or more intermediate aggregation steps. type Ingester interface { Type() ingester.Type Ingest(value, providerAddress string) (canAutoCommit bool) diff --git a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/instance.gno b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/instance.gno index 5c8f76ad074..4033b7cbaa3 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/instance.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/instance.gno @@ -10,26 +10,33 @@ import ( "gno.land/p/demo/gnorkle/v1/message" ) +// Instance is a single instance of an oracle. type Instance struct { feeds *avl.Tree whitelist agent.Whitelist } +// NewInstance creates a new instance of an oracle. func NewInstance() *Instance { return &Instance{ feeds: avl.NewTree(), } } -func assertNonEmptyString(s string) { - if len(s) == 0 { +func assertValidID(id string) { + if len(id) == 0 { panic("feed ids cannot be empty") } + + if strings.Contains(id, ",") { + panic("feed ids cannot contain commas") + } } +// AddFeeds adds feeds to the instance with empty whitelists. func (i *Instance) AddFeeds(feeds ...Feed) { for _, feed := range feeds { - assertNonEmptyString(feed.ID()) + assertValidID(feed.ID()) i.feeds.Set( feed.ID(), FeedWithWhitelist{ @@ -40,9 +47,10 @@ func (i *Instance) AddFeeds(feeds ...Feed) { } } +// AddFeedsWithWhitelists adds feeds to the instance with the given whitelists. func (i *Instance) AddFeedsWithWhitelists(feeds ...FeedWithWhitelist) { for _, feed := range feeds { - assertNonEmptyString(feed.ID()) + assertValidID(feed.ID()) i.feeds.Set( feed.ID(), FeedWithWhitelist{ @@ -53,14 +61,22 @@ func (i *Instance) AddFeedsWithWhitelists(feeds ...FeedWithWhitelist) { } } +// RemoveFeed removes a feed from the instance. func (i *Instance) RemoveFeed(id string) { i.feeds.Remove(id) } +// PostMessageHandler is a type that allows for post-processing of feed state after a feed +// ingests a message from an agent. type PostMessageHandler interface { Handle(i *Instance, funcType message.FuncType, feed Feed) } +// HandleMessage handles a message from an agent and routes to either the logic that returns +// feed definitions or the logic that allows a feed to ingest a message. +// +// TODO: Consider further message types that could allow administrative action such as modifying +// a feed's whitelist without the owner of this oracle having to maintain a reference to it. func (i *Instance) HandleMessage(msg string, postHandler PostMessageHandler) string { caller := string(std.GetOrigCaller()) @@ -72,8 +88,9 @@ func (i *Instance) HandleMessage(msg string, postHandler PostMessageHandler) str default: id, msg := message.ParseID(msg) - feedWithWhitelist := i.getFeedWithWhitelist(id) + assertValidID(id) + feedWithWhitelist := i.getFeedWithWhitelist(id) if !addressIsWhitelisted(&i.whitelist, feedWithWhitelist, caller, nil) { panic("caller not whitelisted") } @@ -116,10 +133,15 @@ func (i *Instance) getFeedWithWhitelist(id string) FeedWithWhitelist { return feedWithWhitelist } -func (i *Instance) GetFeedValue(id string) (feed.Value, string, bool) { +// GetFeedValue returns the most recently published value of a feed along with a string +// representation of the value's type and boolean indicating whether the value is +// okay for consumption. +func (i *Instance) GetFeedValue(id string) (value feed.Value, valueType string, consumable bool) { return i.getFeed(id).Value() } +// GetFeedDefinitions returns a JSON string representing the feed definitions for which the given +// agent address is whitelisted to provide values for ingestion. func (i *Instance) GetFeedDefinitions(forAddress string) string { instanceHasAddressWhitelisted := !i.whitelist.HasDefinition() || i.whitelist.HasAddress(forAddress) diff --git a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/storage.gno b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/storage.gno index c32d2e4dcae..753f8627d21 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/storage.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/storage.gno @@ -2,6 +2,8 @@ package gnorkle import "gno.land/p/demo/gnorkle/v1/feed" +// Storage defines how published feed values should be read +// and written. type Storage interface { Put(value string) GetLatest() feed.Value diff --git a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/whitelist.gno b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/whitelist.gno index fa1d08b2d34..da7a0fd7df6 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/whitelist.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/whitelist.gno @@ -1,5 +1,6 @@ package gnorkle +// Whitelist is used to manage which agents are allowed to interact. type Whitelist interface { ClearAddresses() AddAddresses(addresses []string) @@ -8,6 +9,7 @@ type Whitelist interface { HasAddress(address string) bool } +// ClearWhitelist clears the whitelist of the instance or feed depending on the feed ID. func (i *Instance) ClearWhitelist(feedID string) { if feedID == "" { i.whitelist.ClearAddresses() @@ -18,6 +20,7 @@ func (i *Instance) ClearWhitelist(feedID string) { feedWithWhitelist.ClearAddresses() } +// AddToWhitelist adds the given addresses to the whitelist of the instance or feed depending on the feed ID. func (i *Instance) AddToWhitelist(feedID string, addresses []string) { if feedID == "" { i.whitelist.AddAddresses(addresses) @@ -28,6 +31,7 @@ func (i *Instance) AddToWhitelist(feedID string, addresses []string) { feedWithWhitelist.AddAddresses(addresses) } +// RemoveFromWhitelist removes the given address from the whitelist of the instance or feed depending on the feed ID. func (i *Instance) RemoveFromWhitelist(feedID string, address string) { if feedID == "" { i.whitelist.RemoveAddress(address) diff --git a/examples/gno.land/p/demo/gnorkle/v1/ingester/type.gno b/examples/gno.land/p/demo/gnorkle/v1/ingester/type.gno index abc48eb1f32..a0ddef9cb35 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/ingester/type.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/ingester/type.gno @@ -1,8 +1,11 @@ package ingester +// Type indicates an ingester type. type Type int const ( + // TypeSingle indicates an ingester that can only ingest a single within a given period or no period. TypeSingle Type = iota + // TypeMulti indicates an ingester that can ingest multiple within a given period or no period TypeMulti ) diff --git a/examples/gno.land/p/demo/gnorkle/v1/ingesters/single/ingester.gno b/examples/gno.land/p/demo/gnorkle/v1/ingesters/single/ingester.gno index de754f8cda8..a4f4b2ee98a 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/ingesters/single/ingester.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/ingesters/single/ingester.gno @@ -5,19 +5,23 @@ import ( "gno.land/p/demo/gnorkle/v1/ingester" ) +// ValueIngester is an ingester that ingests a single value. type ValueIngester struct { value string } +// Type returns the type of the ingester. func (i *ValueIngester) Type() ingester.Type { return ingester.TypeSingle } +// Ingest ingests a value provided by the given agent address. func (i *ValueIngester) Ingest(value, providerAddress string) bool { i.value = value return true } +// CommitValue commits the ingested value to the given storage instance. func (i *ValueIngester) CommitValue(valueStorer gnorkle.Storage, providerAddress string) { valueStorer.Put(i.value) } diff --git a/examples/gno.land/p/demo/gnorkle/v1/message/parse.gno b/examples/gno.land/p/demo/gnorkle/v1/message/parse.gno index c459ce32f46..d8d0edde05d 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/message/parse.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/message/parse.gno @@ -2,6 +2,8 @@ package message import "strings" +// ParseFunc parses a raw message and returns the message function +// type extracted from the remainder of the message. func ParseFunc(rawMsg string) (FuncType, string) { msgParts := strings.SplitN(rawMsg, ",", 2) if len(msgParts) < 2 { @@ -11,6 +13,8 @@ func ParseFunc(rawMsg string) (FuncType, string) { return FuncType(msgParts[0]), msgParts[1] } +// ParseID parses a raw message and returns the ID extracted from +// the remainder of the message. func ParseID(rawMsg string) (string, string) { msgParts := strings.SplitN(rawMsg, ",", 2) if len(msgParts) < 2 { diff --git a/examples/gno.land/p/demo/gnorkle/v1/message/type.gno b/examples/gno.land/p/demo/gnorkle/v1/message/type.gno index 252a6fbd532..a80e568ea24 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/message/type.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/message/type.gno @@ -1,9 +1,15 @@ package message +// FuncType is the type of function that is being called by the agent. type FuncType string const ( - FuncTypeIngest FuncType = "ingest" - FuncTypeCommit FuncType = "commit" + // FuncTypeIngest means the agent is sending data for ingestion. + FuncTypeIngest FuncType = "ingest" + // FuncTypeCommit means the agent is requesting a feed commit the transitive data + // being held by its ingester. + FuncTypeCommit FuncType = "commit" + // FuncTypeRequest means the agent is requesting feed definitions for all those + // that it is whitelisted to provide data for. FuncTypeRequest FuncType = "request" ) diff --git a/examples/gno.land/p/demo/gnorkle/v1/storage/simple/storage.gno b/examples/gno.land/p/demo/gnorkle/v1/storage/simple/storage.gno index ddfc2b8db73..102c25fa407 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/storage/simple/storage.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/storage/simple/storage.gno @@ -6,24 +6,35 @@ import ( "gno.land/p/demo/gnorkle/v1/feed" ) +// Storage is simple, bounded storage for published feed values. type Storage struct { values []feed.Value - maxValues int + maxValues uint } -func NewStorage(maxValues int) *Storage { +// NewStorage creates a new Storage with the given maximum number of values. +// If maxValues is 0, the storage is bounded to a size of one. If this is not desirable, +// then don't provide a value of 0. +func NewStorage(maxValues uint) *Storage { + if maxValues == 0 { + maxValues = 1 + } + return &Storage{ maxValues: maxValues, } } +// Put adds a new value to the storage. If the storage is full, the oldest value +// is removed. If maxValues is 0, the storage is bounded to a size of one. func (s *Storage) Put(value string) { s.values = append(s.values, feed.Value{String: value, Time: time.Now()}) - if len(s.values) > s.maxValues && !(len(s.values) == 1 && s.maxValues == 0) { + if uint(len(s.values)) > s.maxValues { s.values = s.values[1:] } } +// GetLatest returns the most recently added value, or an empty value if none exist. func (s *Storage) GetLatest() feed.Value { if len(s.values) == 0 { return feed.Value{} @@ -32,6 +43,7 @@ func (s *Storage) GetLatest() feed.Value { return s.values[len(s.values)-1] } +// GetHistory returns all values in the storage, from oldest to newest. func (s *Storage) GetHistory() []feed.Value { return s.values } From 88b231ec961adf1a50c2f578dfcdce039ed9c955 Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 24 Jan 2024 13:30:44 -0800 Subject: [PATCH 21/48] added ghverify realm test --- .../r/gnoland/ghverify/contract_test.gno | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 examples/gno.land/r/gnoland/ghverify/contract_test.gno diff --git a/examples/gno.land/r/gnoland/ghverify/contract_test.gno b/examples/gno.land/r/gnoland/ghverify/contract_test.gno new file mode 100644 index 00000000000..d62d92d64bb --- /dev/null +++ b/examples/gno.land/r/gnoland/ghverify/contract_test.gno @@ -0,0 +1,78 @@ +package ghverify + +import ( + "std" + "strings" + "testing" + + "gno.land/p/demo/testutils" +) + +func TestVerificationLifecycle(t *testing.T) { + defaultAddress := std.GetOrigCaller() + userAddress := std.Address(testutils.TestAddress("user")) + + // Verify request returns no feeds. + result := GnorkleEntrypoint("request") + if result != "[]" { + t.Fatalf("expected empty request result, got %s", result) + } + + // Make a verification request with the created user. + std.TestSetOrigCaller(userAddress) + RequestVerification("deelawn") + + // Verify the request returns no feeds for this non-whitelisted user. + result = GnorkleEntrypoint("request") + if result != "[]" { + t.Fatalf("expected empty request result, got %s", result) + } + + // Set the caller back to the whitelisted user and verify that the feed data + // returned matches what should have been created by the `RequestVerification` + // invocation. + std.TestSetOrigCaller(defaultAddress) + result = GnorkleEntrypoint("request") + expResult := `[{"id":"deelawn","type":"0","value_type":"string","tasks":[{"gnoAddress":"` + + string(userAddress) + + `","githubHandle":"deelawn"}]}]` + if result != expResult { + t.Fatalf("expected request result %s, got %s", expResult, result) + } + + // Try to trigger feed ingestion from the non-authorized user. + std.TestSetOrigCaller(userAddress) + var errMsg string + func() { + defer func() { + if r := recover(); r != nil { + errMsg = r.(string) + } + }() + GnorkleEntrypoint("ingest,deelawn,OK") + }() + if errMsg != "caller not whitelisted" { + t.Fatalf("expected caller not whitelisted, got %s", errMsg) + } + + // Set the caller back to the whitelisted user and transfer contract ownership. + std.TestSetOrigCaller(defaultAddress) + SetOwner(string(userAddress)) + + // Now trigger the feed ingestion from the user and new owner and only whitelisted address. + std.TestSetOrigCaller(userAddress) + GnorkleEntrypoint("ingest,deelawn,OK") + + // Verify the ingestion autocommitted the value and triggered the post handler. + data := Render("") + expResult = `{"deelawn": "` + string(userAddress) + `"}` + if data != expResult { + t.Fatalf("expected render data %s, got %s", expResult, data) + } + + // Finally make sure the feed was cleaned up after the data was committed. + result = GnorkleEntrypoint("request") + if result != "[]" { + t.Fatalf("expected empty request result, got %s", result) + } +} From 797c7a19d7ca42e9d5fe3ea3c575458628dba0c5 Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 24 Jan 2024 13:37:28 -0800 Subject: [PATCH 22/48] gno mod tidy --- examples/gno.land/r/gnoland/ghverify/contract_test.gno | 3 +-- examples/gno.land/r/gnoland/ghverify/gno.mod | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/gno.land/r/gnoland/ghverify/contract_test.gno b/examples/gno.land/r/gnoland/ghverify/contract_test.gno index d62d92d64bb..40ad8cbdb6a 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract_test.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract_test.gno @@ -34,8 +34,7 @@ func TestVerificationLifecycle(t *testing.T) { std.TestSetOrigCaller(defaultAddress) result = GnorkleEntrypoint("request") expResult := `[{"id":"deelawn","type":"0","value_type":"string","tasks":[{"gnoAddress":"` + - string(userAddress) + - `","githubHandle":"deelawn"}]}]` + string(userAddress) + `","githubHandle":"deelawn"}]}]` if result != expResult { t.Fatalf("expected request result %s, got %s", expResult, result) } diff --git a/examples/gno.land/r/gnoland/ghverify/gno.mod b/examples/gno.land/r/gnoland/ghverify/gno.mod index 48098e2d907..4ef7eb61924 100644 --- a/examples/gno.land/r/gnoland/ghverify/gno.mod +++ b/examples/gno.land/r/gnoland/ghverify/gno.mod @@ -6,4 +6,5 @@ require ( gno.land/p/demo/gnorkle/v1/feeds/static v0.0.0-latest gno.land/p/demo/gnorkle/v1/gnorkle v0.0.0-latest gno.land/p/demo/gnorkle/v1/message v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest ) From 5ea02ffa8e454e9986e6b3bb8a205dd5662aafdf Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 24 Jan 2024 13:56:21 -0800 Subject: [PATCH 23/48] added ghverify accessors --- examples/gno.land/r/gnoland/ghverify/contract.gno | 12 ++++++++++++ .../gno.land/r/gnoland/ghverify/contract_test.gno | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/examples/gno.land/r/gnoland/ghverify/contract.gno b/examples/gno.land/r/gnoland/ghverify/contract.gno index fc479905e9f..09260b79c92 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract.gno @@ -87,6 +87,18 @@ func SetOwner(owner string) { oracle.AddToWhitelist("", []string{ownerAddress}) } +func GetHandleByAddress(address string) string { + var value string + value, _ = addressToHandleMap.Get(address) + return value.(string) +} + +func GetAddressByHandle(handle string) string { + var value string + value, _ = handleToAddressMap.Get(handle) + return value.(string) +} + func Render(_ string) string { result := "{" var appendComma bool diff --git a/examples/gno.land/r/gnoland/ghverify/contract_test.gno b/examples/gno.land/r/gnoland/ghverify/contract_test.gno index 40ad8cbdb6a..2eed22fca40 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract_test.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract_test.gno @@ -74,4 +74,12 @@ func TestVerificationLifecycle(t *testing.T) { if result != "[]" { t.Fatalf("expected empty request result, got %s", result) } + + // Check that the accessor functions are working as expected. + if handle := GetHandleByAddress(string(userAddress)); handle != "deelawn" { + t.Fatalf("expected deelawn, got %s", handle) + } + if address := GetAddressByHandle("deelawn"); address != string(userAddress) { + t.Fatalf("expected %s, got %s", string(userAddress), address) + } } From 3a439597f054cc2bc59e3160fe2a372e0489b155 Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 24 Jan 2024 14:12:24 -0800 Subject: [PATCH 24/48] fix type error --- examples/gno.land/r/gnoland/ghverify/contract.gno | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/gno.land/r/gnoland/ghverify/contract.gno b/examples/gno.land/r/gnoland/ghverify/contract.gno index 09260b79c92..612c11d18fa 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract.gno @@ -88,14 +88,12 @@ func SetOwner(owner string) { } func GetHandleByAddress(address string) string { - var value string - value, _ = addressToHandleMap.Get(address) + value, _ := addressToHandleMap.Get(address) return value.(string) } func GetAddressByHandle(handle string) string { - var value string - value, _ = handleToAddressMap.Get(handle) + value, _ := handleToAddressMap.Get(handle) return value.(string) } From 02920e54f46f0c607fcadc8869603d921589f2b3 Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 24 Jan 2024 15:38:09 -0800 Subject: [PATCH 25/48] docstrings and cleanup --- .../gno.land/r/gnoland/ghverify/contract.gno | 24 ++++++++++++++++--- .../r/gnoland/ghverify/contract_test.gno | 4 ++-- examples/gno.land/r/gnoland/ghverify/task.gno | 18 ++------------ 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/examples/gno.land/r/gnoland/ghverify/contract.gno b/examples/gno.land/r/gnoland/ghverify/contract.gno index 612c11d18fa..fdb55754678 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract.gno @@ -11,6 +11,7 @@ import ( ) const ( + // The agent should send this value if it has verified the github handle. verifiedResult = "OK" ) @@ -30,6 +31,8 @@ func init() { type postGnorkleMessageHandler struct{} +// Handle does post processing after a message is ingested by the oracle feed. It extracts the value to realm +// storage and removes the feed from the oracle. func (h postGnorkleMessageHandler) Handle(i *gnorkle.Instance, funcType message.FuncType, feed gnorkle.Feed) { if funcType != message.FuncTypeIngest { return @@ -40,8 +43,11 @@ func (h postGnorkleMessageHandler) Handle(i *gnorkle.Instance, funcType message. return } + // The value is consumable, meaning the ingestion occurred, so we can remove the feed from the oracle + // after saving it to realm storage. defer oracle.RemoveFeed(feed.ID()) + // Couldn't verify; nothing to do. if result.String != verifiedResult { return } @@ -56,24 +62,33 @@ func (h postGnorkleMessageHandler) Handle(i *gnorkle.Instance, funcType message. panic("expected ghverify task") } - handleToAddressMap.Set(task.GithubHandle(), task.GnoAddress()) - addressToHandleMap.Set(task.GnoAddress(), task.GithubHandle()) + handleToAddressMap.Set(task.githubHandle, task.gnoAddress) + addressToHandleMap.Set(task.gnoAddress, task.githubHandle) } +// RequestVerification creates a new static feed with a single task that will +// instruct an agent to verify the github handle / gno address pair. func RequestVerification(githubHandle string) { oracle.AddFeeds( static.NewSingleValueFeed( githubHandle, "string", - []feed.Task{NewVerificationTask(string(std.GetOrigCaller()), githubHandle)}, + []feed.Task{ + &verificationTask{ + gnoAddress: string(std.GetOrigCaller()), + githubHandle: githubHandle, + }, + }, ), ) } +// GnorkleEntrypoint is the entrypoint to the gnorkle oracle handler. func GnorkleEntrypoint(message string) string { return oracle.HandleMessage(message, postHandler) } +// SetOwner transfers ownership of the contract to the given address. func SetOwner(owner string) { if ownerAddress != string(std.GetOrigCaller()) { panic("only the owner can set a new owner") @@ -87,16 +102,19 @@ func SetOwner(owner string) { oracle.AddToWhitelist("", []string{ownerAddress}) } +// GetHandleByAddress returns the github handle associated with the given gno address. func GetHandleByAddress(address string) string { value, _ := addressToHandleMap.Get(address) return value.(string) } +// GetAddressByHandle returns the gno address associated with the given github handle. func GetAddressByHandle(handle string) string { value, _ := handleToAddressMap.Get(handle) return value.(string) } +// Render returns a json object string will all verified handle -> address mappings. func Render(_ string) string { result := "{" var appendComma bool diff --git a/examples/gno.land/r/gnoland/ghverify/contract_test.gno b/examples/gno.land/r/gnoland/ghverify/contract_test.gno index 2eed22fca40..462c0e9d4a3 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract_test.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract_test.gno @@ -33,8 +33,8 @@ func TestVerificationLifecycle(t *testing.T) { // invocation. std.TestSetOrigCaller(defaultAddress) result = GnorkleEntrypoint("request") - expResult := `[{"id":"deelawn","type":"0","value_type":"string","tasks":[{"gnoAddress":"` + - string(userAddress) + `","githubHandle":"deelawn"}]}]` + expResult := `[{"id":"deelawn","type":"0","value_type":"string","tasks":[{"gno_address":"` + + string(userAddress) + `","github_handle":"deelawn"}]}]` if result != expResult { t.Fatalf("expected request result %s, got %s", expResult, result) } diff --git a/examples/gno.land/r/gnoland/ghverify/task.gno b/examples/gno.land/r/gnoland/ghverify/task.gno index f49a174fd12..cbc8ee0fbe6 100644 --- a/examples/gno.land/r/gnoland/ghverify/task.gno +++ b/examples/gno.land/r/gnoland/ghverify/task.gno @@ -10,29 +10,15 @@ type verificationTask struct { githubHandle string } -func NewVerificationTask(gnoAddress, githubHandle string) *verificationTask { - return &verificationTask{ - gnoAddress: gnoAddress, - githubHandle: githubHandle, - } -} - +// MarshalToJSON marshals the task contents to JSON. func (t *verificationTask) MarshalToJSON() ([]byte, error) { buf := new(bytes.Buffer) w := bufio.NewWriter(buf) w.Write( - []byte(`{"gnoAddress":"` + t.gnoAddress + `","githubHandle":"` + t.githubHandle + `"}`), + []byte(`{"gno_address":"` + t.gnoAddress + `","github_handle":"` + t.githubHandle + `"}`), ) w.Flush() return buf.Bytes(), nil } - -func (t *verificationTask) GnoAddress() string { - return t.gnoAddress -} - -func (t *verificationTask) GithubHandle() string { - return t.githubHandle -} From d3d93763bf28d3f73bc4f3d708ff3ab2082f7223 Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 24 Jan 2024 15:54:48 -0800 Subject: [PATCH 26/48] prevent overwriting existing verification requests --- .../p/demo/gnorkle/v1/gnorkle/instance.gno | 8 +++++++ .../gno.land/r/gnoland/ghverify/contract.gno | 5 +++-- .../r/gnoland/ghverify/contract_test.gno | 22 +++++++++++++++---- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/instance.gno b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/instance.gno index 4033b7cbaa3..c3ffac385fd 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/instance.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/instance.gno @@ -33,10 +33,17 @@ func assertValidID(id string) { } } +func (i *Instance) assertFeedDoesNotExist(id string) { + if i.feeds.Has(id) { + panic("feed already exists") + } +} + // AddFeeds adds feeds to the instance with empty whitelists. func (i *Instance) AddFeeds(feeds ...Feed) { for _, feed := range feeds { assertValidID(feed.ID()) + i.assertFeedDoesNotExist(feed.ID()) i.feeds.Set( feed.ID(), FeedWithWhitelist{ @@ -50,6 +57,7 @@ func (i *Instance) AddFeeds(feeds ...Feed) { // AddFeedsWithWhitelists adds feeds to the instance with the given whitelists. func (i *Instance) AddFeedsWithWhitelists(feeds ...FeedWithWhitelist) { for _, feed := range feeds { + i.assertFeedDoesNotExist(feed.ID()) assertValidID(feed.ID()) i.feeds.Set( feed.ID(), diff --git a/examples/gno.land/r/gnoland/ghverify/contract.gno b/examples/gno.land/r/gnoland/ghverify/contract.gno index fdb55754678..10dd5eeb37b 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract.gno @@ -69,13 +69,14 @@ func (h postGnorkleMessageHandler) Handle(i *gnorkle.Instance, funcType message. // RequestVerification creates a new static feed with a single task that will // instruct an agent to verify the github handle / gno address pair. func RequestVerification(githubHandle string) { + gnoAddress := string(std.GetOrigCaller()) oracle.AddFeeds( static.NewSingleValueFeed( - githubHandle, + gnoAddress, "string", []feed.Task{ &verificationTask{ - gnoAddress: string(std.GetOrigCaller()), + gnoAddress: gnoAddress, githubHandle: githubHandle, }, }, diff --git a/examples/gno.land/r/gnoland/ghverify/contract_test.gno b/examples/gno.land/r/gnoland/ghverify/contract_test.gno index 462c0e9d4a3..6a459573c70 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract_test.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract_test.gno @@ -22,6 +22,21 @@ func TestVerificationLifecycle(t *testing.T) { std.TestSetOrigCaller(userAddress) RequestVerification("deelawn") + // A subsequent request from the same address should panic because there is + // already a feed with an ID of this user's address. + var errMsg string + func() { + defer func() { + if r := recover(); r != nil { + errMsg = r.(string) + } + }() + RequestVerification("deelawn") + }() + if errMsg != "feed already exists" { + t.Fatalf("expected feed already exists, got %s", errMsg) + } + // Verify the request returns no feeds for this non-whitelisted user. result = GnorkleEntrypoint("request") if result != "[]" { @@ -33,7 +48,7 @@ func TestVerificationLifecycle(t *testing.T) { // invocation. std.TestSetOrigCaller(defaultAddress) result = GnorkleEntrypoint("request") - expResult := `[{"id":"deelawn","type":"0","value_type":"string","tasks":[{"gno_address":"` + + expResult := `[{"id":"` + string(userAddress) + `","type":"0","value_type":"string","tasks":[{"gno_address":"` + string(userAddress) + `","github_handle":"deelawn"}]}]` if result != expResult { t.Fatalf("expected request result %s, got %s", expResult, result) @@ -41,14 +56,13 @@ func TestVerificationLifecycle(t *testing.T) { // Try to trigger feed ingestion from the non-authorized user. std.TestSetOrigCaller(userAddress) - var errMsg string func() { defer func() { if r := recover(); r != nil { errMsg = r.(string) } }() - GnorkleEntrypoint("ingest,deelawn,OK") + GnorkleEntrypoint("ingest," + string(userAddress) + ",OK") }() if errMsg != "caller not whitelisted" { t.Fatalf("expected caller not whitelisted, got %s", errMsg) @@ -60,7 +74,7 @@ func TestVerificationLifecycle(t *testing.T) { // Now trigger the feed ingestion from the user and new owner and only whitelisted address. std.TestSetOrigCaller(userAddress) - GnorkleEntrypoint("ingest,deelawn,OK") + GnorkleEntrypoint("ingest," + string(userAddress) + ",OK") // Verify the ingestion autocommitted the value and triggered the post handler. data := Render("") From 2c11cb3d0e0b2995ddddc2f948798aff4c042f46 Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 24 Jan 2024 17:28:08 -0800 Subject: [PATCH 27/48] added readmes --- .../p/demo/gnorkle/v1/gnorkle/feed.gno | 2 +- .../p/demo/gnorkle/v1/gnorkle/whitelist.gno | 2 - examples/gno.land/p/demo/gnorkle/v1/readme.md | 41 +++++++++++++++++++ .../gno.land/r/gnoland/ghverify/readme.md | 9 ++++ 4 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 examples/gno.land/p/demo/gnorkle/v1/readme.md create mode 100644 examples/gno.land/r/gnoland/ghverify/readme.md diff --git a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/feed.gno b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/feed.gno index 7357bf588ff..59b66df4252 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/feed.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/feed.gno @@ -8,7 +8,7 @@ import ( // Feed is an abstraction used by a gnorkle `Instance` to ingest data from // agents and provide data feeds to consumers. type Feed interface { - ID() string // necessary? + ID() string Type() feed.Type Value() (value feed.Value, dataType string, consumable bool) Ingest(funcType message.FuncType, rawMessage, providerAddress string) diff --git a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/whitelist.gno b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/whitelist.gno index da7a0fd7df6..f6ccace7ac6 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/whitelist.gno +++ b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/whitelist.gno @@ -42,8 +42,6 @@ func (i *Instance) RemoveFromWhitelist(feedID string, address string) { feedWithWhitelist.RemoveAddress(address) } -// TODO: test this. - // addressWhiteListed returns true if: // - the feed has a white list and the address is whitelisted, or // - the feed has no white list and the instance has a white list and the address is whitelisted, or diff --git a/examples/gno.land/p/demo/gnorkle/v1/readme.md b/examples/gno.land/p/demo/gnorkle/v1/readme.md new file mode 100644 index 00000000000..55b49462273 --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/v1/readme.md @@ -0,0 +1,41 @@ +# gnorkle + +gnorkle is an attempt at a generalized oracle implementation. The gnorkle `Instance` definitions lives in the `gnorkle` directory. It can be effectively initialized by using the `gnorkle.NewInstance` constructor. The owner of the instance is then able to add feeds with or without whitelists, manage the instance's own whitelist, remove feeds, get the latest feed values, and handle incoming message requests that result in some type of action taking place. + +An example of gnorkle in action can be found in the `examples/gno.land/r/gnoland/ghverify` realm. Any realm that wishes to integrate the minimum oracle functionality should include functions that relay incoming messages to the oracle instance's message handler and that can produce the latest feed values. + +## Feeds + +Feeds that are added to an oracle must implement the `gnorkle.Feed` interface. A feed can be thought of as a mechanism that takes in off-chain data and produces data to be consumed on-chain. The three important components of a feed are: +- Tasks: a feed is composed of one or more `feed.Task` instances. These tasks ultimately instruct an agent what it needs to do to provide data to a feed for ingestion. +- Ingester: a feed should have a `gnorkle.Ingester` instance that handles incoming data from agents. The ingester's role is to perform the correct action on the incoming data, like adding it to an aggregate or storing it for later evaluation. +- Storage: a feed should have a `gnorkle.Storage` instance that manages the state of the data a feed publishes -- data to be consumed on-chain. + +A single oracle instance may be composed of many feeds, each of which has a unique ID. + +Here is an example of what differences may exist amongst feed implementations: +- Static: a static feed is one that only needs to produce a value once. It ingests values and then publishes the result. Once a single value is published, the state of the feed becomes immutable. +- Continuous: a continuous feed can accept and ingest data, continously adding and changing its own internal state based on the data received. It can then publish values on demand based on its current state. +- Periodic: a periodic feed may give all whitelisted agents the opportunity to send data for ingestion within a bounded period of time. After this window closes, the results can be committed and a value is pubished. The process then begins again for the next period. + +The only feed currently implemented is the `feeds/static.Feed` type. + +## Tasks + +It's not hard to be a task -- just implement the one method `feed.Task` interface. On-chain task definitions should not do anything other than store data and be able to marshal that data to JSON. Of course, it is also useful if the task marshal's a `type` field as part of the JSON object so an agent is able to know what type of task it is dealing with and what data to expect in the payload. + +## Ingesters + +An ingester's primary role is to receive data provided by agents and figure out what to do with it. Ingesters must implement the `gnorkle.Ingester` interface. There are currently two message function types that an ingester may want to handle, `message.FuncTypeIngest` and `message.FuncTypeCommit`. The former message type should result in the ingester accumulating data in its own data store, while the latter should use what it has in its data store to publish a feed value to the `gnorkle.Storage` instance provided to it. + +The only ingester currently implemented is the `ingesters/single.ValueIngester` type. + +## Storage + +Storage types are responsible for storing values produced by a feed's ingester. A storage type must implement `gnorkle.Storage`. This type should be able add values to the storage, retrieve the latest value, and retrieve a set of historical values. It is probably a good idea to make the storage bounded. + +The only storage currently implemented is the `storage/simple.Storage` type. + +## Whitelists + +Whitelists are optional but they can be set on both the oracle instance and the feed levels. The absence of a whitelist definition indicates that ALL addresses should be considered to be whitelisted. Otherwise, the presence of a defined whitelist indicates the callers address MUST be in the whitelist in order for the request to succeed. A feed whitelist has precedence over the oracle instance's whitelist. If a feed has a whitelist and the caller is not on it, the call fails. If a feed doesn't have a whitelist but the instance does and the caller is not on it, the call fails. If neither have a whitelist, the call succeeds. The whitlist logic mostly lives in `gnorkle/whitelist.gno` while the only current `gnorkle.Whitelist` implementation is the `agent.Whitelist` type. The whitelist is not owned by the feeds they are associated with in order to not further pollute the `gnorkle.Feed` interface. \ No newline at end of file diff --git a/examples/gno.land/r/gnoland/ghverify/readme.md b/examples/gno.land/r/gnoland/ghverify/readme.md new file mode 100644 index 00000000000..039e9b6be0a --- /dev/null +++ b/examples/gno.land/r/gnoland/ghverify/readme.md @@ -0,0 +1,9 @@ +# ghverify + +This realm is intended to enable off chain gno address to github handle verification. +The steps are as follows: +- A user calls `RequestVerification` and provides a github handle. This creates a new static oracle feed. +- An off-chain agent controlled by the owner of this realm requests current feeds using the `GnorkleEntrypoint` function and provides a message of `"request"` +- The agent receives the task information that includes the github handle and the gno address. It performs the verification step by checking whether this github user has the address in a github repository it controls. +- The agent publishes the result of the verification by calling `GnorkleEntrypoint` with a message structured like: `"ingest,,`. The verification status is `OK` if verification succeeded and any other value if it failed. +- The oracle feed's ingester processes the verification and the handle to address mapping is written to the avl trees that exist as ghverify realm variables. \ No newline at end of file From f2c0d0dbf9745973fb52e3eb9ba6b7a7fb8bda94 Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 22 Feb 2024 11:52:46 -0800 Subject: [PATCH 28/48] removed v1 suffix --- .../gno.land/p/demo/gnorkle/{v1 => }/agent/gno.mod | 2 +- .../p/demo/gnorkle/{v1 => }/agent/whitelist.gno | 0 examples/gno.land/p/demo/gnorkle/feed/gno.mod | 1 + .../gno.land/p/demo/gnorkle/{v1 => }/feed/task.gno | 0 .../gno.land/p/demo/gnorkle/{v1 => }/feed/type.gno | 0 .../gno.land/p/demo/gnorkle/{v1 => }/feed/value.gno | 0 .../p/demo/gnorkle/{v1 => }/feeds/static/feed.gno | 10 +++++----- examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod | 10 ++++++++++ .../gno.land/p/demo/gnorkle/{v1 => }/gnorkle/feed.gno | 4 ++-- examples/gno.land/p/demo/gnorkle/gnorkle/gno.mod | 9 +++++++++ .../p/demo/gnorkle/{v1 => }/gnorkle/ingester.gno | 2 +- .../p/demo/gnorkle/{v1 => }/gnorkle/instance.gno | 6 +++--- .../p/demo/gnorkle/{v1 => }/gnorkle/storage.gno | 2 +- .../p/demo/gnorkle/{v1 => }/gnorkle/whitelist.gno | 0 examples/gno.land/p/demo/gnorkle/ingester/gno.mod | 1 + .../gno.land/p/demo/gnorkle/{v1 => }/ingester/type.gno | 0 .../gno.land/p/demo/gnorkle/ingesters/single/gno.mod | 6 ++++++ .../gnorkle/{v1 => }/ingesters/single/ingester.gno | 4 ++-- examples/gno.land/p/demo/gnorkle/message/gno.mod | 1 + .../gno.land/p/demo/gnorkle/{v1 => }/message/parse.gno | 0 .../gno.land/p/demo/gnorkle/{v1 => }/message/type.gno | 0 examples/gno.land/p/demo/gnorkle/{v1 => }/readme.md | 0 .../gno.land/p/demo/gnorkle/storage/simple/gno.mod | 3 +++ .../p/demo/gnorkle/{v1 => }/storage/simple/storage.gno | 2 +- examples/gno.land/p/demo/gnorkle/v1/feed/gno.mod | 1 - .../gno.land/p/demo/gnorkle/v1/feeds/static/gno.mod | 10 ---------- examples/gno.land/p/demo/gnorkle/v1/gnorkle/gno.mod | 9 --------- examples/gno.land/p/demo/gnorkle/v1/ingester/gno.mod | 1 - .../p/demo/gnorkle/v1/ingesters/single/gno.mod | 6 ------ examples/gno.land/p/demo/gnorkle/v1/message/gno.mod | 1 - .../gno.land/p/demo/gnorkle/v1/storage/simple/gno.mod | 3 --- examples/gno.land/r/gnoland/ghverify/contract.gno | 8 ++++---- examples/gno.land/r/gnoland/ghverify/gno.mod | 8 ++++---- 33 files changed, 55 insertions(+), 55 deletions(-) rename examples/gno.land/p/demo/gnorkle/{v1 => }/agent/gno.mod (51%) rename examples/gno.land/p/demo/gnorkle/{v1 => }/agent/whitelist.gno (100%) create mode 100644 examples/gno.land/p/demo/gnorkle/feed/gno.mod rename examples/gno.land/p/demo/gnorkle/{v1 => }/feed/task.gno (100%) rename examples/gno.land/p/demo/gnorkle/{v1 => }/feed/type.gno (100%) rename examples/gno.land/p/demo/gnorkle/{v1 => }/feed/value.gno (100%) rename examples/gno.land/p/demo/gnorkle/{v1 => }/feeds/static/feed.gno (94%) create mode 100644 examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod rename examples/gno.land/p/demo/gnorkle/{v1 => }/gnorkle/feed.gno (87%) create mode 100644 examples/gno.land/p/demo/gnorkle/gnorkle/gno.mod rename examples/gno.land/p/demo/gnorkle/{v1 => }/gnorkle/ingester.gno (88%) rename examples/gno.land/p/demo/gnorkle/{v1 => }/gnorkle/instance.gno (97%) rename examples/gno.land/p/demo/gnorkle/{v1 => }/gnorkle/storage.gno (82%) rename examples/gno.land/p/demo/gnorkle/{v1 => }/gnorkle/whitelist.gno (100%) create mode 100644 examples/gno.land/p/demo/gnorkle/ingester/gno.mod rename examples/gno.land/p/demo/gnorkle/{v1 => }/ingester/type.gno (100%) create mode 100644 examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod rename examples/gno.land/p/demo/gnorkle/{v1 => }/ingesters/single/ingester.gno (88%) create mode 100644 examples/gno.land/p/demo/gnorkle/message/gno.mod rename examples/gno.land/p/demo/gnorkle/{v1 => }/message/parse.gno (100%) rename examples/gno.land/p/demo/gnorkle/{v1 => }/message/type.gno (100%) rename examples/gno.land/p/demo/gnorkle/{v1 => }/readme.md (100%) create mode 100644 examples/gno.land/p/demo/gnorkle/storage/simple/gno.mod rename examples/gno.land/p/demo/gnorkle/{v1 => }/storage/simple/storage.gno (97%) delete mode 100644 examples/gno.land/p/demo/gnorkle/v1/feed/gno.mod delete mode 100644 examples/gno.land/p/demo/gnorkle/v1/feeds/static/gno.mod delete mode 100644 examples/gno.land/p/demo/gnorkle/v1/gnorkle/gno.mod delete mode 100644 examples/gno.land/p/demo/gnorkle/v1/ingester/gno.mod delete mode 100644 examples/gno.land/p/demo/gnorkle/v1/ingesters/single/gno.mod delete mode 100644 examples/gno.land/p/demo/gnorkle/v1/message/gno.mod delete mode 100644 examples/gno.land/p/demo/gnorkle/v1/storage/simple/gno.mod diff --git a/examples/gno.land/p/demo/gnorkle/v1/agent/gno.mod b/examples/gno.land/p/demo/gnorkle/agent/gno.mod similarity index 51% rename from examples/gno.land/p/demo/gnorkle/v1/agent/gno.mod rename to examples/gno.land/p/demo/gnorkle/agent/gno.mod index 3e8720ba385..6cf359dcf87 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/agent/gno.mod +++ b/examples/gno.land/p/demo/gnorkle/agent/gno.mod @@ -1,3 +1,3 @@ -module gno.land/p/demo/gnorkle/v1/agent +module gno.land/p/demo/gnorkle/agent require gno.land/p/demo/avl v0.0.0-latest diff --git a/examples/gno.land/p/demo/gnorkle/v1/agent/whitelist.gno b/examples/gno.land/p/demo/gnorkle/agent/whitelist.gno similarity index 100% rename from examples/gno.land/p/demo/gnorkle/v1/agent/whitelist.gno rename to examples/gno.land/p/demo/gnorkle/agent/whitelist.gno diff --git a/examples/gno.land/p/demo/gnorkle/feed/gno.mod b/examples/gno.land/p/demo/gnorkle/feed/gno.mod new file mode 100644 index 00000000000..65e1ffa5897 --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/feed/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/gnorkle/feed diff --git a/examples/gno.land/p/demo/gnorkle/v1/feed/task.gno b/examples/gno.land/p/demo/gnorkle/feed/task.gno similarity index 100% rename from examples/gno.land/p/demo/gnorkle/v1/feed/task.gno rename to examples/gno.land/p/demo/gnorkle/feed/task.gno diff --git a/examples/gno.land/p/demo/gnorkle/v1/feed/type.gno b/examples/gno.land/p/demo/gnorkle/feed/type.gno similarity index 100% rename from examples/gno.land/p/demo/gnorkle/v1/feed/type.gno rename to examples/gno.land/p/demo/gnorkle/feed/type.gno diff --git a/examples/gno.land/p/demo/gnorkle/v1/feed/value.gno b/examples/gno.land/p/demo/gnorkle/feed/value.gno similarity index 100% rename from examples/gno.land/p/demo/gnorkle/v1/feed/value.gno rename to examples/gno.land/p/demo/gnorkle/feed/value.gno diff --git a/examples/gno.land/p/demo/gnorkle/v1/feeds/static/feed.gno b/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno similarity index 94% rename from examples/gno.land/p/demo/gnorkle/v1/feeds/static/feed.gno rename to examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno index f9eba3cb7bc..c406163db2d 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/feeds/static/feed.gno +++ b/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno @@ -4,11 +4,11 @@ import ( "bufio" "bytes" - "gno.land/p/demo/gnorkle/v1/feed" - "gno.land/p/demo/gnorkle/v1/gnorkle" - "gno.land/p/demo/gnorkle/v1/ingesters/single" - "gno.land/p/demo/gnorkle/v1/message" - "gno.land/p/demo/gnorkle/v1/storage/simple" + "gno.land/p/demo/gnorkle/feed" + "gno.land/p/demo/gnorkle/gnorkle" + "gno.land/p/demo/gnorkle/ingesters/single" + "gno.land/p/demo/gnorkle/message" + "gno.land/p/demo/gnorkle/storage/simple" "gno.land/p/demo/ufmt" ) diff --git a/examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod b/examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod new file mode 100644 index 00000000000..7893fd2bf76 --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod @@ -0,0 +1,10 @@ +module gno.land/p/demo/gnorkle/feeds/static + +require ( + gno.land/p/demo/gnorkle/feed v0.0.0-latest + gno.land/p/demo/gnorkle/gnorkle v0.0.0-latest + gno.land/p/demo/gnorkle/ingesters/single v0.0.0-latest + gno.land/p/demo/gnorkle/message v0.0.0-latest + gno.land/p/demo/gnorkle/storage/simple v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/feed.gno b/examples/gno.land/p/demo/gnorkle/gnorkle/feed.gno similarity index 87% rename from examples/gno.land/p/demo/gnorkle/v1/gnorkle/feed.gno rename to examples/gno.land/p/demo/gnorkle/gnorkle/feed.gno index 59b66df4252..4ac52f373b8 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/feed.gno +++ b/examples/gno.land/p/demo/gnorkle/gnorkle/feed.gno @@ -1,8 +1,8 @@ package gnorkle import ( - "gno.land/p/demo/gnorkle/v1/feed" - "gno.land/p/demo/gnorkle/v1/message" + "gno.land/p/demo/gnorkle/feed" + "gno.land/p/demo/gnorkle/message" ) // Feed is an abstraction used by a gnorkle `Instance` to ingest data from diff --git a/examples/gno.land/p/demo/gnorkle/gnorkle/gno.mod b/examples/gno.land/p/demo/gnorkle/gnorkle/gno.mod new file mode 100644 index 00000000000..88fb202863f --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/gnorkle/gno.mod @@ -0,0 +1,9 @@ +module gno.land/p/demo/gnorkle/gnorkle + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/gnorkle/agent v0.0.0-latest + gno.land/p/demo/gnorkle/feed v0.0.0-latest + gno.land/p/demo/gnorkle/ingester v0.0.0-latest + gno.land/p/demo/gnorkle/message v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/ingester.gno b/examples/gno.land/p/demo/gnorkle/gnorkle/ingester.gno similarity index 88% rename from examples/gno.land/p/demo/gnorkle/v1/gnorkle/ingester.gno rename to examples/gno.land/p/demo/gnorkle/gnorkle/ingester.gno index d4dbac73f60..827df0a1685 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/ingester.gno +++ b/examples/gno.land/p/demo/gnorkle/gnorkle/ingester.gno @@ -1,6 +1,6 @@ package gnorkle -import "gno.land/p/demo/gnorkle/v1/ingester" +import "gno.land/p/demo/gnorkle/ingester" // Ingester is the abstraction that allows a `Feed` to ingest data from agents // and commit it to storage using zero or more intermediate aggregation steps. diff --git a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/instance.gno b/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno similarity index 97% rename from examples/gno.land/p/demo/gnorkle/v1/gnorkle/instance.gno rename to examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno index c3ffac385fd..5bab51453a4 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/instance.gno +++ b/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno @@ -5,9 +5,9 @@ import ( "strings" "gno.land/p/demo/avl" - "gno.land/p/demo/gnorkle/v1/agent" - "gno.land/p/demo/gnorkle/v1/feed" - "gno.land/p/demo/gnorkle/v1/message" + "gno.land/p/demo/gnorkle/agent" + "gno.land/p/demo/gnorkle/feed" + "gno.land/p/demo/gnorkle/message" ) // Instance is a single instance of an oracle. diff --git a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/storage.gno b/examples/gno.land/p/demo/gnorkle/gnorkle/storage.gno similarity index 82% rename from examples/gno.land/p/demo/gnorkle/v1/gnorkle/storage.gno rename to examples/gno.land/p/demo/gnorkle/gnorkle/storage.gno index 753f8627d21..7dee3b84a16 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/storage.gno +++ b/examples/gno.land/p/demo/gnorkle/gnorkle/storage.gno @@ -1,6 +1,6 @@ package gnorkle -import "gno.land/p/demo/gnorkle/v1/feed" +import "gno.land/p/demo/gnorkle/feed" // Storage defines how published feed values should be read // and written. diff --git a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/whitelist.gno b/examples/gno.land/p/demo/gnorkle/gnorkle/whitelist.gno similarity index 100% rename from examples/gno.land/p/demo/gnorkle/v1/gnorkle/whitelist.gno rename to examples/gno.land/p/demo/gnorkle/gnorkle/whitelist.gno diff --git a/examples/gno.land/p/demo/gnorkle/ingester/gno.mod b/examples/gno.land/p/demo/gnorkle/ingester/gno.mod new file mode 100644 index 00000000000..66fa3abf6ad --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/ingester/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/gnorkle/ingester diff --git a/examples/gno.land/p/demo/gnorkle/v1/ingester/type.gno b/examples/gno.land/p/demo/gnorkle/ingester/type.gno similarity index 100% rename from examples/gno.land/p/demo/gnorkle/v1/ingester/type.gno rename to examples/gno.land/p/demo/gnorkle/ingester/type.gno diff --git a/examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod b/examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod new file mode 100644 index 00000000000..69f43b8ef85 --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod @@ -0,0 +1,6 @@ +module gno.land/p/demo/gnorkle/ingesters/single + +require ( + gno.land/p/demo/gnorkle/gnorkle v0.0.0-latest + gno.land/p/demo/gnorkle/ingester v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/gnorkle/v1/ingesters/single/ingester.gno b/examples/gno.land/p/demo/gnorkle/ingesters/single/ingester.gno similarity index 88% rename from examples/gno.land/p/demo/gnorkle/v1/ingesters/single/ingester.gno rename to examples/gno.land/p/demo/gnorkle/ingesters/single/ingester.gno index a4f4b2ee98a..b242236a80d 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/ingesters/single/ingester.gno +++ b/examples/gno.land/p/demo/gnorkle/ingesters/single/ingester.gno @@ -1,8 +1,8 @@ package single import ( - "gno.land/p/demo/gnorkle/v1/gnorkle" - "gno.land/p/demo/gnorkle/v1/ingester" + "gno.land/p/demo/gnorkle/gnorkle" + "gno.land/p/demo/gnorkle/ingester" ) // ValueIngester is an ingester that ingests a single value. diff --git a/examples/gno.land/p/demo/gnorkle/message/gno.mod b/examples/gno.land/p/demo/gnorkle/message/gno.mod new file mode 100644 index 00000000000..5544d0eb873 --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/message/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/gnorkle/message diff --git a/examples/gno.land/p/demo/gnorkle/v1/message/parse.gno b/examples/gno.land/p/demo/gnorkle/message/parse.gno similarity index 100% rename from examples/gno.land/p/demo/gnorkle/v1/message/parse.gno rename to examples/gno.land/p/demo/gnorkle/message/parse.gno diff --git a/examples/gno.land/p/demo/gnorkle/v1/message/type.gno b/examples/gno.land/p/demo/gnorkle/message/type.gno similarity index 100% rename from examples/gno.land/p/demo/gnorkle/v1/message/type.gno rename to examples/gno.land/p/demo/gnorkle/message/type.gno diff --git a/examples/gno.land/p/demo/gnorkle/v1/readme.md b/examples/gno.land/p/demo/gnorkle/readme.md similarity index 100% rename from examples/gno.land/p/demo/gnorkle/v1/readme.md rename to examples/gno.land/p/demo/gnorkle/readme.md diff --git a/examples/gno.land/p/demo/gnorkle/storage/simple/gno.mod b/examples/gno.land/p/demo/gnorkle/storage/simple/gno.mod new file mode 100644 index 00000000000..0434d1749b0 --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/storage/simple/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/gnorkle/storage/simple + +require gno.land/p/demo/gnorkle/feed v0.0.0-latest diff --git a/examples/gno.land/p/demo/gnorkle/v1/storage/simple/storage.gno b/examples/gno.land/p/demo/gnorkle/storage/simple/storage.gno similarity index 97% rename from examples/gno.land/p/demo/gnorkle/v1/storage/simple/storage.gno rename to examples/gno.land/p/demo/gnorkle/storage/simple/storage.gno index 102c25fa407..7934a95392a 100644 --- a/examples/gno.land/p/demo/gnorkle/v1/storage/simple/storage.gno +++ b/examples/gno.land/p/demo/gnorkle/storage/simple/storage.gno @@ -3,7 +3,7 @@ package simple import ( "time" - "gno.land/p/demo/gnorkle/v1/feed" + "gno.land/p/demo/gnorkle/feed" ) // Storage is simple, bounded storage for published feed values. diff --git a/examples/gno.land/p/demo/gnorkle/v1/feed/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/feed/gno.mod deleted file mode 100644 index a8bd934d666..00000000000 --- a/examples/gno.land/p/demo/gnorkle/v1/feed/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/gnorkle/v1/feed diff --git a/examples/gno.land/p/demo/gnorkle/v1/feeds/static/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/feeds/static/gno.mod deleted file mode 100644 index 60299be1e6d..00000000000 --- a/examples/gno.land/p/demo/gnorkle/v1/feeds/static/gno.mod +++ /dev/null @@ -1,10 +0,0 @@ -module gno.land/p/demo/gnorkle/v1/feeds/static - -require ( - gno.land/p/demo/gnorkle/v1/feed v0.0.0-latest - gno.land/p/demo/gnorkle/v1/gnorkle v0.0.0-latest - gno.land/p/demo/gnorkle/v1/ingesters/single v0.0.0-latest - gno.land/p/demo/gnorkle/v1/message v0.0.0-latest - gno.land/p/demo/gnorkle/v1/storage/simple v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/gnorkle/gno.mod deleted file mode 100644 index 7ef1e02f659..00000000000 --- a/examples/gno.land/p/demo/gnorkle/v1/gnorkle/gno.mod +++ /dev/null @@ -1,9 +0,0 @@ -module gno.land/p/demo/gnorkle/v1/gnorkle - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/gnorkle/v1/agent v0.0.0-latest - gno.land/p/demo/gnorkle/v1/feed v0.0.0-latest - gno.land/p/demo/gnorkle/v1/ingester v0.0.0-latest - gno.land/p/demo/gnorkle/v1/message v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/gnorkle/v1/ingester/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/ingester/gno.mod deleted file mode 100644 index cde40e6f3df..00000000000 --- a/examples/gno.land/p/demo/gnorkle/v1/ingester/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/gnorkle/v1/ingester diff --git a/examples/gno.land/p/demo/gnorkle/v1/ingesters/single/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/ingesters/single/gno.mod deleted file mode 100644 index e803e22630b..00000000000 --- a/examples/gno.land/p/demo/gnorkle/v1/ingesters/single/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/p/demo/gnorkle/v1/ingesters/single - -require ( - gno.land/p/demo/gnorkle/v1/gnorkle v0.0.0-latest - gno.land/p/demo/gnorkle/v1/ingester v0.0.0-latest -) diff --git a/examples/gno.land/p/demo/gnorkle/v1/message/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/message/gno.mod deleted file mode 100644 index 7ae80e4cc35..00000000000 --- a/examples/gno.land/p/demo/gnorkle/v1/message/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/gnorkle/v1/message diff --git a/examples/gno.land/p/demo/gnorkle/v1/storage/simple/gno.mod b/examples/gno.land/p/demo/gnorkle/v1/storage/simple/gno.mod deleted file mode 100644 index eb593b72d4b..00000000000 --- a/examples/gno.land/p/demo/gnorkle/v1/storage/simple/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -module gno.land/p/demo/gnorkle/v1/storage/simple - -require gno.land/p/demo/gnorkle/v1/feed v0.0.0-latest diff --git a/examples/gno.land/r/gnoland/ghverify/contract.gno b/examples/gno.land/r/gnoland/ghverify/contract.gno index 10dd5eeb37b..1ae064df018 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract.gno @@ -4,10 +4,10 @@ import ( "std" "gno.land/p/demo/avl" - "gno.land/p/demo/gnorkle/v1/feed" - "gno.land/p/demo/gnorkle/v1/feeds/static" - "gno.land/p/demo/gnorkle/v1/gnorkle" - "gno.land/p/demo/gnorkle/v1/message" + "gno.land/p/demo/gnorkle/feed" + "gno.land/p/demo/gnorkle/feeds/static" + "gno.land/p/demo/gnorkle/gnorkle" + "gno.land/p/demo/gnorkle/message" ) const ( diff --git a/examples/gno.land/r/gnoland/ghverify/gno.mod b/examples/gno.land/r/gnoland/ghverify/gno.mod index 4ef7eb61924..25698975cd5 100644 --- a/examples/gno.land/r/gnoland/ghverify/gno.mod +++ b/examples/gno.land/r/gnoland/ghverify/gno.mod @@ -2,9 +2,9 @@ module gno.land/r/gnoland/ghverify require ( gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/gnorkle/v1/feed v0.0.0-latest - gno.land/p/demo/gnorkle/v1/feeds/static v0.0.0-latest - gno.land/p/demo/gnorkle/v1/gnorkle v0.0.0-latest - gno.land/p/demo/gnorkle/v1/message v0.0.0-latest + gno.land/p/demo/gnorkle/feed v0.0.0-latest + gno.land/p/demo/gnorkle/feeds/static v0.0.0-latest + gno.land/p/demo/gnorkle/gnorkle v0.0.0-latest + gno.land/p/demo/gnorkle/message v0.0.0-latest gno.land/p/demo/testutils v0.0.0-latest ) From 31f955a887a1a51d6639e917744282f005b52847 Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 22 Feb 2024 12:36:44 -0800 Subject: [PATCH 29/48] txtar test and contract bug fix --- .../gno.land/r/gnoland/ghverify/contract.gno | 14 +++++-- gno.land/cmd/gnoland/testdata/ghverify.txtar | 37 +++++++++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 gno.land/cmd/gnoland/testdata/ghverify.txtar diff --git a/examples/gno.land/r/gnoland/ghverify/contract.gno b/examples/gno.land/r/gnoland/ghverify/contract.gno index 1ae064df018..c747c56f279 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract.gno @@ -105,14 +105,20 @@ func SetOwner(owner string) { // GetHandleByAddress returns the github handle associated with the given gno address. func GetHandleByAddress(address string) string { - value, _ := addressToHandleMap.Get(address) - return value.(string) + if value, ok := addressToHandleMap.Get(address); ok { + return value.(string) + } + + return "" } // GetAddressByHandle returns the gno address associated with the given github handle. func GetAddressByHandle(handle string) string { - value, _ := handleToAddressMap.Get(handle) - return value.(string) + if value, ok := handleToAddressMap.Get(handle); ok { + return value.(string) + } + + return "" } // Render returns a json object string will all verified handle -> address mappings. diff --git a/gno.land/cmd/gnoland/testdata/ghverify.txtar b/gno.land/cmd/gnoland/testdata/ghverify.txtar new file mode 100644 index 00000000000..549ec480f08 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/ghverify.txtar @@ -0,0 +1,37 @@ +# start the node +gnoland start + +# make a verification request +gnokey maketx call -pkgpath gno.land/r/gnoland/ghverify -func RequestVerification -args 'deelawn' -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +# request tasks to complete (this is done by the agent) +gnokey maketx call -pkgpath gno.land/r/gnoland/ghverify -func GnorkleEntrypoint -args 'request' -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout '\("\[\{\\"id\\":\\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\\",\\"type\\":\\"0\\",\\"value_type\\":\\"string\\",\\"tasks\\":\[\{\\"gno_address\\":\\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\\",\\"github_handle\\":\\"deelawn\\"\}\]\}\]" string\)' + +# a verification request was made but there should be no verified address +gnokey maketx call -pkgpath gno.land/r/gnoland/ghverify -func GetHandleByAddress -args 'g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5' -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout "" + +# a verification request was made but there should be no verified handle +gnokey maketx call -pkgpath gno.land/r/gnoland/ghverify -func GetAddressByHandle -args 'deelawn' -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout "" + +# fail on ingestion with a bad task ID +! gnokey maketx call -pkgpath gno.land/r/gnoland/ghverify -func GnorkleEntrypoint -args 'ingest,a' -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stderr 'invalid ingest id: a' + +# the agent publishes their response to the task and the verification is complete +gnokey maketx call -pkgpath gno.land/r/gnoland/ghverify -func GnorkleEntrypoint -args 'ingest,g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5,OK' -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +# get verified github handle by gno address +gnokey maketx call -pkgpath gno.land/r/gnoland/ghverify -func GetHandleByAddress -args 'g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5' -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout "deelawn" + +# get verified gno address by github handle +gnokey maketx call -pkgpath gno.land/r/gnoland/ghverify -func GetAddressByHandle -args 'deelawn' -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" + +gnokey maketx call -pkgpath gno.land/r/gnoland/ghverify -func Render -args '' -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout '\("\{\\"deelawn\\": \\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\\"\}" string\)' \ No newline at end of file From 719bde48d1f3c272140c7baef3cd14baefdbcc9a Mon Sep 17 00:00:00 2001 From: deelawn Date: Fri, 23 Feb 2024 09:23:58 -0500 Subject: [PATCH 30/48] txtar loadpkg --- gno.land/cmd/gnoland/testdata/ghverify.txtar | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gno.land/cmd/gnoland/testdata/ghverify.txtar b/gno.land/cmd/gnoland/testdata/ghverify.txtar index 549ec480f08..f8cd05c762f 100644 --- a/gno.land/cmd/gnoland/testdata/ghverify.txtar +++ b/gno.land/cmd/gnoland/testdata/ghverify.txtar @@ -1,3 +1,5 @@ +loadpkg gno.land/r/gnoland/ghverify + # start the node gnoland start From 667fc062073541bffd2efc37e302eee0725551c9 Mon Sep 17 00:00:00 2001 From: deelawn Date: Mon, 4 Mar 2024 02:03:16 +0000 Subject: [PATCH 31/48] Update examples/gno.land/r/gnoland/ghverify/readme.md Co-authored-by: Morgan --- examples/gno.land/r/gnoland/ghverify/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/r/gnoland/ghverify/readme.md b/examples/gno.land/r/gnoland/ghverify/readme.md index 039e9b6be0a..d58c1014edb 100644 --- a/examples/gno.land/r/gnoland/ghverify/readme.md +++ b/examples/gno.land/r/gnoland/ghverify/readme.md @@ -5,5 +5,5 @@ The steps are as follows: - A user calls `RequestVerification` and provides a github handle. This creates a new static oracle feed. - An off-chain agent controlled by the owner of this realm requests current feeds using the `GnorkleEntrypoint` function and provides a message of `"request"` - The agent receives the task information that includes the github handle and the gno address. It performs the verification step by checking whether this github user has the address in a github repository it controls. -- The agent publishes the result of the verification by calling `GnorkleEntrypoint` with a message structured like: `"ingest,,`. The verification status is `OK` if verification succeeded and any other value if it failed. +- The agent publishes the result of the verification by calling `GnorkleEntrypoint` with a message structured like: `"ingest,,"`. The verification status is `OK` if verification succeeded and any other value if it failed. - The oracle feed's ingester processes the verification and the handle to address mapping is written to the avl trees that exist as ghverify realm variables. \ No newline at end of file From bead186c974a570620c77f596557cd267045decd Mon Sep 17 00:00:00 2001 From: deelawn Date: Sun, 3 Mar 2024 18:13:20 -0800 Subject: [PATCH 32/48] MarshalToJSON -> MarshalJSON --- examples/gno.land/p/demo/gnorkle/feed/task.gno | 2 +- examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno | 2 +- examples/gno.land/r/gnoland/ghverify/task.gno | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/gno.land/p/demo/gnorkle/feed/task.gno b/examples/gno.land/p/demo/gnorkle/feed/task.gno index 06dc52eca61..a68f256cb16 100644 --- a/examples/gno.land/p/demo/gnorkle/feed/task.gno +++ b/examples/gno.land/p/demo/gnorkle/feed/task.gno @@ -3,5 +3,5 @@ package feed // Task is a unit of work that can be part of a `Feed` definition. Tasks // are executed by agents. type Task interface { - MarshalToJSON() ([]byte, error) + MarshalJSON() ([]byte, error) } diff --git a/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno b/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno index c406163db2d..e3ad630caa1 100644 --- a/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno +++ b/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno @@ -117,7 +117,7 @@ func (f *Feed) MarshalJSON() ([]byte, error) { w.WriteString(",") } - taskJSON, err := task.MarshalToJSON() + taskJSON, err := task.MarshalJSON() if err != nil { return nil, err } diff --git a/examples/gno.land/r/gnoland/ghverify/task.gno b/examples/gno.land/r/gnoland/ghverify/task.gno index cbc8ee0fbe6..b850605158f 100644 --- a/examples/gno.land/r/gnoland/ghverify/task.gno +++ b/examples/gno.land/r/gnoland/ghverify/task.gno @@ -10,8 +10,8 @@ type verificationTask struct { githubHandle string } -// MarshalToJSON marshals the task contents to JSON. -func (t *verificationTask) MarshalToJSON() ([]byte, error) { +// MarshalJSON marshals the task contents to JSON. +func (t *verificationTask) MarshalJSON() ([]byte, error) { buf := new(bytes.Buffer) w := bufio.NewWriter(buf) From 89d8e33c0df3b8874f9f99b1e21bb45b1958d8f1 Mon Sep 17 00:00:00 2001 From: deelawn Date: Sun, 3 Mar 2024 18:20:25 -0800 Subject: [PATCH 33/48] capitalized readme --- examples/gno.land/p/demo/gnorkle/{readme.md => READM.md} | 0 examples/gno.land/r/gnoland/ghverify/{readme.md => READM.md} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename examples/gno.land/p/demo/gnorkle/{readme.md => READM.md} (100%) rename examples/gno.land/r/gnoland/ghverify/{readme.md => READM.md} (100%) diff --git a/examples/gno.land/p/demo/gnorkle/readme.md b/examples/gno.land/p/demo/gnorkle/READM.md similarity index 100% rename from examples/gno.land/p/demo/gnorkle/readme.md rename to examples/gno.land/p/demo/gnorkle/READM.md diff --git a/examples/gno.land/r/gnoland/ghverify/readme.md b/examples/gno.land/r/gnoland/ghverify/READM.md similarity index 100% rename from examples/gno.land/r/gnoland/ghverify/readme.md rename to examples/gno.land/r/gnoland/ghverify/READM.md From a4b301e06eb9d9a70c04949a2b919d0ebcbe3247 Mon Sep 17 00:00:00 2001 From: deelawn Date: Sun, 3 Mar 2024 18:20:53 -0800 Subject: [PATCH 34/48] filename typo --- examples/gno.land/p/demo/gnorkle/{READM.md => README.md} | 0 examples/gno.land/r/gnoland/ghverify/{READM.md => README.md} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename examples/gno.land/p/demo/gnorkle/{READM.md => README.md} (100%) rename examples/gno.land/r/gnoland/ghverify/{READM.md => README.md} (100%) diff --git a/examples/gno.land/p/demo/gnorkle/READM.md b/examples/gno.land/p/demo/gnorkle/README.md similarity index 100% rename from examples/gno.land/p/demo/gnorkle/READM.md rename to examples/gno.land/p/demo/gnorkle/README.md diff --git a/examples/gno.land/r/gnoland/ghverify/READM.md b/examples/gno.land/r/gnoland/ghverify/README.md similarity index 100% rename from examples/gno.land/r/gnoland/ghverify/READM.md rename to examples/gno.land/r/gnoland/ghverify/README.md From 5d45122beef63ef10ec414b114777b5d33d3183c Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 5 Mar 2024 16:23:01 -0800 Subject: [PATCH 35/48] added an example for each proposed feed type --- examples/gno.land/p/demo/gnorkle/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/gno.land/p/demo/gnorkle/README.md b/examples/gno.land/p/demo/gnorkle/README.md index 55b49462273..89f6b042a79 100644 --- a/examples/gno.land/p/demo/gnorkle/README.md +++ b/examples/gno.land/p/demo/gnorkle/README.md @@ -15,8 +15,11 @@ A single oracle instance may be composed of many feeds, each of which has a uniq Here is an example of what differences may exist amongst feed implementations: - Static: a static feed is one that only needs to produce a value once. It ingests values and then publishes the result. Once a single value is published, the state of the feed becomes immutable. + - Example: a realm wants to integrate football match results for its users. It may embed a static oracle that allows it to publish the match results to the chain. A static feed is a good choice for this because the match results will never change. - Continuous: a continuous feed can accept and ingest data, continously adding and changing its own internal state based on the data received. It can then publish values on demand based on its current state. -- Periodic: a periodic feed may give all whitelisted agents the opportunity to send data for ingestion within a bounded period of time. After this window closes, the results can be committed and a value is pubished. The process then begins again for the next period. + - Example: a realm wants to provide a verifiable random function. It embeds an oracle that defines tasks and whitelists a select group of trusted agents to provide data that gets combined to produce a random value. A continuous feed is a good choice for this because data may be accepted continously and there is no single static result. +- Periodic: periodic feed may give all whitelisted agents the opportunity to send data for ingestion within a bounded period of time. After this window closes, the results can be committed and a value is pubished. The process then begins again for the next period. + - Example: a realm wants to provide weather information to its users. It may choose a group of trusted agents to publish weather data for each half hour interval. This periodic feed can finalize the weather data for each postal code at the end of each interval using the aggregation function defined by the owner of the oracle. The only feed currently implemented is the `feeds/static.Feed` type. From 139984fea7bb37cb77d65c914b6bb9f3ac365626 Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 5 Mar 2024 17:02:50 -0800 Subject: [PATCH 36/48] added more examples --- examples/gno.land/p/demo/gnorkle/README.md | 68 ++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/examples/gno.land/p/demo/gnorkle/README.md b/examples/gno.land/p/demo/gnorkle/README.md index 89f6b042a79..6c129c8fb53 100644 --- a/examples/gno.land/p/demo/gnorkle/README.md +++ b/examples/gno.land/p/demo/gnorkle/README.md @@ -27,18 +27,86 @@ The only feed currently implemented is the `feeds/static.Feed` type. It's not hard to be a task -- just implement the one method `feed.Task` interface. On-chain task definitions should not do anything other than store data and be able to marshal that data to JSON. Of course, it is also useful if the task marshal's a `type` field as part of the JSON object so an agent is able to know what type of task it is dealing with and what data to expect in the payload. +### Example use case +Imagine there is a public API out there. The oracle defines a task with a type of `HTTPGET`. The agent interacting with the oracle knows how to extract the task's type and extract the rest of the data to complete the task +```go +type apiGet struct { + taskType string + url string +} +``` +So the data might look something like: +```json +apiGet { + "task_type": "HTTPGET", + "url": "http://example.com/api/latest" +} +``` +The agent can use this data to make the request and publish the results to the oracle. Tasks can have structures as complex or simple as is required. + ## Ingesters An ingester's primary role is to receive data provided by agents and figure out what to do with it. Ingesters must implement the `gnorkle.Ingester` interface. There are currently two message function types that an ingester may want to handle, `message.FuncTypeIngest` and `message.FuncTypeCommit`. The former message type should result in the ingester accumulating data in its own data store, while the latter should use what it has in its data store to publish a feed value to the `gnorkle.Storage` instance provided to it. The only ingester currently implemented is the `ingesters/single.ValueIngester` type. +### Example use case +The `Ingester` interface has two main methods that must be implemented, `Ingest` and `CommitValue`, as defined here: +```go +type Ingester interface { + Type() ingester.Type + Ingest(value, providerAddress string) (canAutoCommit bool) + CommitValue(storage Storage, providerAddress string) +} +``` +Consider an oracle that provides a price feed. This price feed would need an ingester capable of ingesting incoming values and producing a final result at the end of a given amount of time. So we may have something that looks like: +```go +type MultiValueIngester struct { + agentAddresses []string + prices []uint64 +} + +func (i *MultiValueIngester) Ingest(value, providerAddress string) bool { + price, err := strconv.ParseUint(value, 10, 64) + if err != nil { + panic("invalid value type") + } + + i.agentAddresses = append(i.agentAddresses, providerAddress) + i.prices = append(i.prices, price) + + // This value cannot be autocommitted to storage because the ingester expects + // multiple values and it doesn't know if this is the final value. + return false +} + +func (i *MultiValueIngester) CommitValue(storage Storage, providerAddress string) { + priceAggregate := i.aggregatePrices() + storage.Put(priceAggregate) + + // Reset to prepare for the next price period. + i.agentAddresses = []string{} + i.prices = []uint64{} +} +``` +An ingester is highly customizable and should be used to do any necessary aggregations. + ## Storage Storage types are responsible for storing values produced by a feed's ingester. A storage type must implement `gnorkle.Storage`. This type should be able add values to the storage, retrieve the latest value, and retrieve a set of historical values. It is probably a good idea to make the storage bounded. The only storage currently implemented is the `storage/simple.Storage` type. +### Example use case +In most cases the storage implementation will be a key value store, so its use case is fairly generic. The `Put` method is used to store finalized data and the `GetLatest` method can be used by consumers of the data. `Storage` is an interface in order to allow for memory management throughout the lifetime of the oracle writing data to it. The `GetHistory` method can be used, if desired, to keep a fixed historical window of published data points. +```go +type Storage interface { + Put(value string) + GetLatest() feed.Value + GetHistory() []feed.Value +} +``` + ## Whitelists Whitelists are optional but they can be set on both the oracle instance and the feed levels. The absence of a whitelist definition indicates that ALL addresses should be considered to be whitelisted. Otherwise, the presence of a defined whitelist indicates the callers address MUST be in the whitelist in order for the request to succeed. A feed whitelist has precedence over the oracle instance's whitelist. If a feed has a whitelist and the caller is not on it, the call fails. If a feed doesn't have a whitelist but the instance does and the caller is not on it, the call fails. If neither have a whitelist, the call succeeds. The whitlist logic mostly lives in `gnorkle/whitelist.gno` while the only current `gnorkle.Whitelist` implementation is the `agent.Whitelist` type. The whitelist is not owned by the feeds they are associated with in order to not further pollute the `gnorkle.Feed` interface. \ No newline at end of file From d10aa8b68559d01d975afe0c7cf9edb0026982bc Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 5 Mar 2024 17:07:00 -0800 Subject: [PATCH 37/48] set first to false after first iternation --- examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno b/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno index e3ad630caa1..f91c8aad204 100644 --- a/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno +++ b/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno @@ -123,6 +123,7 @@ func (f *Feed) MarshalJSON() ([]byte, error) { } w.Write(taskJSON) + first = false } w.Write([]byte("]}")) From 0ddd13d13ea62ed1341c23f00286995046319b35 Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 7 Mar 2024 10:28:10 -0800 Subject: [PATCH 38/48] make feed task arguments variadic --- examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno | 6 +++--- examples/gno.land/r/gnoland/ghverify/contract.gno | 8 +++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno b/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno index f91c8aad204..d56b1aeb8ed 100644 --- a/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno +++ b/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno @@ -28,7 +28,7 @@ func NewFeed( valueDataType string, ingester gnorkle.Ingester, storage gnorkle.Storage, - tasks []feed.Task, + tasks ...feed.Task, ) *Feed { return &Feed{ id: id, @@ -44,14 +44,14 @@ func NewFeed( func NewSingleValueFeed( id string, valueDataType string, - tasks []feed.Task, + tasks ...feed.Task, ) *Feed { return NewFeed( id, valueDataType, &single.ValueIngester{}, simple.NewStorage(1), - tasks, + tasks..., ) } diff --git a/examples/gno.land/r/gnoland/ghverify/contract.gno b/examples/gno.land/r/gnoland/ghverify/contract.gno index c747c56f279..c1b741bae32 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract.gno @@ -74,11 +74,9 @@ func RequestVerification(githubHandle string) { static.NewSingleValueFeed( gnoAddress, "string", - []feed.Task{ - &verificationTask{ - gnoAddress: gnoAddress, - githubHandle: githubHandle, - }, + &verificationTask{ + gnoAddress: gnoAddress, + githubHandle: githubHandle, }, ), ) From 0934899873dd4c6b205c3b03331888dc18459f0b Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 7 Mar 2024 11:08:43 -0800 Subject: [PATCH 39/48] return errors from package code and panic in the realm --- .../p/demo/gnorkle/feeds/static/feed.gno | 9 +- .../gno.land/p/demo/gnorkle/gnorkle/feed.gno | 2 +- .../p/demo/gnorkle/gnorkle/instance.gno | 116 ++++++++++++------ .../p/demo/gnorkle/gnorkle/whitelist.gno | 33 +++-- .../gno.land/r/gnoland/ghverify/contract.gno | 27 ++-- 5 files changed, 128 insertions(+), 59 deletions(-) diff --git a/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno b/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno index d56b1aeb8ed..6c033f2d6af 100644 --- a/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno +++ b/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno @@ -3,6 +3,7 @@ package static import ( "bufio" "bytes" + "errors" "gno.land/p/demo/gnorkle/feed" "gno.land/p/demo/gnorkle/gnorkle" @@ -67,9 +68,9 @@ func (f *Feed) Type() feed.Type { // Ingest ingests a message into the feed. It either adds the value to the ingester's // pending values or commits the value to the storage. -func (f *Feed) Ingest(funcType message.FuncType, msg, providerAddress string) { +func (f *Feed) Ingest(funcType message.FuncType, msg, providerAddress string) error { if f.isLocked { - panic("feed locked") + return errors.New("feed locked") } switch funcType { @@ -86,8 +87,10 @@ func (f *Feed) Ingest(funcType message.FuncType, msg, providerAddress string) { f.isLocked = true default: - panic("invalid message function " + string(funcType)) + return errors.New("invalid message function " + string(funcType)) } + + return nil } // Value returns the feed's latest value, it's data type, and whether or not it can diff --git a/examples/gno.land/p/demo/gnorkle/gnorkle/feed.gno b/examples/gno.land/p/demo/gnorkle/gnorkle/feed.gno index 4ac52f373b8..9651eb9287b 100644 --- a/examples/gno.land/p/demo/gnorkle/gnorkle/feed.gno +++ b/examples/gno.land/p/demo/gnorkle/gnorkle/feed.gno @@ -11,7 +11,7 @@ type Feed interface { ID() string Type() feed.Type Value() (value feed.Value, dataType string, consumable bool) - Ingest(funcType message.FuncType, rawMessage, providerAddress string) + Ingest(funcType message.FuncType, rawMessage, providerAddress string) error MarshalJSON() ([]byte, error) Tasks() []feed.Task IsActive() bool diff --git a/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno b/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno index 5bab51453a4..6179b4c896d 100644 --- a/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno +++ b/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno @@ -1,6 +1,7 @@ package gnorkle import ( + "errors" "std" "strings" @@ -23,27 +24,37 @@ func NewInstance() *Instance { } } -func assertValidID(id string) { +func assertValidID(id string) error { if len(id) == 0 { - panic("feed ids cannot be empty") + return errors.New("feed ids cannot be empty") } if strings.Contains(id, ",") { - panic("feed ids cannot contain commas") + return errors.New("feed ids cannot contain commas") } + + return nil } -func (i *Instance) assertFeedDoesNotExist(id string) { +func (i *Instance) assertFeedDoesNotExist(id string) error { if i.feeds.Has(id) { - panic("feed already exists") + return errors.New("feed already exists") } + + return nil } // AddFeeds adds feeds to the instance with empty whitelists. -func (i *Instance) AddFeeds(feeds ...Feed) { +func (i *Instance) AddFeeds(feeds ...Feed) error { for _, feed := range feeds { - assertValidID(feed.ID()) - i.assertFeedDoesNotExist(feed.ID()) + if err := assertValidID(feed.ID()); err != nil { + return err + } + + if err := i.assertFeedDoesNotExist(feed.ID()); err != nil { + return err + } + i.feeds.Set( feed.ID(), FeedWithWhitelist{ @@ -52,13 +63,20 @@ func (i *Instance) AddFeeds(feeds ...Feed) { }, ) } + + return nil } // AddFeedsWithWhitelists adds feeds to the instance with the given whitelists. -func (i *Instance) AddFeedsWithWhitelists(feeds ...FeedWithWhitelist) { +func (i *Instance) AddFeedsWithWhitelists(feeds ...FeedWithWhitelist) error { for _, feed := range feeds { - i.assertFeedDoesNotExist(feed.ID()) - assertValidID(feed.ID()) + if err := i.assertFeedDoesNotExist(feed.ID()); err != nil { + return err + } + if err := assertValidID(feed.ID()); err != nil { + return err + } + i.feeds.Set( feed.ID(), FeedWithWhitelist{ @@ -67,6 +85,8 @@ func (i *Instance) AddFeedsWithWhitelists(feeds ...FeedWithWhitelist) { }, ) } + + return nil } // RemoveFeed removes a feed from the instance. @@ -77,7 +97,7 @@ func (i *Instance) RemoveFeed(id string) { // PostMessageHandler is a type that allows for post-processing of feed state after a feed // ingests a message from an agent. type PostMessageHandler interface { - Handle(i *Instance, funcType message.FuncType, feed Feed) + Handle(i *Instance, funcType message.FuncType, feed Feed) error } // HandleMessage handles a message from an agent and routes to either the logic that returns @@ -85,7 +105,7 @@ type PostMessageHandler interface { // // TODO: Consider further message types that could allow administrative action such as modifying // a feed's whitelist without the owner of this oracle having to maintain a reference to it. -func (i *Instance) HandleMessage(msg string, postHandler PostMessageHandler) string { +func (i *Instance) HandleMessage(msg string, postHandler PostMessageHandler) (string, error) { caller := string(std.GetOrigCaller()) funcType, msg := message.ParseFunc(msg) @@ -96,92 +116,109 @@ func (i *Instance) HandleMessage(msg string, postHandler PostMessageHandler) str default: id, msg := message.ParseID(msg) - assertValidID(id) + if err := assertValidID(id); err != nil { + return "", err + } + + feedWithWhitelist, err := i.getFeedWithWhitelist(id) + if err != nil { + return "", err + } - feedWithWhitelist := i.getFeedWithWhitelist(id) if !addressIsWhitelisted(&i.whitelist, feedWithWhitelist, caller, nil) { - panic("caller not whitelisted") + return "", errors.New("caller not whitelisted") } - feedWithWhitelist.Ingest(funcType, msg, caller) + if err := feedWithWhitelist.Ingest(funcType, msg, caller); err != nil { + return "", err + } if postHandler != nil { postHandler.Handle(i, funcType, feedWithWhitelist) } } - return "" + return "", nil } -func (i *Instance) getFeed(id string) Feed { +func (i *Instance) getFeed(id string) (Feed, error) { untypedFeed, ok := i.feeds.Get(id) if !ok { - panic("invalid ingest id: " + id) + return nil, errors.New("invalid ingest id: " + id) } feed, ok := untypedFeed.(Feed) if !ok { - panic("invalid feed type") + return nil, errors.New("invalid feed type") } - return feed + return feed, nil } -func (i *Instance) getFeedWithWhitelist(id string) FeedWithWhitelist { +func (i *Instance) getFeedWithWhitelist(id string) (FeedWithWhitelist, error) { untypedFeedWithWhitelist, ok := i.feeds.Get(id) if !ok { - panic("invalid ingest id: " + id) + return nil, errors.New("invalid ingest id: " + id) } feedWithWhitelist, ok := untypedFeedWithWhitelist.(FeedWithWhitelist) if !ok { - panic("invalid feed with whitelist type") + return nil, errors.New("invalid feed with whitelist type") } - return feedWithWhitelist + return feedWithWhitelist, nil } // GetFeedValue returns the most recently published value of a feed along with a string // representation of the value's type and boolean indicating whether the value is // okay for consumption. -func (i *Instance) GetFeedValue(id string) (value feed.Value, valueType string, consumable bool) { - return i.getFeed(id).Value() +func (i *Instance) GetFeedValue(id string) (feed.Value, string, bool, error) { + foundFeed, err := i.getFeed(id) + if err != nil { + return feed.Value{}, "", false, err + } + + value, valueType, consumable := foundFeed.Value() + return value, valueType, consumable, nil } // GetFeedDefinitions returns a JSON string representing the feed definitions for which the given // agent address is whitelisted to provide values for ingestion. -func (i *Instance) GetFeedDefinitions(forAddress string) string { +func (i *Instance) GetFeedDefinitions(forAddress string) (string, error) { instanceHasAddressWhitelisted := !i.whitelist.HasDefinition() || i.whitelist.HasAddress(forAddress) buf := new(strings.Builder) buf.WriteString("[") first := true + var err error + // The boolean value returned by this callback function indicates whether to stop iterating. i.feeds.Iterate("", "", func(_ string, value interface{}) bool { feedWithWhitelist, ok := value.(FeedWithWhitelist) if !ok { - panic("invalid feed type") + err = errors.New("invalid feed type") + return true } // Don't give agents the ability to try to publish to inactive feeds. if !feedWithWhitelist.IsActive() { - return true + return false } // Skip feeds the address is not whitelisted for. if !addressIsWhitelisted(&i.whitelist, feedWithWhitelist, forAddress, &instanceHasAddressWhitelisted) { - return true + return false } - taskBytes, err := feedWithWhitelist.Feed.MarshalJSON() - if err != nil { - panic(err) + var taskBytes []byte + if taskBytes, err = feedWithWhitelist.Feed.MarshalJSON(); err != nil { + return true } // Guard against any tasks that shouldn't be returned; maybe they are not active because they have // already been completed. if len(taskBytes) == 0 { - return true + return false } if !first { @@ -192,6 +229,11 @@ func (i *Instance) GetFeedDefinitions(forAddress string) string { buf.Write(taskBytes) return true }) + + if err != nil { + return "", err + } + buf.WriteString("]") - return buf.String() + return buf.String(), nil } diff --git a/examples/gno.land/p/demo/gnorkle/gnorkle/whitelist.gno b/examples/gno.land/p/demo/gnorkle/gnorkle/whitelist.gno index f6ccace7ac6..22880f005e7 100644 --- a/examples/gno.land/p/demo/gnorkle/gnorkle/whitelist.gno +++ b/examples/gno.land/p/demo/gnorkle/gnorkle/whitelist.gno @@ -10,36 +10,51 @@ type Whitelist interface { } // ClearWhitelist clears the whitelist of the instance or feed depending on the feed ID. -func (i *Instance) ClearWhitelist(feedID string) { +func (i *Instance) ClearWhitelist(feedID string) error { if feedID == "" { i.whitelist.ClearAddresses() - return + return nil + } + + feedWithWhitelist, err := i.getFeedWithWhitelist(feedID) + if err != nil { + return err } - feedWithWhitelist := i.getFeedWithWhitelist(feedID) feedWithWhitelist.ClearAddresses() + return nil } // AddToWhitelist adds the given addresses to the whitelist of the instance or feed depending on the feed ID. -func (i *Instance) AddToWhitelist(feedID string, addresses []string) { +func (i *Instance) AddToWhitelist(feedID string, addresses []string) error { if feedID == "" { i.whitelist.AddAddresses(addresses) - return + return nil + } + + feedWithWhitelist, err := i.getFeedWithWhitelist(feedID) + if err != nil { + return err } - feedWithWhitelist := i.getFeedWithWhitelist(feedID) feedWithWhitelist.AddAddresses(addresses) + return nil } // RemoveFromWhitelist removes the given address from the whitelist of the instance or feed depending on the feed ID. -func (i *Instance) RemoveFromWhitelist(feedID string, address string) { +func (i *Instance) RemoveFromWhitelist(feedID string, address string) error { if feedID == "" { i.whitelist.RemoveAddress(address) - return + return nil + } + + feedWithWhitelist, err := i.getFeedWithWhitelist(feedID) + if err != nil { + return err } - feedWithWhitelist := i.getFeedWithWhitelist(feedID) feedWithWhitelist.RemoveAddress(address) + return nil } // addressWhiteListed returns true if: diff --git a/examples/gno.land/r/gnoland/ghverify/contract.gno b/examples/gno.land/r/gnoland/ghverify/contract.gno index c1b741bae32..6219be8cd00 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract.gno @@ -1,6 +1,7 @@ package ghverify import ( + "errors" "std" "gno.land/p/demo/avl" @@ -33,14 +34,14 @@ type postGnorkleMessageHandler struct{} // Handle does post processing after a message is ingested by the oracle feed. It extracts the value to realm // storage and removes the feed from the oracle. -func (h postGnorkleMessageHandler) Handle(i *gnorkle.Instance, funcType message.FuncType, feed gnorkle.Feed) { +func (h postGnorkleMessageHandler) Handle(i *gnorkle.Instance, funcType message.FuncType, feed gnorkle.Feed) error { if funcType != message.FuncTypeIngest { - return + return nil } result, _, consumable := feed.Value() if !consumable { - return + return nil } // The value is consumable, meaning the ingestion occurred, so we can remove the feed from the oracle @@ -49,28 +50,29 @@ func (h postGnorkleMessageHandler) Handle(i *gnorkle.Instance, funcType message. // Couldn't verify; nothing to do. if result.String != verifiedResult { - return + return nil } feedTasks := feed.Tasks() if len(feedTasks) != 1 { - panic("expected feed to have exactly one task") + return errors.New("expected feed to have exactly one task") } task, ok := feedTasks[0].(*verificationTask) if !ok { - panic("expected ghverify task") + return errors.New("expected ghverify task") } handleToAddressMap.Set(task.githubHandle, task.gnoAddress) addressToHandleMap.Set(task.gnoAddress, task.githubHandle) + return nil } // RequestVerification creates a new static feed with a single task that will // instruct an agent to verify the github handle / gno address pair. func RequestVerification(githubHandle string) { gnoAddress := string(std.GetOrigCaller()) - oracle.AddFeeds( + if err := oracle.AddFeeds( static.NewSingleValueFeed( gnoAddress, "string", @@ -79,12 +81,19 @@ func RequestVerification(githubHandle string) { githubHandle: githubHandle, }, ), - ) + ); err != nil { + panic(err) + } } // GnorkleEntrypoint is the entrypoint to the gnorkle oracle handler. func GnorkleEntrypoint(message string) string { - return oracle.HandleMessage(message, postHandler) + result, err := oracle.HandleMessage(message, postHandler) + if err != nil { + panic(err) + } + + return result } // SetOwner transfers ownership of the contract to the given address. From 3a500aa39239f7b056d0f7bc9b30b5be543ccdee Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 7 Mar 2024 11:22:13 -0800 Subject: [PATCH 40/48] fixed contract tests --- examples/gno.land/r/gnoland/ghverify/contract_test.gno | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/gno.land/r/gnoland/ghverify/contract_test.gno b/examples/gno.land/r/gnoland/ghverify/contract_test.gno index 6a459573c70..ac3c1f32280 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract_test.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract_test.gno @@ -28,7 +28,7 @@ func TestVerificationLifecycle(t *testing.T) { func() { defer func() { if r := recover(); r != nil { - errMsg = r.(string) + errMsg = r.(error).Error() } }() RequestVerification("deelawn") @@ -59,7 +59,7 @@ func TestVerificationLifecycle(t *testing.T) { func() { defer func() { if r := recover(); r != nil { - errMsg = r.(string) + errMsg = r.(error).Error() } }() GnorkleEntrypoint("ingest," + string(userAddress) + ",OK") From 6b9c9de8b10719c9011ebe4a85b43c1567fb7d3d Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 7 Mar 2024 11:35:18 -0800 Subject: [PATCH 41/48] use proper return types --- examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno b/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno index 6179b4c896d..22746d569a8 100644 --- a/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno +++ b/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno @@ -158,12 +158,12 @@ func (i *Instance) getFeed(id string) (Feed, error) { func (i *Instance) getFeedWithWhitelist(id string) (FeedWithWhitelist, error) { untypedFeedWithWhitelist, ok := i.feeds.Get(id) if !ok { - return nil, errors.New("invalid ingest id: " + id) + return FeedWithWhitelist{}, errors.New("invalid ingest id: " + id) } feedWithWhitelist, ok := untypedFeedWithWhitelist.(FeedWithWhitelist) if !ok { - return nil, errors.New("invalid feed with whitelist type") + return FeedWithWhitelist{}, errors.New("invalid feed with whitelist type") } return feedWithWhitelist, nil From fed4a3fe686818ec6717797f6924ae9dc1688597 Mon Sep 17 00:00:00 2001 From: deelawn Date: Sun, 28 Apr 2024 15:20:34 -0700 Subject: [PATCH 42/48] remove unused import --- examples/gno.land/r/gnoland/ghverify/contract.gno | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/gno.land/r/gnoland/ghverify/contract.gno b/examples/gno.land/r/gnoland/ghverify/contract.gno index 6219be8cd00..280d0068dfc 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract.gno @@ -5,7 +5,6 @@ import ( "std" "gno.land/p/demo/avl" - "gno.land/p/demo/gnorkle/feed" "gno.land/p/demo/gnorkle/feeds/static" "gno.land/p/demo/gnorkle/gnorkle" "gno.land/p/demo/gnorkle/message" From 850393cb1f4d631946cfdbabb3e1179af80e770d Mon Sep 17 00:00:00 2001 From: deelawn Date: Fri, 3 May 2024 08:13:07 -0700 Subject: [PATCH 43/48] add error return types to methods of gnorkle interfaces and consolidate some code --- .../p/demo/gnorkle/feeds/static/feed.gno | 14 +++++++++++--- .../gno.land/p/demo/gnorkle/gnorkle/ingester.gno | 4 ++-- .../gno.land/p/demo/gnorkle/gnorkle/storage.gno | 2 +- .../gno.land/p/demo/gnorkle/ingester/errors.gno | 5 +++++ .../p/demo/gnorkle/ingesters/single/ingester.gno | 16 ++++++++++++---- .../gno.land/p/demo/gnorkle/message/parse.gno | 12 ++++++------ .../gno.land/p/demo/gnorkle/storage/errors.gno | 5 +++++ examples/gno.land/p/demo/gnorkle/storage/gno.mod | 1 + .../p/demo/gnorkle/storage/simple/gno.mod | 5 ++++- .../p/demo/gnorkle/storage/simple/storage.gno | 13 ++++++++++--- 10 files changed, 57 insertions(+), 20 deletions(-) create mode 100644 examples/gno.land/p/demo/gnorkle/ingester/errors.gno create mode 100644 examples/gno.land/p/demo/gnorkle/storage/errors.gno create mode 100644 examples/gno.land/p/demo/gnorkle/storage/gno.mod diff --git a/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno b/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno index 6c033f2d6af..1b9b0bc495c 100644 --- a/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno +++ b/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno @@ -77,13 +77,21 @@ func (f *Feed) Ingest(funcType message.FuncType, msg, providerAddress string) er case message.FuncTypeIngest: // Autocommit the ingester's value if it's a single value ingester // because this is a static feed and this is the only value it will ever have. - if canAutoCommit := f.ingester.Ingest(msg, providerAddress); canAutoCommit { - f.ingester.CommitValue(f.storage, providerAddress) + if canAutoCommit, err := f.ingester.Ingest(msg, providerAddress); canAutoCommit && err == nil { + if err := f.ingester.CommitValue(f.storage, providerAddress); err != nil { + return err + } + f.isLocked = true + } else if err != nil { + return err } case message.FuncTypeCommit: - f.ingester.CommitValue(f.storage, providerAddress) + if err := f.ingester.CommitValue(f.storage, providerAddress); err != nil { + return err + } + f.isLocked = true default: diff --git a/examples/gno.land/p/demo/gnorkle/gnorkle/ingester.gno b/examples/gno.land/p/demo/gnorkle/gnorkle/ingester.gno index 827df0a1685..da8ce9bdecf 100644 --- a/examples/gno.land/p/demo/gnorkle/gnorkle/ingester.gno +++ b/examples/gno.land/p/demo/gnorkle/gnorkle/ingester.gno @@ -6,6 +6,6 @@ import "gno.land/p/demo/gnorkle/ingester" // and commit it to storage using zero or more intermediate aggregation steps. type Ingester interface { Type() ingester.Type - Ingest(value, providerAddress string) (canAutoCommit bool) - CommitValue(storage Storage, providerAddress string) + Ingest(value, providerAddress string) (canAutoCommit bool, err error) + CommitValue(storage Storage, providerAddress string) error } diff --git a/examples/gno.land/p/demo/gnorkle/gnorkle/storage.gno b/examples/gno.land/p/demo/gnorkle/gnorkle/storage.gno index 7dee3b84a16..d8c8e17351d 100644 --- a/examples/gno.land/p/demo/gnorkle/gnorkle/storage.gno +++ b/examples/gno.land/p/demo/gnorkle/gnorkle/storage.gno @@ -5,7 +5,7 @@ import "gno.land/p/demo/gnorkle/feed" // Storage defines how published feed values should be read // and written. type Storage interface { - Put(value string) + Put(value string) error GetLatest() feed.Value GetHistory() []feed.Value } diff --git a/examples/gno.land/p/demo/gnorkle/ingester/errors.gno b/examples/gno.land/p/demo/gnorkle/ingester/errors.gno new file mode 100644 index 00000000000..8dcedcec7c5 --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/ingester/errors.gno @@ -0,0 +1,5 @@ +package ingester + +import "errors" + +var ErrUndefined = errors.New("ingester undefined") diff --git a/examples/gno.land/p/demo/gnorkle/ingesters/single/ingester.gno b/examples/gno.land/p/demo/gnorkle/ingesters/single/ingester.gno index b242236a80d..4b437834e67 100644 --- a/examples/gno.land/p/demo/gnorkle/ingesters/single/ingester.gno +++ b/examples/gno.land/p/demo/gnorkle/ingesters/single/ingester.gno @@ -16,12 +16,20 @@ func (i *ValueIngester) Type() ingester.Type { } // Ingest ingests a value provided by the given agent address. -func (i *ValueIngester) Ingest(value, providerAddress string) bool { +func (i *ValueIngester) Ingest(value, providerAddress string) (bool, error) { + if i == nil { + return false, ingester.ErrUndefined + } + i.value = value - return true + return true, nil } // CommitValue commits the ingested value to the given storage instance. -func (i *ValueIngester) CommitValue(valueStorer gnorkle.Storage, providerAddress string) { - valueStorer.Put(i.value) +func (i *ValueIngester) CommitValue(valueStorer gnorkle.Storage, providerAddress string) error { + if i == nil { + return ingester.ErrUndefined + } + + return valueStorer.Put(i.value) } diff --git a/examples/gno.land/p/demo/gnorkle/message/parse.gno b/examples/gno.land/p/demo/gnorkle/message/parse.gno index d8d0edde05d..633dcc38b66 100644 --- a/examples/gno.land/p/demo/gnorkle/message/parse.gno +++ b/examples/gno.land/p/demo/gnorkle/message/parse.gno @@ -5,17 +5,17 @@ import "strings" // ParseFunc parses a raw message and returns the message function // type extracted from the remainder of the message. func ParseFunc(rawMsg string) (FuncType, string) { - msgParts := strings.SplitN(rawMsg, ",", 2) - if len(msgParts) < 2 { - return FuncType(msgParts[0]), "" - } - - return FuncType(msgParts[0]), msgParts[1] + funcType, remainder := parseFirstToken(rawMsg) + return FuncType(funcType), remainder } // ParseID parses a raw message and returns the ID extracted from // the remainder of the message. func ParseID(rawMsg string) (string, string) { + return parseFirstToken(rawMsg) +} + +func parseFirstToken(rawMsg string) (string, string) { msgParts := strings.SplitN(rawMsg, ",", 2) if len(msgParts) < 2 { return msgParts[0], "" diff --git a/examples/gno.land/p/demo/gnorkle/storage/errors.gno b/examples/gno.land/p/demo/gnorkle/storage/errors.gno new file mode 100644 index 00000000000..d38808721e0 --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/storage/errors.gno @@ -0,0 +1,5 @@ +package storage + +import "errors" + +var ErrUndefined = errors.New("undefined storage") diff --git a/examples/gno.land/p/demo/gnorkle/storage/gno.mod b/examples/gno.land/p/demo/gnorkle/storage/gno.mod new file mode 100644 index 00000000000..da5b278537d --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/storage/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/gnorkle/storage \ No newline at end of file diff --git a/examples/gno.land/p/demo/gnorkle/storage/simple/gno.mod b/examples/gno.land/p/demo/gnorkle/storage/simple/gno.mod index 0434d1749b0..d3b7b8af814 100644 --- a/examples/gno.land/p/demo/gnorkle/storage/simple/gno.mod +++ b/examples/gno.land/p/demo/gnorkle/storage/simple/gno.mod @@ -1,3 +1,6 @@ module gno.land/p/demo/gnorkle/storage/simple -require gno.land/p/demo/gnorkle/feed v0.0.0-latest +require ( + gno.land/p/demo/gnorkle/feed v0.0.0-latest + gno.land/p/demo/gnorkle/storage v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/gnorkle/storage/simple/storage.gno b/examples/gno.land/p/demo/gnorkle/storage/simple/storage.gno index 7934a95392a..3f7d60e4066 100644 --- a/examples/gno.land/p/demo/gnorkle/storage/simple/storage.gno +++ b/examples/gno.land/p/demo/gnorkle/storage/simple/storage.gno @@ -4,6 +4,7 @@ import ( "time" "gno.land/p/demo/gnorkle/feed" + "gno.land/p/demo/gnorkle/storage" ) // Storage is simple, bounded storage for published feed values. @@ -27,15 +28,21 @@ func NewStorage(maxValues uint) *Storage { // Put adds a new value to the storage. If the storage is full, the oldest value // is removed. If maxValues is 0, the storage is bounded to a size of one. -func (s *Storage) Put(value string) { +func (s *Storage) Put(value string) error { + if s == nil { + return storage.ErrUndefined + } + s.values = append(s.values, feed.Value{String: value, Time: time.Now()}) if uint(len(s.values)) > s.maxValues { s.values = s.values[1:] } + + return nil } // GetLatest returns the most recently added value, or an empty value if none exist. -func (s *Storage) GetLatest() feed.Value { +func (s Storage) GetLatest() feed.Value { if len(s.values) == 0 { return feed.Value{} } @@ -44,6 +51,6 @@ func (s *Storage) GetLatest() feed.Value { } // GetHistory returns all values in the storage, from oldest to newest. -func (s *Storage) GetHistory() []feed.Value { +func (s Storage) GetHistory() []feed.Value { return s.values } From 3960abce04a6c7eef5d71a7a0e80ec8ab615361a Mon Sep 17 00:00:00 2001 From: deelawn Date: Fri, 3 May 2024 14:17:19 -0700 Subject: [PATCH 44/48] added some tests --- .../p/demo/gnorkle/agent/whitelist_test.gno | 47 ++++++++++ .../gno.land/p/demo/gnorkle/feed/errors.gno | 5 ++ .../p/demo/gnorkle/feeds/static/feed.gno | 16 ++-- .../p/demo/gnorkle/feeds/static/feed_test.gno | 51 +++++++++++ .../p/demo/gnorkle/feeds/static/gno.mod | 1 + .../p/demo/gnorkle/gnorkle/whitelist.gno | 2 +- .../ingesters/single/ingester_test.gno | 43 +++++++++ .../p/demo/gnorkle/message/parse_test.gno | 50 +++++++++++ .../gnorkle/storage/simple/storage_test.gno | 90 +++++++++++++++++++ 9 files changed, 298 insertions(+), 7 deletions(-) create mode 100644 examples/gno.land/p/demo/gnorkle/agent/whitelist_test.gno create mode 100644 examples/gno.land/p/demo/gnorkle/feed/errors.gno create mode 100644 examples/gno.land/p/demo/gnorkle/feeds/static/feed_test.gno create mode 100644 examples/gno.land/p/demo/gnorkle/ingesters/single/ingester_test.gno create mode 100644 examples/gno.land/p/demo/gnorkle/message/parse_test.gno create mode 100644 examples/gno.land/p/demo/gnorkle/storage/simple/storage_test.gno diff --git a/examples/gno.land/p/demo/gnorkle/agent/whitelist_test.gno b/examples/gno.land/p/demo/gnorkle/agent/whitelist_test.gno new file mode 100644 index 00000000000..8c306a24a93 --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/agent/whitelist_test.gno @@ -0,0 +1,47 @@ +package agent_test + +import ( + "testing" + + "gno.land/p/demo/gnorkle/agent" +) + +func TestWhitelist(t *testing.T) { + var whitelist agent.Whitelist + + if whitelist.HasDefinition() { + t.Error("whitelist should not be defined initially") + } + + whitelist.AddAddresses([]string{"a", "b"}) + if !whitelist.HasAddress("a") { + t.Error(`whitelist should have address "a"`) + } + if !whitelist.HasAddress("b") { + t.Error(`whitelist should have address "b"`) + } + + if !whitelist.HasDefinition() { + t.Error("whitelist should be defined after adding addresses") + } + + whitelist.RemoveAddress("a") + if whitelist.HasAddress("a") { + t.Error(`whitelist should not have address "a"`) + } + if !whitelist.HasAddress("b") { + t.Error(`whitelist should still have address "b"`) + } + + whitelist.ClearAddresses() + if whitelist.HasAddress("a") { + t.Error(`whitelist cleared; should not have address "a"`) + } + if whitelist.HasAddress("b") { + t.Error(`whitelist cleared; should still have address "b"`) + } + + if whitelist.HasDefinition() { + t.Error("whitelist cleared; should not be defined") + } +} diff --git a/examples/gno.land/p/demo/gnorkle/feed/errors.gno b/examples/gno.land/p/demo/gnorkle/feed/errors.gno new file mode 100644 index 00000000000..b4f71311c07 --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/feed/errors.gno @@ -0,0 +1,5 @@ +package feed + +import "errors" + +var ErrUndefined = errors.New("undefined feed") diff --git a/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno b/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno index 1b9b0bc495c..2c3aeec905c 100644 --- a/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno +++ b/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno @@ -57,18 +57,22 @@ func NewSingleValueFeed( } // ID returns the feed's ID. -func (f *Feed) ID() string { +func (f Feed) ID() string { return f.id } // Type returns the feed's type. -func (f *Feed) Type() feed.Type { +func (f Feed) Type() feed.Type { return feed.TypeStatic } // Ingest ingests a message into the feed. It either adds the value to the ingester's // pending values or commits the value to the storage. func (f *Feed) Ingest(funcType message.FuncType, msg, providerAddress string) error { + if f == nil { + return feed.ErrUndefined + } + if f.isLocked { return errors.New("feed locked") } @@ -105,13 +109,13 @@ func (f *Feed) Ingest(funcType message.FuncType, msg, providerAddress string) er // be safely consumed. In this case it uses `f.isLocked` because, this being a static // feed, it will only ever have one value; once that value is committed the feed is locked // and there is a valid, non-empty value to consume. -func (f *Feed) Value() (feed.Value, string, bool) { +func (f Feed) Value() (feed.Value, string, bool) { return f.storage.GetLatest(), f.valueDataType, f.isLocked } // MarshalJSON marshals the components of the feed that are needed for // an agent to execute tasks and send values for ingestion. -func (f *Feed) MarshalJSON() ([]byte, error) { +func (f Feed) MarshalJSON() ([]byte, error) { buf := new(bytes.Buffer) w := bufio.NewWriter(buf) @@ -145,11 +149,11 @@ func (f *Feed) MarshalJSON() ([]byte, error) { // Tasks returns the feed's tasks. This allows task consumers to extract task // contents without having to marshal the entire feed. -func (f *Feed) Tasks() []feed.Task { +func (f Feed) Tasks() []feed.Task { return f.tasks } // IsActive returns true if the feed is accepting ingestion requests from agents. -func (f *Feed) IsActive() bool { +func (f Feed) IsActive() bool { return !f.isLocked } diff --git a/examples/gno.land/p/demo/gnorkle/feeds/static/feed_test.gno b/examples/gno.land/p/demo/gnorkle/feeds/static/feed_test.gno new file mode 100644 index 00000000000..8f75378b883 --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/feeds/static/feed_test.gno @@ -0,0 +1,51 @@ +package static_test + +import ( + "testing" + + "gno.land/p/demo/gnorkle/feed" + "gno.land/p/demo/gnorkle/feeds/static" + "gno.land/p/demo/gnorkle/gnorkle" + "gno.land/p/demo/gnorkle/ingester" +) + +type mockIngester struct { + canAutoCommit bool + err error + value string + providerAddress string +} + +func (i mockIngester) Type() ingester.Type { + return ingester.Type(0) +} + +func (i *mockIngester) Ingest(value, providerAddress string) (bool, error) { + if i.err != nil { + return false, i.err + } + + i.value = value + return i.canAutoCommit, nil +} + +func (i *mockIngester) CommitValue(storage gnorkle.Storage, providerAddress string) error { + if i.err != nil { + return i.err + } + + return storage.Put(i.value) +} + +func TestNewSingleValueFeed(t *testing.T) { + staticFeed := static.NewSingleValueFeed("1", "") + if staticFeed.ID() != "1" { + t.Errorf("expected ID to be 1, got %s", staticFeed.ID()) + } + + if staticFeed.Type() != feed.TypeStatic { + t.Errorf("expected static feed type, got %s", staticFeed.Type()) + } +} + +// TODO: Finish tests for this. diff --git a/examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod b/examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod index 7893fd2bf76..64d5570878b 100644 --- a/examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod +++ b/examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod @@ -3,6 +3,7 @@ module gno.land/p/demo/gnorkle/feeds/static require ( gno.land/p/demo/gnorkle/feed v0.0.0-latest gno.land/p/demo/gnorkle/gnorkle v0.0.0-latest + gno.land/p/demo/gnorkle/ingester v0.0.0-latest gno.land/p/demo/gnorkle/ingesters/single v0.0.0-latest gno.land/p/demo/gnorkle/message v0.0.0-latest gno.land/p/demo/gnorkle/storage/simple v0.0.0-latest diff --git a/examples/gno.land/p/demo/gnorkle/gnorkle/whitelist.gno b/examples/gno.land/p/demo/gnorkle/gnorkle/whitelist.gno index 22880f005e7..321a0467ad7 100644 --- a/examples/gno.land/p/demo/gnorkle/gnorkle/whitelist.gno +++ b/examples/gno.land/p/demo/gnorkle/gnorkle/whitelist.gno @@ -91,6 +91,6 @@ func addressIsWhitelisted(instanceWhitelist, feedWhitelist Whitelist, address st return true } - // The instance whitelist is defined so it the address is present then it is allowed. + // The instance whitelist is defined so if the address is present then it is allowed. return instanceWhitelist.HasAddress(address) } diff --git a/examples/gno.land/p/demo/gnorkle/ingesters/single/ingester_test.gno b/examples/gno.land/p/demo/gnorkle/ingesters/single/ingester_test.gno new file mode 100644 index 00000000000..b394fbdcc2f --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/ingesters/single/ingester_test.gno @@ -0,0 +1,43 @@ +package single_test + +import ( + "testing" + + "gno.land/p/demo/gnorkle/ingester" + "gno.land/p/demo/gnorkle/ingesters/single" + "gno.land/p/demo/gnorkle/storage/simple" +) + +func TestValueIngester(t *testing.T) { + storage := simple.NewStorage(1) + + var undefinedIngester *single.ValueIngester + if _, err := undefinedIngester.Ingest("asdf", "gno11111"); err != ingester.ErrUndefined { + t.Error("undefined ingester call to Ingest should return ingester.ErrUndefined") + } + if err := undefinedIngester.CommitValue(storage, "gno11111"); err != ingester.ErrUndefined { + t.Error("undefined ingester call to CommitValue should return ingester.ErrUndefined") + } + + var valueIngester single.ValueIngester + if typ := valueIngester.Type(); typ != ingester.TypeSingle { + t.Error("single value ingester should return type ingester.TypeSingle") + } + + ingestValue := "value" + autocommit, err := valueIngester.Ingest(ingestValue, "gno11111") + if !autocommit { + t.Error("single value ingester should return autocommit true") + } + if err != nil { + t.Errorf("unexpected ingest error %s", err.Error()) + } + + if err := valueIngester.CommitValue(storage, "gno11111"); err != nil { + t.Errorf("unexpected commit error %s", err.Error()) + } + + if latestValue := storage.GetLatest(); latestValue.String != ingestValue { + t.Errorf("expected latest value of %s, got %s", ingestValue, latestValue.String) + } +} diff --git a/examples/gno.land/p/demo/gnorkle/message/parse_test.gno b/examples/gno.land/p/demo/gnorkle/message/parse_test.gno new file mode 100644 index 00000000000..7e1c66c5182 --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/message/parse_test.gno @@ -0,0 +1,50 @@ +package message_test + +import ( + "testing" + + "gno.land/p/demo/gnorkle/message" +) + +func TestParseFunc(t *testing.T) { + tests := []struct { + name string + input string + expFuncType message.FuncType + expRemainder string + }{ + { + name: "empty", + }, + { + name: "func only", + input: "ingest", + expFuncType: message.FuncTypeIngest, + }, + { + name: "func with short remainder", + input: "commit,asdf", + expFuncType: message.FuncTypeCommit, + expRemainder: "asdf", + }, + { + name: "func with long remainder", + input: "request,hello,world,goodbye", + expFuncType: message.FuncTypeRequest, + expRemainder: "hello,world,goodbye", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + funcType, remainder := message.ParseFunc(tt.input) + if funcType != tt.expFuncType { + t.Errorf("expected func type %s, got %s", tt.expFuncType, funcType) + } + + if remainder != tt.expRemainder { + t.Errorf("expected remainder of %s, got %s", tt.expRemainder, remainder) + } + }) + } +} diff --git a/examples/gno.land/p/demo/gnorkle/storage/simple/storage_test.gno b/examples/gno.land/p/demo/gnorkle/storage/simple/storage_test.gno new file mode 100644 index 00000000000..4f0c1c45c40 --- /dev/null +++ b/examples/gno.land/p/demo/gnorkle/storage/simple/storage_test.gno @@ -0,0 +1,90 @@ +package simple_test + +import ( + "testing" + + "gno.land/p/demo/gnorkle/storage" + "gno.land/p/demo/gnorkle/storage/simple" +) + +func TestStorage(t *testing.T) { + var undefinedStorage *simple.Storage + if err := undefinedStorage.Put(""); err != storage.ErrUndefined { + t.Error("expected storage.ErrUndefined on undefined storage") + } + + tests := []struct { + name string + valuesToPut []string + expLatestValueString string + expLatestValueTimeIsZero bool + expHistoricalValueStrings []string + }{ + { + name: "empty", + expLatestValueTimeIsZero: true, + }, + { + name: "one value", + valuesToPut: []string{"one"}, + expLatestValueString: "one", + expHistoricalValueStrings: []string{"one"}, + }, + { + name: "two values", + valuesToPut: []string{"one", "two"}, + expLatestValueString: "two", + expHistoricalValueStrings: []string{"one", "two"}, + }, + { + name: "three values", + valuesToPut: []string{"one", "two", "three"}, + expLatestValueString: "three", + expHistoricalValueStrings: []string{"two", "three"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + simpleStorage := simple.NewStorage(2) + for _, value := range tt.valuesToPut { + if err := simpleStorage.Put(value); err != nil { + t.Fatal("unexpected error putting value in storage") + } + } + + latestValue := simpleStorage.GetLatest() + if latestValue.String != tt.expLatestValueString { + t.Errorf("expected latest value of %s, got %s", tt.expLatestValueString, latestValue.String) + } + + if latestValue.Time.IsZero() != tt.expLatestValueTimeIsZero { + t.Errorf( + "expected latest value time zero value of %t, got %t", + tt.expLatestValueTimeIsZero, + latestValue.Time.IsZero(), + ) + } + + historicalValues := simpleStorage.GetHistory() + if len(historicalValues) != len(tt.expHistoricalValueStrings) { + t.Fatal("historical values length does not match") + } + + for i, expValue := range tt.expHistoricalValueStrings { + if expValue != historicalValues[i].String { + t.Errorf( + "expected historical value at idx %d to be %s, got %s", + i, + expValue, + historicalValues[i].String, + ) + } + + if historicalValues[i].Time.IsZero() { + t.Errorf("unexpeced zero time for historical value at index %d", i) + } + } + }) + } +} From 6fa61f7145b0f993d8064548b513fe1cb4865bc3 Mon Sep 17 00:00:00 2001 From: deelawn Date: Mon, 6 May 2024 18:14:18 -0700 Subject: [PATCH 45/48] added feed tests --- .../p/demo/gnorkle/feeds/static/feed_test.gno | 245 +++++++++++++++++- 1 file changed, 239 insertions(+), 6 deletions(-) diff --git a/examples/gno.land/p/demo/gnorkle/feeds/static/feed_test.gno b/examples/gno.land/p/demo/gnorkle/feeds/static/feed_test.gno index 8f75378b883..af8522b5aac 100644 --- a/examples/gno.land/p/demo/gnorkle/feeds/static/feed_test.gno +++ b/examples/gno.land/p/demo/gnorkle/feeds/static/feed_test.gno @@ -1,17 +1,21 @@ package static_test import ( + "errors" "testing" "gno.land/p/demo/gnorkle/feed" "gno.land/p/demo/gnorkle/feeds/static" "gno.land/p/demo/gnorkle/gnorkle" "gno.land/p/demo/gnorkle/ingester" + "gno.land/p/demo/gnorkle/message" + "gno.land/p/demo/gnorkle/storage/simple" ) type mockIngester struct { canAutoCommit bool - err error + ingestErr error + commitErr error value string providerAddress string } @@ -21,17 +25,18 @@ func (i mockIngester) Type() ingester.Type { } func (i *mockIngester) Ingest(value, providerAddress string) (bool, error) { - if i.err != nil { - return false, i.err + if i.ingestErr != nil { + return false, i.ingestErr } i.value = value + i.providerAddress = providerAddress return i.canAutoCommit, nil } func (i *mockIngester) CommitValue(storage gnorkle.Storage, providerAddress string) error { - if i.err != nil { - return i.err + if i.commitErr != nil { + return i.commitErr } return storage.Put(i.value) @@ -48,4 +53,232 @@ func TestNewSingleValueFeed(t *testing.T) { } } -// TODO: Finish tests for this. +func TestFeed_Ingest(t *testing.T) { + var undefinedFeed *static.Feed + if undefinedFeed.Ingest("", "", "") != feed.ErrUndefined { + t.Errorf("expected ErrUndefined, got nil") + } + + tests := []struct { + name string + ingester *mockIngester + verifyIsLocked bool + doCommit bool + funcType message.FuncType + msg string + providerAddress string + expFeedValueString string + expErrText string + expIsActive bool + }{ + { + name: "func invalid error", + ingester: &mockIngester{}, + funcType: message.FuncType("derp"), + expErrText: "invalid message function derp", + expIsActive: true, + }, + { + name: "func ingest ingest error", + ingester: &mockIngester{ + ingestErr: errors.New("ingest error"), + }, + funcType: message.FuncTypeIngest, + expErrText: "ingest error", + expIsActive: true, + }, + { + name: "func ingest commit error", + ingester: &mockIngester{ + commitErr: errors.New("commit error"), + canAutoCommit: true, + }, + funcType: message.FuncTypeIngest, + expErrText: "commit error", + expIsActive: true, + }, + { + name: "func commit commit error", + ingester: &mockIngester{ + commitErr: errors.New("commit error"), + canAutoCommit: true, + }, + funcType: message.FuncTypeCommit, + expErrText: "commit error", + expIsActive: true, + }, + { + name: "only ingest", + ingester: &mockIngester{}, + funcType: message.FuncTypeIngest, + msg: "still active feed", + providerAddress: "gno1234", + expIsActive: true, + }, + { + name: "ingest autocommit", + ingester: &mockIngester{canAutoCommit: true}, + funcType: message.FuncTypeIngest, + msg: "still active feed", + providerAddress: "gno1234", + expFeedValueString: "still active feed", + verifyIsLocked: true, + }, + { + name: "commit no value", + ingester: &mockIngester{}, + funcType: message.FuncTypeCommit, + msg: "shouldn't be stored", + verifyIsLocked: true, + }, + { + name: "ingest then commmit", + ingester: &mockIngester{}, + funcType: message.FuncTypeIngest, + msg: "blahblah", + doCommit: true, + expFeedValueString: "blahblah", + verifyIsLocked: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + staticFeed := static.NewFeed( + "1", + "string", + tt.ingester, + simple.NewStorage(1), + nil, + ) + + var errText string + if err := staticFeed.Ingest(tt.funcType, tt.msg, tt.providerAddress); err != nil { + errText = err.Error() + } + + if errText != tt.expErrText { + t.Fatalf("expected error text %s, got %s", tt.expErrText, errText) + } + + if tt.doCommit { + err := staticFeed.Ingest(message.FuncTypeCommit, "", "") + if err != nil { + t.Fatalf("follow up commit failed: %s", err.Error()) + } + } + + if tt.verifyIsLocked { + errText = "" + if err := staticFeed.Ingest(tt.funcType, tt.msg, tt.providerAddress); err != nil { + errText = err.Error() + } + + if errText != "feed locked" { + t.Fatalf("expected error text feed locked, got %s", errText) + } + } + + if tt.ingester.providerAddress != tt.providerAddress { + t.Errorf("expected provider address %s, got %s", tt.providerAddress, tt.ingester.providerAddress) + } + + feedValue, dataType, isLocked := staticFeed.Value() + if feedValue.String != tt.expFeedValueString { + t.Errorf("expected feed value string %s, got %s", tt.expFeedValueString, feedValue.String) + } + + if dataType != "string" { + t.Errorf("expected data type string, got %s", dataType) + } + + if isLocked != tt.verifyIsLocked { + t.Errorf("expected is locked %t, got %t", tt.verifyIsLocked, isLocked) + } + + if staticFeed.IsActive() != tt.expIsActive { + t.Errorf("expected is active %t, got %t", tt.expIsActive, staticFeed.IsActive()) + } + }) + } +} + +type mockTask struct { + err error + value string +} + +func (t mockTask) MarshalJSON() ([]byte, error) { + if t.err != nil { + return nil, t.err + } + + return []byte(`{"value":"` + t.value + `"}`), nil +} + +func TestFeed_Tasks(t *testing.T) { + id := "99" + valueDataType := "int" + + tests := []struct { + name string + tasks []feed.Task + expErrText string + expJSON string + }{ + { + name: "no tasks", + expJSON: `{"id":"99","type":"0","value_type":"int","tasks":[]}`, + }, + { + name: "marshal error", + tasks: []feed.Task{ + mockTask{err: errors.New("marshal error")}, + }, + expErrText: "marshal error", + }, + { + name: "one task", + tasks: []feed.Task{ + mockTask{value: "single"}, + }, + expJSON: `{"id":"99","type":"0","value_type":"int","tasks":[{"value":"single"}]}`, + }, + { + name: "two tasks", + tasks: []feed.Task{ + mockTask{value: "first"}, + mockTask{value: "second"}, + }, + expJSON: `{"id":"99","type":"0","value_type":"int","tasks":[{"value":"first"},{"value":"second"}]}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + staticFeed := static.NewSingleValueFeed( + id, + valueDataType, + tt.tasks..., + ) + + if len(staticFeed.Tasks()) != len(tt.tasks) { + t.Fatalf("expected %d tasks, got %d", len(tt.tasks), len(staticFeed.Tasks())) + } + + var errText string + json, err := staticFeed.MarshalJSON() + if err != nil { + errText = err.Error() + } + + if errText != tt.expErrText { + t.Fatalf("expected error text %s, got %s", tt.expErrText, errText) + } + + if string(json) != tt.expJSON { + t.Errorf("expected json %s, got %s", tt.expJSON, string(json)) + } + }) + } +} From d97f4ab01952ab78605a26414cd87d777756d4d1 Mon Sep 17 00:00:00 2001 From: deelawn Date: Mon, 6 May 2024 18:52:42 -0700 Subject: [PATCH 46/48] examples gno mod tidy --- examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod | 1 + examples/gno.land/p/demo/gnorkle/storage/gno.mod | 2 +- examples/gno.land/r/gnoland/ghverify/gno.mod | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod b/examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod index 69f43b8ef85..d5ab52411d9 100644 --- a/examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod +++ b/examples/gno.land/p/demo/gnorkle/ingesters/single/gno.mod @@ -3,4 +3,5 @@ module gno.land/p/demo/gnorkle/ingesters/single require ( gno.land/p/demo/gnorkle/gnorkle v0.0.0-latest gno.land/p/demo/gnorkle/ingester v0.0.0-latest + gno.land/p/demo/gnorkle/storage/simple v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/gnorkle/storage/gno.mod b/examples/gno.land/p/demo/gnorkle/storage/gno.mod index da5b278537d..d98962cb771 100644 --- a/examples/gno.land/p/demo/gnorkle/storage/gno.mod +++ b/examples/gno.land/p/demo/gnorkle/storage/gno.mod @@ -1 +1 @@ -module gno.land/p/demo/gnorkle/storage \ No newline at end of file +module gno.land/p/demo/gnorkle/storage diff --git a/examples/gno.land/r/gnoland/ghverify/gno.mod b/examples/gno.land/r/gnoland/ghverify/gno.mod index 25698975cd5..386bd9293d2 100644 --- a/examples/gno.land/r/gnoland/ghverify/gno.mod +++ b/examples/gno.land/r/gnoland/ghverify/gno.mod @@ -2,7 +2,6 @@ module gno.land/r/gnoland/ghverify require ( gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/gnorkle/feed v0.0.0-latest gno.land/p/demo/gnorkle/feeds/static v0.0.0-latest gno.land/p/demo/gnorkle/gnorkle v0.0.0-latest gno.land/p/demo/gnorkle/message v0.0.0-latest From dc851ce054b1dbf49df854095b90bb81ce8ee9d5 Mon Sep 17 00:00:00 2001 From: deelawn Date: Mon, 6 May 2024 19:25:49 -0700 Subject: [PATCH 47/48] updated type --- examples/gno.land/r/gnoland/ghverify/contract.gno | 6 +++--- examples/gno.land/r/gnoland/ghverify/contract_test.gno | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/gno.land/r/gnoland/ghverify/contract.gno b/examples/gno.land/r/gnoland/ghverify/contract.gno index 280d0068dfc..79482cba29d 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract.gno @@ -96,12 +96,12 @@ func GnorkleEntrypoint(message string) string { } // SetOwner transfers ownership of the contract to the given address. -func SetOwner(owner string) { - if ownerAddress != string(std.GetOrigCaller()) { +func SetOwner(owner std.Address) { + if ownerAddress != std.GetOrigCaller() { panic("only the owner can set a new owner") } - ownerAddress = owner + ownerAddress = string(owner) // In the context of this contract, the owner is the only one that can // add new feeds to the oracle. diff --git a/examples/gno.land/r/gnoland/ghverify/contract_test.gno b/examples/gno.land/r/gnoland/ghverify/contract_test.gno index ac3c1f32280..3f12bd0a7d6 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract_test.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract_test.gno @@ -70,7 +70,7 @@ func TestVerificationLifecycle(t *testing.T) { // Set the caller back to the whitelisted user and transfer contract ownership. std.TestSetOrigCaller(defaultAddress) - SetOwner(string(userAddress)) + SetOwner(userAddress) // Now trigger the feed ingestion from the user and new owner and only whitelisted address. std.TestSetOrigCaller(userAddress) From 189bcfad702e2059de024cb7954318e62cbdf4b3 Mon Sep 17 00:00:00 2001 From: deelawn Date: Mon, 6 May 2024 21:21:29 -0700 Subject: [PATCH 48/48] fix address type issues --- examples/gno.land/r/gnoland/ghverify/contract.gno | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/gno.land/r/gnoland/ghverify/contract.gno b/examples/gno.land/r/gnoland/ghverify/contract.gno index 79482cba29d..b40c9ef1448 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract.gno @@ -16,7 +16,7 @@ const ( ) var ( - ownerAddress = string(std.GetOrigCaller()) + ownerAddress = std.GetOrigCaller() oracle *gnorkle.Instance postHandler postGnorkleMessageHandler @@ -26,7 +26,7 @@ var ( func init() { oracle = gnorkle.NewInstance() - oracle.AddToWhitelist("", []string{ownerAddress}) + oracle.AddToWhitelist("", []string{string(ownerAddress)}) } type postGnorkleMessageHandler struct{} @@ -101,12 +101,12 @@ func SetOwner(owner std.Address) { panic("only the owner can set a new owner") } - ownerAddress = string(owner) + ownerAddress = owner // In the context of this contract, the owner is the only one that can // add new feeds to the oracle. oracle.ClearWhitelist("") - oracle.AddToWhitelist("", []string{ownerAddress}) + oracle.AddToWhitelist("", []string{string(ownerAddress)}) } // GetHandleByAddress returns the github handle associated with the given gno address.