Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Oracle packages and Github verification realm #1568

Merged
merged 53 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
0a688e6
agent wip
deelawn Dec 17, 2023
9523aa4
reworked task agent
deelawn Jan 9, 2024
2f5d997
reorg and more features
deelawn Jan 9, 2024
b6f99b0
added github contract that uses the oracle
deelawn Jan 9, 2024
43c1130
moved files to examples for creation at genesis
deelawn Jan 10, 2024
a95f0ec
docs
deelawn Jan 10, 2024
c9f6b51
oracle package bug fixes
deelawn Jan 11, 2024
6f0c7ce
prototype new version of the oracle package
deelawn Jan 17, 2024
393d418
more work on v2 orkle prototype
deelawn Jan 18, 2024
ed864e3
implemented gh verify oracle using new model
deelawn Jan 19, 2024
c5dcc10
exclude inactive feeds from agent requests
deelawn Jan 19, 2024
03958de
tried to implement whitelisting effectively
deelawn Jan 23, 2024
fc3d750
moved the gh verification task to the realm
deelawn Jan 23, 2024
d00ffaa
delete code and move new version to the gno p and r folders
deelawn Jan 23, 2024
cd63af0
tidy go mod
deelawn Jan 23, 2024
c91084d
rename orkle -> gnorkle
deelawn Jan 23, 2024
cab8a03
bug fixes and realm ownership
deelawn Jan 23, 2024
1ac9af8
try to fix weird vcs sync issue
deelawn Jan 23, 2024
f73a94b
gno mod tidy
deelawn Jan 23, 2024
10e52de
added godocs, comments, and minor changes
deelawn Jan 24, 2024
88b231e
added ghverify realm test
deelawn Jan 24, 2024
797c7a1
gno mod tidy
deelawn Jan 24, 2024
5ea02ff
added ghverify accessors
deelawn Jan 24, 2024
3a43959
fix type error
deelawn Jan 24, 2024
02920e5
docstrings and cleanup
deelawn Jan 24, 2024
d3d9376
prevent overwriting existing verification requests
deelawn Jan 24, 2024
2c11cb3
added readmes
deelawn Jan 25, 2024
f2c0d0d
removed v1 suffix
deelawn Feb 22, 2024
31f955a
txtar test and contract bug fix
deelawn Feb 22, 2024
433228e
Merge branch 'master' into feat/agents
deelawn Feb 23, 2024
719bde4
txtar loadpkg
deelawn Feb 23, 2024
667fc06
Update examples/gno.land/r/gnoland/ghverify/readme.md
deelawn Mar 4, 2024
bead186
MarshalToJSON -> MarshalJSON
deelawn Mar 4, 2024
2e19ebd
Merge branch 'feat/agents' of github.com:deelawn/gno into feat/agents
deelawn Mar 4, 2024
89d8e33
capitalized readme
deelawn Mar 4, 2024
a4b301e
filename typo
deelawn Mar 4, 2024
5d45122
added an example for each proposed feed type
deelawn Mar 6, 2024
139984f
added more examples
deelawn Mar 6, 2024
d10aa8b
set first to false after first iternation
deelawn Mar 6, 2024
0ddd13d
make feed task arguments variadic
deelawn Mar 7, 2024
0934899
return errors from package code and panic in the realm
deelawn Mar 7, 2024
3a500aa
fixed contract tests
deelawn Mar 7, 2024
6b9c9de
use proper return types
deelawn Mar 7, 2024
fed4a3f
remove unused import
deelawn Apr 28, 2024
5f62b9e
Merge branch 'master' into feat/agents
deelawn Apr 30, 2024
850393c
add error return types to methods of gnorkle interfaces and consolida…
deelawn May 3, 2024
3960abc
added some tests
deelawn May 3, 2024
6fa61f7
added feed tests
deelawn May 7, 2024
dfba393
Merge branch 'master' into feat/agents
deelawn May 7, 2024
d97f4ab
examples gno mod tidy
deelawn May 7, 2024
6696f7a
Merge branch 'feat/agents' of github.com:deelawn/gno into feat/agents
deelawn May 7, 2024
dc851ce
updated type
deelawn May 7, 2024
189bcfa
fix address type issues
deelawn May 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions examples/gno.land/p/demo/gnorkle/README.md
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 3 additions & 0 deletions examples/gno.land/p/demo/gnorkle/agent/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module gno.land/p/demo/gnorkle/agent

