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

WIP book.gno #1224

Draft
wants to merge 28 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions examples/gno.land/p/jaekwon/book/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Design choices for Actions
* Action must be in string serializable form for human review.
* Object references in args should be disallowed for simplicity;
an Action, like an HTTP Request, is discrete structure.
* Is "unmarshalling" opinionated? No, let people choose encoding.

Secure Gno: (move elsewhere)
1. An unexposed (lowercase) declaration can be used by anyone who holds it.
1. Unexposed fields of any struct can still be copied by assignment.
1. You can also copy an unexposed struct's unexposed field and get a
reference. `x := external.MakePrivateStructPtr(); y := *x; z := &y`
1. You could *maybe* prevent the above by only returning interface
values, and generally preventing the holding of an unexposed declaration,
but this also depends on whether reflection supports instantiation, and the
user would still need to check that the type is what they expect it to be.
1. In other words, don't expect to prevent creation of new references for
security.
1. You can tell whether a reference was copied or not by checking the value of
a private field that was originally set to reference.
`x := &unexposedStruct{ptr:nil}; x.ptr = x`
186 changes: 186 additions & 0 deletions examples/gno.land/p/jaekwon/book/action.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package book

import (
"errors"
"std"
)

//----------------------------------------
// Action

// A actor (subject) acts upon an object with a verb and
// arguments. As if actor is calling object.verb(args...).
//
// The sequence is usually an incrementing number,
// but could be something else tracked by an action book.
//
// The actor and object are denoted by their respective
// paths (or URIs?).
type Action struct {
sequence string // typically an incrementing number
actor std.Address // actor (subject) id
object std.Address // object id
verb string // verb name
args []string // legible args
status ActionStatus // new, pending, etc
}

func (a *Action) Sequence() string { return a.sequence }
func (a *Action) Actor() std.Address { return a.actor }
func (a *Action) Object() std.Address { 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] }
func (a *Action) Status() ActionStatus { return a.status }

func (a *Action) setPending() {
if a.status != ActionStatusNew {
panic("should not happen")
}
a.status = ActionStatusPending
}

func (a *Action) SetReceived() {
if a.status != ActionStatusPending {
panic("should not happen")
}
a.status = ActionStatusReceived
}

func (a *Action) setComplete() {
if a.status != ActionStatusPending &&
s.status != ActionStatusReceived {
panic("should not happen")
}
a.status = ActionStatusComplete
}

func (a *Action) setError() {
a.status = ActionStatusError
}

type ActionStatus int

const (
ActionStatusNew = iota
ActionStatusPending
ActionStatusReceived
ActionStatusComplete
ActionStatusError
)

//----------------------------------------
// Actor

type Actor interface {
std.Addressable
// CheckAction verifies that action is
// valid as the next action for Actor.
// It should have no side effect.
CheckAction(action) error
}

//----------------------------------------
// Object

type Object interface {
std.Addressable
// Receive performs action on self.
// Returns an error if action is not permitted.
ReceiveAction(*Action, Actor) error
}

//----------------------------------------
// Main entry methods

// Perform the action on the actor/object in directory.
// How an action is authenticated is determined by the
// implementation of the actor; one could use cryptographic
// private key to authenticate an action, or, one could use
// a object (capabilities) private key, or any other method.
//
// In the implementations provided here, the action is
// first either added to a CryptoActionBook or a
// PrivateActionBook, each backed by an ActionBook (but an
// actor is not required to have an ActionBook).
//
// Case 1 w/ signatures
// 1. caller adds Action to CryptoActionBook.
// 2. CryptoActionBook checks signature & sequence.
// 3. CryptoActionBook calls Perform().
// 4. Perform asks Actor to verify Action.
// 5. Actor's ActionBook says it is good.
// 6. Perform makes object receive action.
//
// Case 2 w/o signatures
// 1. caller adds Action to PrivActionBook.
// 2. PrivActionBook checks sequence.
// 3. PrivActionBook calls Perform().
// 4. Perform asks Actor to verify Action.
// 5. Actor's ActionBook says it is good.
// 6. Perform makes object receive action.
func Perform(dir Directory, action Action) (err error) {
// Defer to set panic upon panic.
defer func() {
if r := recover(); r != nil {
action.setError()
panic(r)
} else if err != nil {
action.setError()
}
}()

// Validate what can be validated.
err = action.ValidateBasic()
if err != nil {
return
}
// Check that status is new.
if action.Status() != ActionStatusNew {
return errors.New("expected a new action")
}
// Get actor and object.
actor := dir.Get(action.actor)
object := dir.Get(action.object)
// Ask Actor to verify action.
err = actor.CheckAction(action)
if err != nil {
return
}
// Set status as pending.
// (action.status == new)
action.setPending()
// (action.status == pending)
// Let object handle action.
err = object.ReceiveAction(action, actor)
if err != nil {
return
}
// (action.status == pending|received)
// Set status as complete.
action.setComplete()
// (action.status == complete)

return nil
}

//----------------------------------------
// Authorization

// Authorization struct is only needed for Actions that
// require cryptographic authorization, where the Action's
// actor 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
}
174 changes: 174 additions & 0 deletions examples/gno.land/p/jaekwon/book/book.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package book

/*

GOALS:

0. Define Action, Actor, Object.

1.a Create a Book (aka List) of text,
that is owned by a Book of Actors.

1.b Create ActionBook.

2. Allow a Book of Actors to split/fork.

3. Later, a Person is interface { Actor ...}.

*/

// TODO use this for Tiers.

import "gno.land/p/demo/avl"

//----------------------------------------
// Usage example

func main() {
// directory of subjects/objects.
directory := NewDirectory("test")
// action to perform.
action := Action{}
// XXX
}

//----------------------------------------
// Book

// A Book is a basic data structure.
// It is like a linked list, but indexable from 0.
// XXX XXX how to do this w/ avl.Tree?
// TODO book merges?
// TODO flesh out book.
type Book struct {
attrs Attributes
entries avl.Tree
}

var _ Object = &Book{}

func (bk *Book) Append(XXX) XXX {
XXX
}

func (bk *Book) Size() int {
XXX
}

func (bk *Book) Get(n int) XXX {
XXX
}

//----------------------------------------
// 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.
//
// TODO implement:
// type CryptoActionBook struct { PubKey, PrivActionBook }
// A CryptoActionBook need not be privileged,
// perhaps anyone can append a signed action,
//
// Also, PrivActionBook.
// Also, ReadActionBook.
// All of these are backed by the same underlying "book".
type PrivActionBook struct {

// All actions must have this object.
object std.FullAddress

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

//----------------------------------------
// misc

func hasPrefix(str, prefix string) bool {
if len(str) <= len(prefix) {
return false
}
return str[:len(prefix)] == prefix
}
Loading
Loading