-
Notifications
You must be signed in to change notification settings - Fork 386
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
WIP book.gno #1224
Draft
jaekwon
wants to merge
28
commits into
master
Choose a base branch
from
dev/jae/book
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
WIP book.gno #1224
Changes from 15 commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
e3d0e86
first commit of book.gno
jaekwon e519144
first draft of Book
jaekwon d71b4b9
Merge branch 'master' into dev/jae/book
gfanton 735dc17
...
jaekwon 7574a73
...
jaekwon 31ad17a
...
jaekwon 88d60b0
...
jaekwon c5cbdb6
...
jaekwon ddf595d
...
jaekwon 5005042
...
jaekwon 8288c03
...
jaekwon 579268e
...
jaekwon 7bf41fa
...
jaekwon d5d55f5
...
jaekwon 44951f1
...
jaekwon 7ae5b1d
...
jaekwon 4d43658
refactor; and use std.Address!
jaekwon d1738fa
Directory uses std.Address
jaekwon 12e0f79
...
jaekwon 425df3c
...
jaekwon 66200d6
...
jaekwon 2b06cd2
...
jaekwon ca9cc13
...
jaekwon f640248
...
jaekwon 8128c2a
added node.gno
jaekwon d9e235c
add book node
jaekwon 62b9196
add references
jaekwon 9eaea3a
...
jaekwon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,379 @@ | ||
package book | ||
|
||
/* | ||
|
||
GOALS: | ||
|
||
1. Create a Book (aka List) of text, | ||
that is owned by a Book of Persons. | ||
|
||
2. Allow a Book of Persons to split/fork. | ||
|
||
*/ | ||
|
||
// XXX open questions | ||
// * Do we want a directory? | ||
// * Should action memoize object/subject/args? | ||
// * Should args be interface{}? | ||
// - NOTE: Amino *can* encode anything registered. | ||
// - XXX: could one pass in a pointer ref as arg? | ||
// - not without a directory, conflicts with GC system. | ||
// - with directory, any ObjectID() can be looked up. | ||
// - PRO: less duplicate string conversion work | ||
// - PRO: more flexible | ||
// - CON: args are mutable, potentially confusing. | ||
// - object/subject *should* be mutable. | ||
// - CON: malleability becomes user problem. | ||
// - so how do you sign an object? SignBytes()? | ||
// - CON: requires some reflection call system. | ||
// | ||
|
||
import "gno.land/p/demo/avl" | ||
|
||
//---------------------------------------- | ||
// Usage example | ||
|
||
func main() { | ||
// directory of subjects/objects. | ||
directory := NewDirectory("test") | ||
// action to perform. | ||
action := Action{} | ||
|
||
//---------------------------------------- | ||
// CASES | ||
// Here are some usage examples. | ||
// For simplicity all methods panic on error. | ||
|
||
// CASE 1A: dummy "priv" account. | ||
// Access to privAccount is like access to priv keys. | ||
privAccount.AppendAction(action) | ||
directory.GetObject(action.object).Receive(action) | ||
|
||
// CASE 1B: ActionRunner with no signing | ||
// This is just a wrapper around CASE 1A. | ||
ar := ActionRunner(directory) | ||
sar.RunWith(action, privAccount) | ||
// sar.RunWith() impl: | ||
// privAccount.AppendAction(action) | ||
// ar.dir.GetObject(action.object).Receive(action) | ||
|
||
// CASE 2A: public crypto key account. | ||
// NOTE: This is NOT secure because | ||
// anyone can call account.Authenticate(), | ||
// but not anyone would complete the tx. | ||
privKey := _ // imagine we have the key | ||
signed := SignAction(action, privKey) | ||
account.Authenticate(signed) | ||
directory.GetObject(action.object).Receive(action) | ||
|
||
// CASE 2B: SignedActionRunner | ||
// SignedActionRunner embeds an ActionRunner. | ||
sar := SignedActionRunner() | ||
sar.Run(signed) | ||
// sar.Run() impl: | ||
// signed.subject.Authenticate(signed) | ||
// sar.dir.GetObject(action.object).Receive(action) | ||
} | ||
|
||
//---------------------------------------- | ||
// Object | ||
|
||
type ObjectKey string | ||
|
||
func (key ObjectKey) Validate() { | ||
if len(key) == "" { | ||
panic("invalid key") | ||
} | ||
// XXX make sure key is alphanumeric. | ||
} | ||
|
||
type Object interface { | ||
// The primary key of the object. | ||
GetKey() ObjectKey | ||
// Set the prmiary key. | ||
// Panics if key exists. | ||
SetKey(ObjectKey) | ||
// Receive performs action on object. | ||
// Panics if action is not permitted. | ||
Receive(Action) | ||
} | ||
|
||
//---------------------------------------- | ||
// Directory | ||
|
||
type Directory struct { | ||
name string // name of directory | ||
objects avl.Tree // key -> Object | ||
auto bool // use automatic numeric keys | ||
lastKey int64 // last auto numeric key used | ||
prefix string // key prefix, if any | ||
} | ||
|
||
func NewDirectory(name string, auto bool, prefix string) *Directory { | ||
return &Directory{ | ||
name: name, | ||
objects: nil, | ||
auto: auto, | ||
lastKey: 0, | ||
prefix: prefix, | ||
} | ||
} | ||
|
||
func (dir *Directory) GetName() string { | ||
return dir.name | ||
} | ||
|
||
func (dir *Directory) GetObject(key ObjectKey) Object { | ||
obj, exists := dir.objects.Get(key) | ||
if !exists { | ||
panic("object not found: invalid key") | ||
} | ||
return obj | ||
} | ||
|
||
func (dir *Directory) AddObject(obj Object) { | ||
key := ObjectKey("") | ||
if dir.auto { | ||
key = dir.nextKey() | ||
obj.SetKey(key) | ||
} else { | ||
key = obj.GetKey() | ||
key.ValidateBasic() | ||
if !hasPrefix(string(key), dir.prefix) { | ||
panic("invalid key: prefix mismatch") | ||
} | ||
} | ||
updated := dir.objects.Set(string(key), obj) | ||
if updated == true { | ||
panic("duplicate object key") | ||
} | ||
} | ||
|
||
func (dir *Directory) RemoveObject(obj Object) { | ||
key := obj.GetKey() | ||
old, removed := dir.objects.Remove(string(key)) | ||
if !removed { | ||
panic("cannot remove object: not found") | ||
} | ||
if old != obj { | ||
panic("cannot remove conflicting object") | ||
} | ||
} | ||
|
||
func (dir *Directory) RemoveObjectByKey(key ObjectKey) { | ||
if !hasPrefix(key, dir.prefix) { | ||
panic("invalid key: prefix mismatch") | ||
} | ||
_, removed := dir.objects.Remove(string(key)) | ||
if !removed { | ||
panic("cannot remove object: not found") | ||
} | ||
} | ||
|
||
func (dir *Directory) nextKey() ObjectKey { | ||
if !dir.auto { | ||
panic("cannot generate key: not auto keyed") | ||
} | ||
nextKey := dir.lastKey + 1 | ||
if nextKey < 0 { | ||
panic("key overflow") | ||
} | ||
dir.lastKey = nextKey | ||
return ObjectKey(dir.prefix + itoa(nextKey)) | ||
} | ||
|
||
//---------------------------------------- | ||
// Book | ||
|
||
type Book struct { | ||
attrs Attributes | ||
entries avl.Tree | ||
} | ||
|
||
func (bk *Book) Append(XXX) XXX { | ||
XXX | ||
} | ||
|
||
func (bk *Book) Size() int { | ||
XXX | ||
} | ||
|
||
func (bk *Book) Get(n int) XXX { | ||
XXX | ||
} | ||
|
||
//---------------------------------------- | ||
// Attributes | ||
|
||
type Attributes struct { | ||
meta avl.Tree // catchall | ||
author Author | ||
// TODO | ||
} | ||
|
||
//---------------------------------------- | ||
// Action | ||
|
||
// A subject acts upon an object with a verb and arguments. | ||
// As if subject is calling object.verb(args...). | ||
// | ||
// The sequence is usually an incrementing number, | ||
// but could be something else tracked by an action book. | ||
// | ||
// The subject and object are denoted by their respective | ||
// paths (or URIs?). | ||
type Action struct { | ||
sequence string // typically an incrementing number | ||
subject string // subject path | ||
object string // object path | ||
verb string // verb name | ||
args []string // legible args | ||
} | ||
|
||
func (a Action) Sequence() string { return a.sequence } | ||
func (a Action) Subject() string { return a.subject } | ||
func (a Action) Object() string { return a.object } | ||
func (a Action) Verb() string { return a.verb } | ||
func (a Action) NumArgs() int { return len(a.args) } | ||
func (a action) Arg(n int) string { return a.args[n] } | ||
|
||
// Authorization struct is only needed for Actions that | ||
// require cryptographic authorization, where the Action's | ||
// subject has a pubkey to verify signatures with. | ||
// | ||
// Presumably once Authorization is validated (signature | ||
// checked) the Action becomes committed, and given a index | ||
// number. | ||
type Authorization struct { | ||
action Action | ||
signatures []Signature | ||
} | ||
|
||
type Signature struct { | ||
account number // or address with some extra data unknown | ||
sequence number // or alternative to sequence | ||
signature []byte | ||
} | ||
|
||
//---------------------------------------- | ||
// PrivActionBook | ||
|
||
// This is a thought experiment to make books work for auth. | ||
// Actions are considered authorized if appended in PrivActionBook. | ||
// | ||
// An PrivActionBook is meant to be owned privately by the object. | ||
// This is similar to PrivKey in crypto; it is privileged. | ||
// | ||
// Actions need not necessarily be signed cryptographically | ||
// to be authenticated in an PrivActionBook, because the test of | ||
// authorization is merely inclusion. | ||
// | ||
// XXX consider: | ||
// type CryptoActionBook struct { PubKey, PrivActionBook } | ||
// A CryptoActionBook need not be privileged, | ||
// perhaps anyone can append a signed action, | ||
// XXX but how to guarantee execution? | ||
// XXX without something like `Everything.Execute(Action)`?? | ||
// | ||
// XXX consider: | ||
// type ReadActionBook, a readonly ActionBook?? | ||
type PrivActionBook struct { | ||
|
||
// All actions must have this object. | ||
object string | ||
|
||
// Maybe PrivActionBook *is* Book? | ||
// not sure yet. | ||
book *Book | ||
|
||
// Number of actions to keep around. | ||
capacity int | ||
|
||
// Validates sequences based on sequenceAccum, | ||
// which is accumulated from sequences seen. | ||
sequenceStrategy SequenceStrategy | ||
|
||
// Typically the last sequence value. | ||
// The type of value depends on SequenceStrategy. | ||
// This field allows the PrivActionBook to prune | ||
// all previous Actions while preserving sequencing. | ||
// XXX string or TextMarshaller() or Stringer() or? | ||
sequenceAccum string | ||
} | ||
|
||
func NewPrivActionBook() *PrivActionBook { | ||
// XXX | ||
} | ||
|
||
// If the action is valid, append to PrivActionBook, | ||
// thereby making it officially authorized. | ||
// The execution of action generally should happen | ||
// atomically with authorization by caller. | ||
// | ||
// if err := pab.Append(action); err != nil { | ||
// execute(action) | ||
// } | ||
func (pab *PrivActionBook) Append(action Action) error { | ||
// XXX copy action. | ||
|
||
// XXX check action.sequence against ab.last | ||
// XXX if good, append and return nil | ||
// XXX otherwise return error | ||
|
||
// XXX match action.object with pab.object. | ||
// XXX set action.object = nil for space. | ||
|
||
// XXX check capacity | ||
} | ||
|
||
func (pab *PrivActionBook) Len() int { | ||
return pab.book.Len() | ||
} | ||
|
||
func (pab *PrivActionBook) Cap() int { | ||
return pab.book.Cap() | ||
} | ||
|
||
// XXX Not sure why this would be useful, | ||
// XXX except to show clients previous actions, | ||
// XXX but either way developers should not rely on it | ||
// XXX for transactional logic. | ||
func (pab *PrivActionBook) Get(idx int) Action { | ||
// XXX fetch action from pab.book.Get() | ||
// XXX copy action | ||
// XXX set copy.object = pab.object | ||
// XXX return copy | ||
} | ||
|
||
// XXX SequenceStragegy.Name()? | ||
// XXX or just make enums? | ||
// XXX Either way need to make globally unique lookup. | ||
func (pab *PrivActionBook) SequenceStrategy() SequenceStrategy { | ||
// XXX | ||
} | ||
|
||
// XXX clients will need this to sign, | ||
// XXX especially after device reset. | ||
func (pab *PrivActionBook) SequenceAccum() string { | ||
// XXX | ||
} | ||
|
||
//---------------------------------------- | ||
// Account | ||
|
||
// XXX incomplete. | ||
type Account struct { | ||
// each account has an associated pab | ||
// XXX or, accounts have devices, each with a pab. | ||
pab *PrivActionBook | ||
} | ||
|
||
//---------------------------------------- | ||
// misc | ||
|
||
func hasPrefix(str, prefix string) bool { | ||
if len(str) <= len(prefix) { | ||
return false | ||
} | ||
return str[:len(prefix)] == prefix | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we need an equivalent of
std.Address
specifically for objects. The directory will function as the object catalog, similar to how the tm2 account module handles key-based accounts.