require gno.land/p/demo/avl v0.0.0-latest
50 changes: 50 additions & 0 deletions examples/gno.land/p/demo/gnorkle/agent/whitelist.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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()
}

for _, address := range addresses {
m.store.Set(address, struct{}{})
}
}

// RemoveAddress removes the given address from the whitelist if it exists.
func (m *Whitelist) RemoveAddress(address string) {
if m.store == nil {
return
}

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
}

return m.store.Has(address)
}
1 change: 1 addition & 0 deletions examples/gno.land/p/demo/gnorkle/feed/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/p/demo/gnorkle/feed
7 changes: 7 additions & 0 deletions examples/gno.land/p/demo/gnorkle/feed/task.gno
Original file line number Diff line number Diff line change
@@ -0,0 +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 {
MarshalJSON() ([]byte, error)
}
16 changes: 16 additions & 0 deletions examples/gno.land/p/demo/gnorkle/feed/type.gno
Original file line number Diff line number Diff line change
@@ -0,0 +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
)
9 changes: 9 additions & 0 deletions examples/gno.land/p/demo/gnorkle/feed/value.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
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
}
143 changes: 143 additions & 0 deletions examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package static

import (
"bufio"
"bytes"

"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"
)

// Feed is a static feed.
type Feed struct {
id string
isLocked bool
valueDataType string
ingester gnorkle.Ingester
storage gnorkle.Storage
tasks []feed.Task
}

// NewFeed creates a new static feed.
func NewFeed(
id string,
valueDataType string,
ingester gnorkle.Ingester,
storage gnorkle.Storage,
tasks []feed.Task,
) *Feed {
return &Feed{
id: id,
valueDataType: valueDataType,
ingester: ingester,
storage: storage,
tasks: tasks,
}
}

// NewSingleValueFeed is a convenience function for creating a static feed
// that autocommits a value after a single ingestion.
func NewSingleValueFeed(
id string,
valueDataType string,
tasks []feed.Task,
deelawn marked this conversation as resolved.
Show resolved Hide resolved
) *Feed {
return NewFeed(
id,
valueDataType,
&single.ValueIngester{},
simple.NewStorage(1),
tasks,
)
}

// 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")
}

switch funcType {
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)
f.isLocked = true
}

case message.FuncTypeCommit:
f.ingester.CommitValue(f.storage, providerAddress)
f.isLocked = true

default:
panic("invalid message function " + string(funcType))
}
}

// 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
deelawn marked this conversation as resolved.
Show resolved Hide resolved
}

// 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)

w.Write([]byte(
`{"id":"` + f.id +
`","type":"` + ufmt.Sprintf("%d", int(f.Type())) +
`","value_type":"` + f.valueDataType +
`","tasks":[`),
)

first := true
for _, task := range f.tasks {
if !first {
w.WriteString(",")
}
deelawn marked this conversation as resolved.
Show resolved Hide resolved

taskJSON, err := task.MarshalJSON()
if err != nil {
return nil, err
}

w.Write(taskJSON)
}

w.Write([]byte("]}"))
w.Flush()

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
}
10 changes: 10 additions & 0 deletions examples/gno.land/p/demo/gnorkle/feeds/static/gno.mod
Original file line number Diff line number Diff line change
@@ -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
)
24 changes: 24 additions & 0 deletions examples/gno.land/p/demo/gnorkle/gnorkle/feed.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package gnorkle

import (
"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
// agents and provide data feeds to consumers.
type Feed interface {
ID() string
Type() feed.Type
Value() (value feed.Value, dataType string, consumable bool)
Ingest(funcType message.FuncType, rawMessage, providerAddress string)
MarshalJSON() ([]byte, error)
Tasks() []feed.Task
IsActive() bool
}

// FeedWithWhitelist associates a `Whitelist` with a `Feed`.
type FeedWithWhitelist struct {
Feed
Whitelist
}
9 changes: 9 additions & 0 deletions examples/gno.land/p/demo/gnorkle/gnorkle/gno.mod
Original file line number Diff line number Diff line change
@@ -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
)
11 changes: 11 additions & 0 deletions examples/gno.land/p/demo/gnorkle/gnorkle/ingester.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package gnorkle

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.
type Ingester interface {
Type() ingester.Type
Ingest(value, providerAddress string) (canAutoCommit bool)
CommitValue(storage Storage, providerAddress string)
}
Loading
Loading