From e3d0e86e3a48b57f2def2d357f0c9a070d514f4f Mon Sep 17 00:00:00 2001 From: jaekwon Date: Tue, 10 Oct 2023 17:17:58 -0700 Subject: [PATCH 01/27] first commit of book.gno --- examples/gno.land/p/jaekwon/book/book.gno | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 examples/gno.land/p/jaekwon/book/book.gno diff --git a/examples/gno.land/p/jaekwon/book/book.gno b/examples/gno.land/p/jaekwon/book/book.gno new file mode 100644 index 00000000000..8ff463a0c3d --- /dev/null +++ b/examples/gno.land/p/jaekwon/book/book.gno @@ -0,0 +1,3 @@ +type Book struct { + // XXX +} From e519144b39a954df1e21263c5151546a181f2158 Mon Sep 17 00:00:00 2001 From: jaekwon Date: Tue, 10 Oct 2023 17:26:44 -0700 Subject: [PATCH 02/27] first draft of Book --- examples/gno.land/p/jaekwon/book/book.gno | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/examples/gno.land/p/jaekwon/book/book.gno b/examples/gno.land/p/jaekwon/book/book.gno index 8ff463a0c3d..54a479c2948 100644 --- a/examples/gno.land/p/jaekwon/book/book.gno +++ b/examples/gno.land/p/jaekwon/book/book.gno @@ -1,3 +1,23 @@ +/* + +This is a concept for a book (basically a list). +A Book has a title, owner, etc. +A Book can be forked. +Books can be components of a virtual Person (where a Person is an interface). + +*/ + +import "gno.land/p/demo/avl" + +type Attributes map[string]string + type Book struct { - // XXX + Attributes + entries avl.Tree +} + +type Attributes struct { + meta avl.Tree // catchall + author Author + // TODO } From 735dc17b2d3a57d3c7e58f69bb39630f7283caec Mon Sep 17 00:00:00 2001 From: jaekwon Date: Tue, 17 Oct 2023 00:31:18 -0700 Subject: [PATCH 03/27] ... --- examples/gno.land/p/jaekwon/book/book.gno | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/examples/gno.land/p/jaekwon/book/book.gno b/examples/gno.land/p/jaekwon/book/book.gno index 54a479c2948..f6fc47f50fb 100644 --- a/examples/gno.land/p/jaekwon/book/book.gno +++ b/examples/gno.land/p/jaekwon/book/book.gno @@ -21,3 +21,30 @@ type Attributes struct { author Author // TODO } + +//---------------------------------------- + +// object has an action verb (method), which takes arguments +type Action struct { + subject // is this an id? + verb // i guess a string or Name type? + object // is this an id? + arguments // are these primitive strings or any +} + +// 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 +} From 7574a73d44f91a27755767fbb355a9eac2827f8d Mon Sep 17 00:00:00 2001 From: jaekwon Date: Tue, 17 Oct 2023 01:08:09 -0700 Subject: [PATCH 04/27] ... --- examples/gno.land/p/jaekwon/book/book.gno | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/gno.land/p/jaekwon/book/book.gno b/examples/gno.land/p/jaekwon/book/book.gno index f6fc47f50fb..e93ffce4b83 100644 --- a/examples/gno.land/p/jaekwon/book/book.gno +++ b/examples/gno.land/p/jaekwon/book/book.gno @@ -1,3 +1,5 @@ +package book + /* This is a concept for a book (basically a list). @@ -24,12 +26,15 @@ type Attributes struct { //---------------------------------------- -// object has an action verb (method), which takes arguments +// 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. type Action struct { - subject // is this an id? - verb // i guess a string or Name type? - object // is this an id? - arguments // are these primitive strings or any + sequence string // must match previous + subject string // subject path + object string // object path + verb string // verb name + args []string // legible args } // Authorization struct is only needed for Actions that require From 31ad17a0d048627dc98f2b81618cdb4a759fb304 Mon Sep 17 00:00:00 2001 From: jaekwon Date: Tue, 17 Oct 2023 04:43:07 -0700 Subject: [PATCH 05/27] ... --- examples/gno.land/p/jaekwon/book/book.gno | 45 ++++++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/examples/gno.land/p/jaekwon/book/book.gno b/examples/gno.land/p/jaekwon/book/book.gno index e93ffce4b83..8764eb96e81 100644 --- a/examples/gno.land/p/jaekwon/book/book.gno +++ b/examples/gno.land/p/jaekwon/book/book.gno @@ -11,13 +11,26 @@ Books can be components of a virtual Person (where a Person is an interface). import "gno.land/p/demo/avl" -type Attributes map[string]string - type Book struct { - Attributes + 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 @@ -25,18 +38,40 @@ type Attributes struct { } //---------------------------------------- +// ActionBook +// XXX thought experiment + +// XXX this doesn't make sense. +func (ab *ActionBook) Authorize(action Action) error { + // XXX check action.sequence against ab.last +} + +//---------------------------------------- +// 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. +// +// 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 // must match previous + 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. From 88d60b0bbaf823c7adfe7812f938246ebfa7db4e Mon Sep 17 00:00:00 2001 From: jaekwon Date: Tue, 17 Oct 2023 04:54:16 -0700 Subject: [PATCH 06/27] ... --- examples/gno.land/p/jaekwon/book/book.gno | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/examples/gno.land/p/jaekwon/book/book.gno b/examples/gno.land/p/jaekwon/book/book.gno index 8764eb96e81..411f6841234 100644 --- a/examples/gno.land/p/jaekwon/book/book.gno +++ b/examples/gno.land/p/jaekwon/book/book.gno @@ -39,11 +39,30 @@ type Attributes struct { //---------------------------------------- // ActionBook -// XXX thought experiment -// XXX this doesn't make sense. +// This is a thought experiment to make books work for auth. +// Actions are considered authorized if appended in ActionBook. +// +// Actions need not necessarily be signed cryptographically +// to be authenticated in an ActionBook, because the test of +// authorization *IS* inclusion by language capabilities. +// +// XXX what if sequece requires > keep records? +// XXX implement type PubKeyActionBook struct { PubKey, ActionBook } +// XXX or refactor these ideas somehow. +type ActionBook struct { + book *Book + keep int // last n actions to keep around +} + +// If the action is valid, append to ActionBook, +// thereby making it officially authorized. +// The execution should happen atomically with authorization. +// e.g. if err := ab.Authorize(); err != nil { execute(action) } func (ab *ActionBook) Authorize(action Action) error { // XXX check action.sequence against ab.last + // XXX if good, append and return nil + // XXX otherwise return error } //---------------------------------------- From c5cbdb64223d38d233cc06f8b436a723d1d951a3 Mon Sep 17 00:00:00 2001 From: jaekwon Date: Tue, 17 Oct 2023 16:11:58 -0700 Subject: [PATCH 07/27] ... --- examples/gno.land/p/jaekwon/book/book.gno | 58 ++++++++++++++++++----- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/examples/gno.land/p/jaekwon/book/book.gno b/examples/gno.land/p/jaekwon/book/book.gno index 411f6841234..dd60b9648b5 100644 --- a/examples/gno.land/p/jaekwon/book/book.gno +++ b/examples/gno.land/p/jaekwon/book/book.gno @@ -38,28 +38,60 @@ type Attributes struct { } //---------------------------------------- -// ActionBook +// PrivActionBook // This is a thought experiment to make books work for auth. -// Actions are considered authorized if appended in ActionBook. +// Actions are considered authorized if appended in PrivActionBook. +// +// An PrivActionBook is meant to be owned privately by an account. +// This is similar to PrivKey in crypto; privileged data. // // Actions need not necessarily be signed cryptographically -// to be authenticated in an ActionBook, because the test of -// authorization *IS* inclusion by language capabilities. +// 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 what if sequece requires > keep records? -// XXX implement type PubKeyActionBook struct { PubKey, ActionBook } -// XXX or refactor these ideas somehow. -type ActionBook struct { +// XXX consider: +// type ReadActionBook, a readonly ActionBook?? +type PrivActionBook struct { + + // Maybe PrivActionBook *is* Book? + // not sure yet. book *Book - keep int // last n actions to keep around + + // 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. + sequenceAccum interface{} +} + +func NewPrivActionBook() *PrivActionBook { + // XXX } -// If the action is valid, append to ActionBook, +// If the action is valid, append to PrivActionBook, // thereby making it officially authorized. -// The execution should happen atomically with authorization. -// e.g. if err := ab.Authorize(); err != nil { execute(action) } -func (ab *ActionBook) Authorize(action Action) error { +// The execution of action generally should happen +// atomically with authorization by caller. +// +// if err := ab.Authorize(); err != nil { +// execute(action) +// } +func (pab *PrivActionBook) Append(action Action) error { // XXX check action.sequence against ab.last // XXX if good, append and return nil // XXX otherwise return error From ddf595d4606edf9307d17b80ef419ac65dd8b8e0 Mon Sep 17 00:00:00 2001 From: jaekwon Date: Tue, 17 Oct 2023 17:19:49 -0700 Subject: [PATCH 08/27] ... --- examples/gno.land/p/jaekwon/book/book.gno | 41 ++++++++++++++++++++--- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/examples/gno.land/p/jaekwon/book/book.gno b/examples/gno.land/p/jaekwon/book/book.gno index dd60b9648b5..0d0581f0d83 100644 --- a/examples/gno.land/p/jaekwon/book/book.gno +++ b/examples/gno.land/p/jaekwon/book/book.gno @@ -43,8 +43,8 @@ type Attributes struct { // 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 an account. -// This is similar to PrivKey in crypto; privileged data. +// 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 @@ -61,6 +61,9 @@ type Attributes struct { // 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 @@ -76,7 +79,8 @@ type PrivActionBook struct { // The type of value depends on SequenceStrategy. // This field allows the PrivActionBook to prune // all previous Actions while preserving sequencing. - sequenceAccum interface{} + // XXX string or TextMarshaller() or Stringer() or? + sequenceAccum string } func NewPrivActionBook() *PrivActionBook { @@ -88,13 +92,42 @@ func NewPrivActionBook() *PrivActionBook { // The execution of action generally should happen // atomically with authorization by caller. // -// if err := ab.Authorize(); err != nil { +// 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. +} + +func (pab *PrivActionBook) Len() int { + return pab.book.Len() +} + +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 } //---------------------------------------- From 50050422c48db5c8e0050310291cef436621a71d Mon Sep 17 00:00:00 2001 From: jaekwon Date: Tue, 17 Oct 2023 17:34:32 -0700 Subject: [PATCH 09/27] ... --- examples/gno.land/p/jaekwon/book/book.gno | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/examples/gno.land/p/jaekwon/book/book.gno b/examples/gno.land/p/jaekwon/book/book.gno index 0d0581f0d83..ec5d6b64af6 100644 --- a/examples/gno.land/p/jaekwon/book/book.gno +++ b/examples/gno.land/p/jaekwon/book/book.gno @@ -11,6 +11,16 @@ Books can be components of a virtual Person (where a Person is an interface). import "gno.land/p/demo/avl" +//---------------------------------------- +// Usage example + +func main() { + // XXX TODO fill out basic examples. +} + +//---------------------------------------- +// Book + type Book struct { attrs Attributes entries avl.Tree @@ -104,12 +114,22 @@ func (pab *PrivActionBook) Append(action Action) 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 From 8288c03669a6dbbfcc5793d435428911004e302c Mon Sep 17 00:00:00 2001 From: jaekwon Date: Wed, 18 Oct 2023 12:10:24 -0700 Subject: [PATCH 10/27] ... --- examples/gno.land/p/jaekwon/book/book.gno | 142 ++++++++++++++++------ 1 file changed, 102 insertions(+), 40 deletions(-) diff --git a/examples/gno.land/p/jaekwon/book/book.gno b/examples/gno.land/p/jaekwon/book/book.gno index ec5d6b64af6..b6deaa7ad42 100644 --- a/examples/gno.land/p/jaekwon/book/book.gno +++ b/examples/gno.land/p/jaekwon/book/book.gno @@ -15,7 +15,59 @@ import "gno.land/p/demo/avl" // Usage example func main() { - // XXX TODO fill out basic examples. + // main usage flow + action := Action{...} + + //---------------------------------------- + // CASES + // Here are some usage examples. + + // CASE 1A: dummy "priv" user. + // access to privUser is like access to keys. + err := privUser.AppendAction(action) + if err != nil { panic(err) } + err := object.Receive(action) + if err != nil {panic(err)} + + // CASE 1B: ActionRunner with no signing + // This is just a wrapper around CASE 1A. + ar := ActionRunner() + err := sar.RunWith(action, privUser) + if err != nil {panic(err) } + // sar.RunWith() above calls the following: + if false { + err := privUser.AppendAction(action) + if err != nil { panic(err) } + // or ar.GetObject(action.Object).Receive(action). + err := action.object.Receive(action) + if err != nil { panic(err) } + } + + // CASE 2A: public crypto key user. + // This is incomplete because + // anyone can call user.Authenticate(), + // but not anyone would complete the tx. + // For this, see CASE 2B below. + priv := _ // imagine we have the key + signed := SignAction(action, priv) + err := user.Authenticate(signed) + if err != nil { panic(err) } + err := object.Receive(action) + if err != nil {panic(err)} + + // CASE 2B: SignedActionRunner + // SignedActionRunner embeds an ActionRunner. + sar := SignedActionRunner() + err := sar.Run(signed) + if err != nil { panic(err) } + // sar.Run() above calls the following: + if false { + err := signed.subject.Authenticate(signed) + if err != nil { panic(err) } + // or sar.GetObject(signed.Object).Receive(action). + err := signed.object.Receive(action) + if err != nil { panic(err) } + } } //---------------------------------------- @@ -47,6 +99,50 @@ type Attributes struct { // 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 @@ -151,44 +247,10 @@ func (pab *PrivActionBook) SequenceAccum() string { } //---------------------------------------- -// 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 -} +// User -type Signature struct { - account number // or address with some extra data unknown - sequence number // or alternative to sequence - signature []byte +type User struct { + // each user has an associated pab + // XXX or, users have devices, each with a pab. + pab *PrivActionBook } From 579268eed1b77ee066e7638851f112d5b7bb9339 Mon Sep 17 00:00:00 2001 From: jaekwon Date: Wed, 18 Oct 2023 12:30:55 -0700 Subject: [PATCH 11/27] ... --- examples/gno.land/p/jaekwon/book/book.gno | 83 ++++++++++------------- 1 file changed, 36 insertions(+), 47 deletions(-) diff --git a/examples/gno.land/p/jaekwon/book/book.gno b/examples/gno.land/p/jaekwon/book/book.gno index b6deaa7ad42..56a1c1d950f 100644 --- a/examples/gno.land/p/jaekwon/book/book.gno +++ b/examples/gno.land/p/jaekwon/book/book.gno @@ -2,10 +2,12 @@ package book /* -This is a concept for a book (basically a list). -A Book has a title, owner, etc. -A Book can be forked. -Books can be components of a virtual Person (where a Person is an interface). +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. */ @@ -15,59 +17,45 @@ import "gno.land/p/demo/avl" // Usage example func main() { - // main usage flow + // directory of subjects/objects. + directory := Directory{...} + // action to perform. action := Action{...} //---------------------------------------- // CASES // Here are some usage examples. + // For simplicity all methods panic on error. - // CASE 1A: dummy "priv" user. - // access to privUser is like access to keys. - err := privUser.AppendAction(action) - if err != nil { panic(err) } - err := object.Receive(action) - if err != nil {panic(err)} + // CASE 1A: dummy "priv" account. + // Access to privAccount is like access to priv keys. + privAccount.CommitAction(action) + directory.GetObject(action.object).Receive(action) // CASE 1B: ActionRunner with no signing // This is just a wrapper around CASE 1A. - ar := ActionRunner() - err := sar.RunWith(action, privUser) - if err != nil {panic(err) } - // sar.RunWith() above calls the following: - if false { - err := privUser.AppendAction(action) - if err != nil { panic(err) } - // or ar.GetObject(action.Object).Receive(action). - err := action.object.Receive(action) - if err != nil { panic(err) } - } - - // CASE 2A: public crypto key user. - // This is incomplete because - // anyone can call user.Authenticate(), + ar := ActionRunner(directory) + sar.RunWith(action, privAccount) + // sar.RunWith() impl: + // privAccount.CommitAction(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. - // For this, see CASE 2B below. - priv := _ // imagine we have the key - signed := SignAction(action, priv) - err := user.Authenticate(signed) - if err != nil { panic(err) } - err := object.Receive(action) - if err != nil {panic(err)} + 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() - err := sar.Run(signed) - if err != nil { panic(err) } - // sar.Run() above calls the following: - if false { - err := signed.subject.Authenticate(signed) - if err != nil { panic(err) } - // or sar.GetObject(signed.Object).Receive(action). - err := signed.object.Receive(action) - if err != nil { panic(err) } - } + sar.Run(signed) + // sar.Run() impl: + // signed.subject.Authenticate(signed) + // sar.dir.GetObject(action.object).Receive(action) } //---------------------------------------- @@ -247,10 +235,11 @@ func (pab *PrivActionBook) SequenceAccum() string { } //---------------------------------------- -// User +// Account -type User struct { - // each user has an associated pab - // XXX or, users have devices, each with a pab. +// XXX incomplete. +type Account struct { + // each account has an associated pab + // XXX or, accounts have devices, each with a pab. pab *PrivActionBook } From 7bf41fad923d44c77f65d1976b4c93b5b22d96ed Mon Sep 17 00:00:00 2001 From: jaekwon Date: Wed, 18 Oct 2023 12:45:00 -0700 Subject: [PATCH 12/27] ... --- examples/gno.land/p/jaekwon/book/book.gno | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/examples/gno.land/p/jaekwon/book/book.gno b/examples/gno.land/p/jaekwon/book/book.gno index 56a1c1d950f..5fca7e5a56a 100644 --- a/examples/gno.land/p/jaekwon/book/book.gno +++ b/examples/gno.land/p/jaekwon/book/book.gno @@ -11,6 +11,23 @@ GOALS: */ +// 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" //---------------------------------------- From d5d55f5152c5d37068fa9f2a252f437e20d2d454 Mon Sep 17 00:00:00 2001 From: jaekwon Date: Wed, 18 Oct 2023 13:42:23 -0700 Subject: [PATCH 13/27] ... --- examples/gno.land/p/jaekwon/book/book.gno | 102 ++++++++++++++++++++-- 1 file changed, 95 insertions(+), 7 deletions(-) diff --git a/examples/gno.land/p/jaekwon/book/book.gno b/examples/gno.land/p/jaekwon/book/book.gno index 5fca7e5a56a..1174c7ea67b 100644 --- a/examples/gno.land/p/jaekwon/book/book.gno +++ b/examples/gno.land/p/jaekwon/book/book.gno @@ -14,7 +14,7 @@ GOALS: // XXX open questions // * Do we want a directory? // * Should action memoize object/subject/args? -// * Should args be interface{}? +// * 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. @@ -26,7 +26,7 @@ GOALS: // - 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" @@ -35,9 +35,9 @@ import "gno.land/p/demo/avl" func main() { // directory of subjects/objects. - directory := Directory{...} + directory := NewDirectory("test") // action to perform. - action := Action{...} + action := Action{} //---------------------------------------- // CASES @@ -46,15 +46,15 @@ func main() { // CASE 1A: dummy "priv" account. // Access to privAccount is like access to priv keys. - privAccount.CommitAction(action) + privAccount.AppendAction(action) directory.GetObject(action.object).Receive(action) // CASE 1B: ActionRunner with no signing - // This is just a wrapper around CASE 1A. + // This is just a wrapper around CASE 1A. ar := ActionRunner(directory) sar.RunWith(action, privAccount) // sar.RunWith() impl: - // privAccount.CommitAction(action) + // privAccount.AppendAction(action) // ar.dir.GetObject(action.object).Receive(action) // CASE 2A: public crypto key account. @@ -75,6 +75,94 @@ func main() { // sar.dir.GetObject(action.object).Receive(action) } +//---------------------------------------- +// Object + +type ObjectKey int + +func (key ObjectKey) String() string { + // XXX itoa +} + +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 + lastKey int64 // last key used + objects avl.Tree // id -> Object +} + +func NewDirectory(name string) *Directory { + return &Directory{ + name: name, + lastKey: 0, + objects: nil, + } +} + +func (dir *Directory) GetName() string { + return dir.name +} + +func (dir *Directory) GetObject(key ObjectKey) Object { + keystr := key.String() + obj, exists := dir.objects.Get(keystr) + if !exists { + panic("object not found: invalid key") + } + return obj +} + +func (dir *Directory) AddObject(obj Object) { + nextKey := dir.nextKey() + obj.SetKey(nextKey) + keystr := nextKey.String() + updated := dir.objects.Set(keystr, obj) + if updated == true { + panic("duplicate object key") + } +} + +func (dir *Directory) RemoveObject(obj Object) { + keystr := obj.GetKey().String() + old, removed := dir.objects.Remove(keystr) + if !removed { + panic("cannot remove object: not found") + } + if old != obj { + panic("cannot remove conflicting object") + } +} + +func (dir *Directory) RemoveObjectByKey(key ObjectKey) { + keystr := key.String() + _, removed := dir.objects.Remove(keystr) + if !removed { + panic("cannot remove object: not found") + } +} + +func (dir *Directory) nextKey() ObjectKey { + nextKey := dir.lastKey + 1 + if nextKey < 0 { + panic("key overflow") + } + dir.lastKey = nextKey + return ObjectKey(nextKey) +} + //---------------------------------------- // Book From 44951f1298b6078ebe45ec482de34b2f5135b9a1 Mon Sep 17 00:00:00 2001 From: jaekwon Date: Wed, 18 Oct 2023 14:02:14 -0700 Subject: [PATCH 14/27] ... --- examples/gno.land/p/jaekwon/book/book.gno | 65 ++++++++++++++++------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/examples/gno.land/p/jaekwon/book/book.gno b/examples/gno.land/p/jaekwon/book/book.gno index 1174c7ea67b..279fe7c5645 100644 --- a/examples/gno.land/p/jaekwon/book/book.gno +++ b/examples/gno.land/p/jaekwon/book/book.gno @@ -78,10 +78,13 @@ func main() { //---------------------------------------- // Object -type ObjectKey int +type ObjectKey string -func (key ObjectKey) String() string { - // XXX itoa +func (key ObjectKey) Validate() { + if len(key) == "" { + panic("invalid key") + } + // XXX make sure key is alphanumeric. } type Object interface { @@ -100,15 +103,19 @@ type Object interface { type Directory struct { name string // name of directory - lastKey int64 // last key used - objects avl.Tree // id -> Object + 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) *Directory { +func NewDirectory(name string, auto bool, prefix string) *Directory { return &Directory{ name: name, - lastKey: 0, objects: nil, + auto: auto, + lastKey: 0, + prefix: prefix, } } @@ -117,8 +124,7 @@ func (dir *Directory) GetName() string { } func (dir *Directory) GetObject(key ObjectKey) Object { - keystr := key.String() - obj, exists := dir.objects.Get(keystr) + obj, exists := dir.objects.Get(key) if !exists { panic("object not found: invalid key") } @@ -126,18 +132,26 @@ func (dir *Directory) GetObject(key ObjectKey) Object { } func (dir *Directory) AddObject(obj Object) { - nextKey := dir.nextKey() - obj.SetKey(nextKey) - keystr := nextKey.String() - updated := dir.objects.Set(keystr, obj) + 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) { - keystr := obj.GetKey().String() - old, removed := dir.objects.Remove(keystr) + key := obj.GetKey() + old, removed := dir.objects.Remove(string(key)) if !removed { panic("cannot remove object: not found") } @@ -147,20 +161,25 @@ func (dir *Directory) RemoveObject(obj Object) { } func (dir *Directory) RemoveObjectByKey(key ObjectKey) { - keystr := key.String() - _, removed := dir.objects.Remove(keystr) + 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(nextKey) + return ObjectKey(dir.prefix + itoa(nextKey)) } //---------------------------------------- @@ -348,3 +367,13 @@ type Account struct { // 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 +} From 7ae5b1db036768c8a381a48cfae2f59d89e7a03f Mon Sep 17 00:00:00 2001 From: jaekwon Date: Wed, 18 Oct 2023 15:50:23 -0700 Subject: [PATCH 15/27] ... --- examples/gno.land/p/jaekwon/book/book.gno | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/gno.land/p/jaekwon/book/book.gno b/examples/gno.land/p/jaekwon/book/book.gno index 279fe7c5645..a96e5c41986 100644 --- a/examples/gno.land/p/jaekwon/book/book.gno +++ b/examples/gno.land/p/jaekwon/book/book.gno @@ -15,7 +15,7 @@ GOALS: // * Do we want a directory? // * Should action memoize object/subject/args? // * Should args be interface{}? -// - NOTE: Amino *can* encode anything registered. +// - NOTE: Amino *can* encode almost 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. @@ -47,7 +47,7 @@ func main() { // CASE 1A: dummy "priv" account. // Access to privAccount is like access to priv keys. privAccount.AppendAction(action) - directory.GetObject(action.object).Receive(action) + directory.GetObject(action.object).Fulfill(action) // CASE 1B: ActionRunner with no signing // This is just a wrapper around CASE 1A. @@ -55,7 +55,7 @@ func main() { sar.RunWith(action, privAccount) // sar.RunWith() impl: // privAccount.AppendAction(action) - // ar.dir.GetObject(action.object).Receive(action) + // ar.dir.GetObject(action.object).Fulfill(action) // CASE 2A: public crypto key account. // NOTE: This is NOT secure because @@ -64,7 +64,7 @@ func main() { privKey := _ // imagine we have the key signed := SignAction(action, privKey) account.Authenticate(signed) - directory.GetObject(action.object).Receive(action) + directory.GetObject(action.object).Fulfill(action) // CASE 2B: SignedActionRunner // SignedActionRunner embeds an ActionRunner. @@ -72,7 +72,7 @@ func main() { sar.Run(signed) // sar.Run() impl: // signed.subject.Authenticate(signed) - // sar.dir.GetObject(action.object).Receive(action) + // sar.dir.GetObject(action.object).Fulfill(action) } //---------------------------------------- @@ -93,9 +93,10 @@ type Object interface { // Set the prmiary key. // Panics if key exists. SetKey(ObjectKey) - // Receive performs action on object. + // Fulfill performs action on self. // Panics if action is not permitted. - Receive(Action) + // XXX what about return values? (prob ignore) + Fulfill(Action) } //---------------------------------------- From 4d436584fb7f12cfb31dc88972d2a61f3981fa29 Mon Sep 17 00:00:00 2001 From: jaekwon Date: Wed, 25 Oct 2023 16:42:14 -0700 Subject: [PATCH 16/27] refactor; and use std.Address! --- examples/gno.land/p/jaekwon/book/action.gno | 81 +++++++ examples/gno.land/p/jaekwon/book/book.gno | 204 ++---------------- .../gno.land/p/jaekwon/book/committee.gno | 14 ++ .../gno.land/p/jaekwon/book/directory.gno | 89 ++++++++ examples/gno.land/p/jaekwon/book/misc.gno | 10 + gnovm/stdlibs/std/crypto.gno | 19 ++ gnovm/stdlibs/stdlibs.go | 2 + 7 files changed, 233 insertions(+), 186 deletions(-) create mode 100644 examples/gno.land/p/jaekwon/book/action.gno create mode 100644 examples/gno.land/p/jaekwon/book/committee.gno create mode 100644 examples/gno.land/p/jaekwon/book/directory.gno create mode 100644 examples/gno.land/p/jaekwon/book/misc.gno diff --git a/examples/gno.land/p/jaekwon/book/action.gno b/examples/gno.land/p/jaekwon/book/action.gno new file mode 100644 index 00000000000..d23ab095871 --- /dev/null +++ b/examples/gno.land/p/jaekwon/book/action.gno @@ -0,0 +1,81 @@ +package book + +import "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 +} + +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] } + +//---------------------------------------- +// Actor + +type Actor interface { + std.Addressable +} + +//---------------------------------------- +// Object + +type Object interface { + // The primary key of the object. + GetObjectKey() ObjectKey + // Set the prmiary key. + // Panics if key exists. + SetObjectKey(ObjectKey) + // Receive performs action on self. + // Panics if action is not permitted. + // NOTE: no return value, like a one way channel. + ReceiveFrom(*Action, Actor) +} + +type ObjectKey string + +func (key ObjectKey) Validate() { + if len(key) == "" { + panic("invalid key") + } + // XXX make sure key is alphanumeric. +} + +//---------------------------------------- +// 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 +} diff --git a/examples/gno.land/p/jaekwon/book/book.gno b/examples/gno.land/p/jaekwon/book/book.gno index a96e5c41986..dec15d8265e 100644 --- a/examples/gno.land/p/jaekwon/book/book.gno +++ b/examples/gno.land/p/jaekwon/book/book.gno @@ -5,12 +5,15 @@ package book GOALS: 1. Create a Book (aka List) of text, - that is owned by a Book of Persons. + that is owned by a Book of Actors. -2. Allow a Book of Persons to split/fork. +2. Allow a Book of Actors to split/fork. + +3. Later, a Person is interface { Actor ...}. */ +// TODO use this for Tiers. // XXX open questions // * Do we want a directory? // * Should action memoize object/subject/args? @@ -47,15 +50,15 @@ func main() { // CASE 1A: dummy "priv" account. // Access to privAccount is like access to priv keys. privAccount.AppendAction(action) - directory.GetObject(action.object).Fulfill(action) + directory.GetObject(action.object).Receive(action) - // CASE 1B: ActionRunner with no signing + // CASE 1B: ActionReceiveer with no signing // This is just a wrapper around CASE 1A. - ar := ActionRunner(directory) - sar.RunWith(action, privAccount) - // sar.RunWith() impl: + ar := ActionReceiveer(directory) + sar.ReceiveBy(action, privAccount) + // sar.ReceiveBy() impl: // privAccount.AppendAction(action) - // ar.dir.GetObject(action.object).Fulfill(action) + // ar.dir.GetObject(action.object).Receive(action) // CASE 2A: public crypto key account. // NOTE: This is NOT secure because @@ -64,123 +67,15 @@ func main() { privKey := _ // imagine we have the key signed := SignAction(action, privKey) account.Authenticate(signed) - directory.GetObject(action.object).Fulfill(action) + directory.GetObject(action.object).Receive(action) - // CASE 2B: SignedActionRunner - // SignedActionRunner embeds an ActionRunner. - sar := SignedActionRunner() - sar.Run(signed) - // sar.Run() impl: + // CASE 2B: SignedActionReceiveer + // SignedActionReceiveer embeds an ActionReceiveer. + sar := SignedActionReceiveer() + sar.Receive(signed) + // sar.Receive() impl: // signed.subject.Authenticate(signed) - // sar.dir.GetObject(action.object).Fulfill(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) - // Fulfill performs action on self. - // Panics if action is not permitted. - // XXX what about return values? (prob ignore) - Fulfill(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)) + // sar.dir.GetObject(action.object).Receive(action) } //---------------------------------------- @@ -203,59 +98,6 @@ 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 @@ -359,16 +201,6 @@ 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 diff --git a/examples/gno.land/p/jaekwon/book/committee.gno b/examples/gno.land/p/jaekwon/book/committee.gno new file mode 100644 index 00000000000..308c358c93c --- /dev/null +++ b/examples/gno.land/p/jaekwon/book/committee.gno @@ -0,0 +1,14 @@ +package book + +//---------------------------------------- +// Committee + +// XXX incomplete. +type Committee struct { + // each account has an associated pab + // XXX or, accounts have devices, each with a pab. + pab *PrivActionBook +} + +// A Committee is an actor +var _ Actor = *Committee{} diff --git a/examples/gno.land/p/jaekwon/book/directory.gno b/examples/gno.land/p/jaekwon/book/directory.gno new file mode 100644 index 00000000000..9ac8d0a8f9d --- /dev/null +++ b/examples/gno.land/p/jaekwon/book/directory.gno @@ -0,0 +1,89 @@ +package book + +import ( + "gno.land/r/demo/avl" +) + +//---------------------------------------- +// 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)) +} diff --git a/examples/gno.land/p/jaekwon/book/misc.gno b/examples/gno.land/p/jaekwon/book/misc.gno new file mode 100644 index 00000000000..e95a08889cc --- /dev/null +++ b/examples/gno.land/p/jaekwon/book/misc.gno @@ -0,0 +1,10 @@ +package book + +//---------------------------------------- +// Attributes + +type Attributes struct { + meta avl.Tree // catchall + author Author + // TODO +} diff --git a/gnovm/stdlibs/std/crypto.gno b/gnovm/stdlibs/std/crypto.gno index 3ebd802dc3f..b5c9b986777 100644 --- a/gnovm/stdlibs/std/crypto.gno +++ b/gnovm/stdlibs/std/crypto.gno @@ -9,3 +9,22 @@ func (a Address) String() string { const RawAddressSize = 20 type RawAddress [RawAddressSize]byte + +type Addressable interface { + // XXX document requirements. + GetAddress() std.Address +} + +// TODO implement as a native function, +// see DerivePkgAddr. +func AddrFromPkgPath(pkgPath string) Address { + panic("not yet implemented") +} + +// Addresses can also be derived from a subpath. +// This way anything with an address also has a URI path. +// TODO implement as a native function. +func AddrFromAddressable(pkgPath, subpath string) Address { + // hash(pkgPath + "/" + subpath) + panic("not yet implemented") +} diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index fb230a0cf86..d334c935805 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -502,6 +502,8 @@ func InjectPackage(store gno.Store, pn *gno.PackageNode) { } }, ) + // XXX TO DEPRECATE. + // TODO migrate as native function std.AddrFromPkgPath(). pn.DefineNative("DerivePkgAddr", gno.Flds( // params "pkgPath", "string", From d1738fa13507a928992091a94b451a5c2198def8 Mon Sep 17 00:00:00 2001 From: jaekwon Date: Wed, 25 Oct 2023 19:07:12 -0700 Subject: [PATCH 17/27] Directory uses std.Address --- examples/gno.land/p/jaekwon/book/action.gno | 15 +---- examples/gno.land/p/jaekwon/book/book.gno | 6 +- .../gno.land/p/jaekwon/book/directory.gno | 65 ++++++------------- gnovm/stdlibs/std/crypto.gno | 6 ++ 4 files changed, 30 insertions(+), 62 deletions(-) diff --git a/examples/gno.land/p/jaekwon/book/action.gno b/examples/gno.land/p/jaekwon/book/action.gno index d23ab095871..b6660fee971 100644 --- a/examples/gno.land/p/jaekwon/book/action.gno +++ b/examples/gno.land/p/jaekwon/book/action.gno @@ -39,26 +39,13 @@ type Actor interface { // Object type Object interface { - // The primary key of the object. - GetObjectKey() ObjectKey - // Set the prmiary key. - // Panics if key exists. - SetObjectKey(ObjectKey) + std.Addressable // Receive performs action on self. // Panics if action is not permitted. // NOTE: no return value, like a one way channel. ReceiveFrom(*Action, Actor) } -type ObjectKey string - -func (key ObjectKey) Validate() { - if len(key) == "" { - panic("invalid key") - } - // XXX make sure key is alphanumeric. -} - //---------------------------------------- // Authorization diff --git a/examples/gno.land/p/jaekwon/book/book.gno b/examples/gno.land/p/jaekwon/book/book.gno index dec15d8265e..f4277d9e2f9 100644 --- a/examples/gno.land/p/jaekwon/book/book.gno +++ b/examples/gno.land/p/jaekwon/book/book.gno @@ -50,7 +50,7 @@ func main() { // CASE 1A: dummy "priv" account. // Access to privAccount is like access to priv keys. privAccount.AppendAction(action) - directory.GetObject(action.object).Receive(action) + directory.Get(action.object).Receive(action) // CASE 1B: ActionReceiveer with no signing // This is just a wrapper around CASE 1A. @@ -67,7 +67,7 @@ func main() { privKey := _ // imagine we have the key signed := SignAction(action, privKey) account.Authenticate(signed) - directory.GetObject(action.object).Receive(action) + directory.Get(action.object).Receive(action) // CASE 2B: SignedActionReceiveer // SignedActionReceiveer embeds an ActionReceiveer. @@ -123,7 +123,7 @@ func (bk *Book) Get(n int) XXX { type PrivActionBook struct { // All actions must have this object. - object string + object std.FullAddress // Maybe PrivActionBook *is* Book? // not sure yet. diff --git a/examples/gno.land/p/jaekwon/book/directory.gno b/examples/gno.land/p/jaekwon/book/directory.gno index 9ac8d0a8f9d..81bf2a86053 100644 --- a/examples/gno.land/p/jaekwon/book/directory.gno +++ b/examples/gno.land/p/jaekwon/book/directory.gno @@ -1,6 +1,8 @@ package book import ( + "std" + "gno.land/r/demo/avl" ) @@ -9,19 +11,13 @@ import ( 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 + objects avl.Tree // std.Address -> Addressable } -func NewDirectory(name string, auto bool, prefix string) *Directory { +func NewDirectory(name string) *Directory { return &Directory{ name: name, objects: nil, - auto: auto, - lastKey: 0, - prefix: prefix, } } @@ -29,35 +25,29 @@ func (dir *Directory) GetName() string { return dir.name } -func (dir *Directory) GetObject(key ObjectKey) Object { - obj, exists := dir.objects.Get(key) +func (dir *Directory) Get(addr std.Address) std.Addressable { + obj, exists := dir.objects.Get(addr) if !exists { - panic("object not found: invalid key") + panic("object not found: invalid address") } 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) +func (dir *Directory) Has(addr std.Address) bool { + return dir.objects.Has(addr) +} + +func (dir *Directory) Add(obj std.Addressable) { + addr := obj.GetAddress() + updated := dir.objects.Set(string(addr), obj) if updated == true { - panic("duplicate object key") + panic("duplicate address") } } -func (dir *Directory) RemoveObject(obj Object) { - key := obj.GetKey() - old, removed := dir.objects.Remove(string(key)) +func (dir *Directory) Remove(obj std.Addressable) { + addr := obj.GetAddress() + old, removed := dir.objects.Remove(string(addr)) if !removed { panic("cannot remove object: not found") } @@ -66,24 +56,9 @@ func (dir *Directory) RemoveObject(obj Object) { } } -func (dir *Directory) RemoveObjectByKey(key ObjectKey) { - if !hasPrefix(key, dir.prefix) { - panic("invalid key: prefix mismatch") - } - _, removed := dir.objects.Remove(string(key)) +func (dir *Directory) RemoveByAddress(addr std.Address) { + _, removed := dir.objects.Remove(string(addr)) 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)) -} diff --git a/gnovm/stdlibs/std/crypto.gno b/gnovm/stdlibs/std/crypto.gno index b5c9b986777..bcef8655f54 100644 --- a/gnovm/stdlibs/std/crypto.gno +++ b/gnovm/stdlibs/std/crypto.gno @@ -28,3 +28,9 @@ func AddrFromAddressable(pkgPath, subpath string) Address { // hash(pkgPath + "/" + subpath) panic("not yet implemented") } + +// A standard structure for storing Subpath and Address. +type SubpathAddress struct { + Subpath string + Address std.Address +} From 12e0f79df3afbc38558c0be2d94318bdc276146f Mon Sep 17 00:00:00 2001 From: jaekwon Date: Thu, 2 Nov 2023 17:11:32 -0700 Subject: [PATCH 18/27] ... --- examples/gno.land/p/jaekwon/book/action.gno | 83 ++++++++++++++++--- examples/gno.land/p/jaekwon/book/book.gno | 9 ++ .../gno.land/p/jaekwon/book/committee.gno | 16 +++- gnovm/stdlibs/std/crypto.gno | 7 ++ 4 files changed, 103 insertions(+), 12 deletions(-) diff --git a/examples/gno.land/p/jaekwon/book/action.gno b/examples/gno.land/p/jaekwon/book/action.gno index b6660fee971..7d99d25bd44 100644 --- a/examples/gno.land/p/jaekwon/book/action.gno +++ b/examples/gno.land/p/jaekwon/book/action.gno @@ -14,19 +14,48 @@ import "std" // 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 + 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) 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) setComplete() { + if a.status != ActionStatusPending { + panic("should not happen") + } + a.status = ActionStatusComplete +} + +func (a Action) setPanic() { + a.status = ActionStatusPanic +} + +type ActionStatus int + +const ( + ActionStatusNew = iota + ActionStatusPending + ActionStatusComplete + ActionStatusPanic +) //---------------------------------------- // Actor @@ -46,6 +75,38 @@ type Object interface { ReceiveFrom(*Action, Actor) } +//---------------------------------------- +// Main entry methods + +// XXX Well maybe it should be a method on a directory. +// XXX otherwise how do we know if actor/object are true. +func Perform(action Action, actor Actor, object Object) { + // Defer to set panic upon panic. + defer func() { + if r := recover(); r != nil { + action.setPanic() + panic(r) + } + }() + + // Check actor matches. + // XXX well but maybe the actor is fake + // Check object matches. + // XXX well but maybe the object is fake + + // XXX ensure that the actor has authorized + // XXX if actor wants signature verification + // XXX if actor has a privactionbook + + // Make action pending. + // This is the only place where this happens. + if action.Status() != ActionStatusNew { + panic("action not new") + } else { + action.setPending() + } +} + //---------------------------------------- // Authorization diff --git a/examples/gno.land/p/jaekwon/book/book.gno b/examples/gno.land/p/jaekwon/book/book.gno index f4277d9e2f9..5eea94235d0 100644 --- a/examples/gno.land/p/jaekwon/book/book.gno +++ b/examples/gno.land/p/jaekwon/book/book.gno @@ -86,6 +86,15 @@ type Book struct { entries avl.Tree } +var _ Object = &Book{} + +func (bk *Book) ReceiveFrom(action *Action, actor Actor) { + // XXX how does Book know + // XXX whether actor authenticated it? + // XXX + XXX +} + func (bk *Book) Append(XXX) XXX { XXX } diff --git a/examples/gno.land/p/jaekwon/book/committee.gno b/examples/gno.land/p/jaekwon/book/committee.gno index 308c358c93c..f1d7741fbab 100644 --- a/examples/gno.land/p/jaekwon/book/committee.gno +++ b/examples/gno.land/p/jaekwon/book/committee.gno @@ -5,10 +5,24 @@ package book // XXX incomplete. type Committee struct { + + // full addr. + faddr std.FullAddress + // each account has an associated pab - // XXX or, accounts have devices, each with a pab. pab *PrivActionBook } // A Committee is an actor var _ Actor = *Committee{} + +func NewCommittee(pkgPath, subpath string) *Committee { + return &Committee{ + faddr: xxx, + pab: xxx, + } +} + +func (cm *Committee) GetAddress() std.Address { + XXX +} diff --git a/gnovm/stdlibs/std/crypto.gno b/gnovm/stdlibs/std/crypto.gno index bcef8655f54..01d08c4ed37 100644 --- a/gnovm/stdlibs/std/crypto.gno +++ b/gnovm/stdlibs/std/crypto.gno @@ -34,3 +34,10 @@ type SubpathAddress struct { Subpath string Address std.Address } + +// A standard structure for storing all addr related fields. +type FullAddress struct { + PkgPath string + Subpath string + Address std.Address +} From 425df3c0545033e58e94740e1f4afcf79c76d64a Mon Sep 17 00:00:00 2001 From: jaekwon Date: Sun, 5 Nov 2023 01:46:01 -0800 Subject: [PATCH 19/27] ... --- examples/gno.land/p/jaekwon/book/action.gno | 43 ++++++++++--------- .../gno.land/p/jaekwon/book/directory.gno | 35 ++++++++++----- 2 files changed, 47 insertions(+), 31 deletions(-) diff --git a/examples/gno.land/p/jaekwon/book/action.gno b/examples/gno.land/p/jaekwon/book/action.gno index 7d99d25bd44..301d83ccb70 100644 --- a/examples/gno.land/p/jaekwon/book/action.gno +++ b/examples/gno.land/p/jaekwon/book/action.gno @@ -5,8 +5,8 @@ import "std" //---------------------------------------- // Action -// A actor (subject) acts upon an object with a verb and arguments. -// As if actor is calling object.verb(args...). +// 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. @@ -78,9 +78,8 @@ type Object interface { //---------------------------------------- // Main entry methods -// XXX Well maybe it should be a method on a directory. -// XXX otherwise how do we know if actor/object are true. -func Perform(action Action, actor Actor, object Object) { +// Perform the action on the actor/object in directory. +func Perform(dir Directory, action Action) { // Defer to set panic upon panic. defer func() { if r := recover(); r != nil { @@ -89,22 +88,24 @@ func Perform(action Action, actor Actor, object Object) { } }() - // Check actor matches. - // XXX well but maybe the actor is fake - // Check object matches. - // XXX well but maybe the object is fake - - // XXX ensure that the actor has authorized - // XXX if actor wants signature verification - // XXX if actor has a privactionbook - - // Make action pending. - // This is the only place where this happens. - if action.Status() != ActionStatusNew { - panic("action not new") - } else { - action.setPending() - } + // validate what can be validated. + action.ValidateBasic() + // get actor and object + actor := dir.Get(action.actor) + object := dir.Get(atino.object) + + // XXX check that action is allowed. + // XXX what are the security assumptions? + // XXX what if someone unauthorized calls it? + action.WillPerform(action, object) + // XXX set status to pending (???) + action.setPending() + // XXX have object handle action. + // XXX what are the security assumptions? + // XXX what if someone unauthorized calls it? + object.ReceiveFrom(action, actor) + // XXX set status to com0lete. + action.setComplete() } //---------------------------------------- diff --git a/examples/gno.land/p/jaekwon/book/directory.gno b/examples/gno.land/p/jaekwon/book/directory.gno index 81bf2a86053..63eb5c565f3 100644 --- a/examples/gno.land/p/jaekwon/book/directory.gno +++ b/examples/gno.land/p/jaekwon/book/directory.gno @@ -7,25 +7,40 @@ import ( ) //---------------------------------------- -// Directory +// Diretory -type Directory struct { +// The Directory as interface is arguably a bit too powerful. +// Custom implementations can do anything, so be careful +// to use only fully vetted, audited, complete implementations. +type Directory interface { + GetName() string + Get(std.Address) std.Addressable + Has(std.Address) bool + Add(std.Addressable) + Remove(std.Addressable) + RemoveByAddress(std.Addressable) +} + +//---------------------------------------- +// defaultDirectory + +type defaultDirectory struct { name string // name of directory objects avl.Tree // std.Address -> Addressable } -func NewDirectory(name string) *Directory { - return &Directory{ +func NewDefaultDirectory(name string) *defaultDirectory { + return &defaultDirectory{ name: name, objects: nil, } } -func (dir *Directory) GetName() string { +func (dir *defaultDirectory) GetName() string { return dir.name } -func (dir *Directory) Get(addr std.Address) std.Addressable { +func (dir *defaultDirectory) Get(addr std.Address) std.Addressable { obj, exists := dir.objects.Get(addr) if !exists { panic("object not found: invalid address") @@ -33,11 +48,11 @@ func (dir *Directory) Get(addr std.Address) std.Addressable { return obj } -func (dir *Directory) Has(addr std.Address) bool { +func (dir *defaultDirectory) Has(addr std.Address) bool { return dir.objects.Has(addr) } -func (dir *Directory) Add(obj std.Addressable) { +func (dir *defaultDirectory) Add(obj std.Addressable) { addr := obj.GetAddress() updated := dir.objects.Set(string(addr), obj) if updated == true { @@ -45,7 +60,7 @@ func (dir *Directory) Add(obj std.Addressable) { } } -func (dir *Directory) Remove(obj std.Addressable) { +func (dir *defaultDirectory) Remove(obj std.Addressable) { addr := obj.GetAddress() old, removed := dir.objects.Remove(string(addr)) if !removed { @@ -56,7 +71,7 @@ func (dir *Directory) Remove(obj std.Addressable) { } } -func (dir *Directory) RemoveByAddress(addr std.Address) { +func (dir *defaultDirectory) RemoveByAddress(addr std.Address) { _, removed := dir.objects.Remove(string(addr)) if !removed { panic("cannot remove object: not found") From 66200d6769fcaa807afb2bb51a2242e108dbf2ec Mon Sep 17 00:00:00 2001 From: jaekwon Date: Tue, 7 Nov 2023 14:39:49 -0800 Subject: [PATCH 20/27] ... --- examples/gno.land/p/jaekwon/book/README.md | 17 +++++++++++++++++ examples/gno.land/p/jaekwon/book/action.gno | 15 ++++++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 examples/gno.land/p/jaekwon/book/README.md diff --git a/examples/gno.land/p/jaekwon/book/README.md b/examples/gno.land/p/jaekwon/book/README.md new file mode 100644 index 00000000000..1f2b9ac4dee --- /dev/null +++ b/examples/gno.land/p/jaekwon/book/README.md @@ -0,0 +1,17 @@ +Design choices: + * 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: + * An unexposed (lowercase) declaration can still be used by anyone who holds it. + * Unexposed fields of any struct can still be copied by assignment. + * You can also copy an unexposed struct's unexposed field and get a reference. + `x := external.MakePrivateStructPtr(); y := *x; z := &y` + * 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` + * You can prevent the aforementioned by only returning interface values, and + generally preventing the holding of an unexposed declaration, but this also + depends on whether reflection supports instantiation. (VERIFY) diff --git a/examples/gno.land/p/jaekwon/book/action.gno b/examples/gno.land/p/jaekwon/book/action.gno index 301d83ccb70..0364ad3f3c7 100644 --- a/examples/gno.land/p/jaekwon/book/action.gno +++ b/examples/gno.land/p/jaekwon/book/action.gno @@ -79,6 +79,9 @@ type Object interface { // Main entry methods // Perform the action on the actor/object in directory. +// XXX but first, who composed the action? +// XXX only the actor should compose it. +// XXX but anyone can call this?! func Perform(dir Directory, action Action) { // Defer to set panic upon panic. defer func() { @@ -88,16 +91,18 @@ func Perform(dir Directory, action Action) { } }() - // validate what can be validated. + // Validate what can be validated. action.ValidateBasic() - // get actor and object + // Get actor and object. actor := dir.Get(action.actor) - object := dir.Get(atino.object) - + object := dir.Get(action.object) + // // XXX check that action is allowed. // XXX what are the security assumptions? // XXX what if someone unauthorized calls it? - action.WillPerform(action, object) + // set actor + action.setActor(actor) + actor.WillPerform(action, object) // XXX set status to pending (???) action.setPending() // XXX have object handle action. From 2b06cd2d974d64c826fbd2eee841987440f19956 Mon Sep 17 00:00:00 2001 From: jaekwon Date: Thu, 9 Nov 2023 01:36:34 -0800 Subject: [PATCH 21/27] ... --- examples/gno.land/p/jaekwon/book/README.md | 27 +++--- examples/gno.land/p/jaekwon/book/action.gno | 91 +++++++++++++++------ 2 files changed, 80 insertions(+), 38 deletions(-) diff --git a/examples/gno.land/p/jaekwon/book/README.md b/examples/gno.land/p/jaekwon/book/README.md index 1f2b9ac4dee..0eb64a4c613 100644 --- a/examples/gno.land/p/jaekwon/book/README.md +++ b/examples/gno.land/p/jaekwon/book/README.md @@ -1,17 +1,20 @@ -Design choices: +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: - * An unexposed (lowercase) declaration can still be used by anyone who holds it. - * Unexposed fields of any struct can still be copied by assignment. - * You can also copy an unexposed struct's unexposed field and get a reference. - `x := external.MakePrivateStructPtr(); y := *x; z := &y` - * 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` - * You can prevent the aforementioned by only returning interface values, and - generally preventing the holding of an unexposed declaration, but this also - depends on whether reflection supports instantiation. (VERIFY) +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` diff --git a/examples/gno.land/p/jaekwon/book/action.gno b/examples/gno.land/p/jaekwon/book/action.gno index 0364ad3f3c7..c97b8de3eb5 100644 --- a/examples/gno.land/p/jaekwon/book/action.gno +++ b/examples/gno.land/p/jaekwon/book/action.gno @@ -1,6 +1,9 @@ package book -import "std" +import ( + "errors" + "std" +) //---------------------------------------- // Action @@ -44,8 +47,8 @@ func (a Action) setComplete() { a.status = ActionStatusComplete } -func (a Action) setPanic() { - a.status = ActionStatusPanic +func (a Action) setError() { + a.status = ActionStatusError } type ActionStatus int @@ -53,8 +56,9 @@ type ActionStatus int const ( ActionStatusNew = iota ActionStatusPending + ActionStatusReceived ActionStatusComplete - ActionStatusPanic + ActionStatusError ) //---------------------------------------- @@ -62,6 +66,10 @@ const ( 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 } //---------------------------------------- @@ -70,46 +78,77 @@ type Actor interface { type Object interface { std.Addressable // Receive performs action on self. - // Panics if action is not permitted. - // NOTE: no return value, like a one way channel. - ReceiveFrom(*Action, Actor) + // Returns an error if action is not permitted. + ReceiveFrom(*Action, Actor) error } //---------------------------------------- // Main entry methods // Perform the action on the actor/object in directory. -// XXX but first, who composed the action? -// XXX only the actor should compose it. -// XXX but anyone can call this?! -func Perform(dir Directory, action Action) { +// 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.setPanic() + action.setError() panic(r) + } else if err != nil { + action.setError() } }() // Validate what can be validated. - action.ValidateBasic() + 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) - // - // XXX check that action is allowed. - // XXX what are the security assumptions? - // XXX what if someone unauthorized calls it? - // set actor - action.setActor(actor) - actor.WillPerform(action, object) - // XXX set status to pending (???) + // Ask Actor to verify action. + err = actor.CheckAction(action) + if err != nil { + return + } + // Set status as pending. action.setPending() - // XXX have object handle action. - // XXX what are the security assumptions? - // XXX what if someone unauthorized calls it? - object.ReceiveFrom(action, actor) - // XXX set status to com0lete. + // Let object handle action. + // (action.status == pending) + err = object.ReceiveFrom(action, actor) + if err != nil { + return + } + // (action.status == pending|received) + // Set status as complete. action.setComplete() } From ca9cc135d7a34aaeeb7b55824e244284c31fb0f8 Mon Sep 17 00:00:00 2001 From: jaekwon Date: Thu, 9 Nov 2023 01:38:45 -0800 Subject: [PATCH 22/27] ... --- examples/gno.land/p/jaekwon/book/action.gno | 32 ++++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/examples/gno.land/p/jaekwon/book/action.gno b/examples/gno.land/p/jaekwon/book/action.gno index c97b8de3eb5..c6e1cb8210e 100644 --- a/examples/gno.land/p/jaekwon/book/action.gno +++ b/examples/gno.land/p/jaekwon/book/action.gno @@ -25,29 +25,37 @@ type Action struct { 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() { +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) setComplete() { +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() { +func (a *Action) setError() { a.status = ActionStatusError } @@ -150,6 +158,8 @@ func Perform(dir Directory, action Action) (err error) { // (action.status == pending|received) // Set status as complete. action.setComplete() + + return nil } //---------------------------------------- From f640248f5da21cf1d6caa51f786df377dc94ff6e Mon Sep 17 00:00:00 2001 From: jaekwon Date: Thu, 9 Nov 2023 02:39:22 -0800 Subject: [PATCH 23/27] ... --- examples/gno.land/p/jaekwon/book/action.gno | 8 +- examples/gno.land/p/jaekwon/book/book.gno | 93 +++++---------------- 2 files changed, 28 insertions(+), 73 deletions(-) diff --git a/examples/gno.land/p/jaekwon/book/action.gno b/examples/gno.land/p/jaekwon/book/action.gno index c6e1cb8210e..5e7c0ba5009 100644 --- a/examples/gno.land/p/jaekwon/book/action.gno +++ b/examples/gno.land/p/jaekwon/book/action.gno @@ -87,7 +87,7 @@ type Object interface { std.Addressable // Receive performs action on self. // Returns an error if action is not permitted. - ReceiveFrom(*Action, Actor) error + ReceiveAction(*Action, Actor) error } //---------------------------------------- @@ -148,16 +148,18 @@ func Perform(dir Directory, action Action) (err error) { return } // Set status as pending. + // (action.status == new) action.setPending() - // Let object handle action. // (action.status == pending) - err = object.ReceiveFrom(action, actor) + // 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 } diff --git a/examples/gno.land/p/jaekwon/book/book.gno b/examples/gno.land/p/jaekwon/book/book.gno index 5eea94235d0..d817ac3e1d1 100644 --- a/examples/gno.land/p/jaekwon/book/book.gno +++ b/examples/gno.land/p/jaekwon/book/book.gno @@ -4,9 +4,13 @@ package book GOALS: -1. Create a Book (aka List) of text, +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 ...}. @@ -14,22 +18,6 @@ GOALS: */ // TODO use this for Tiers. -// XXX open questions -// * Do we want a directory? -// * Should action memoize object/subject/args? -// * Should args be interface{}? -// - NOTE: Amino *can* encode almost 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" @@ -41,46 +29,17 @@ func main() { 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.Get(action.object).Receive(action) - - // CASE 1B: ActionReceiveer with no signing - // This is just a wrapper around CASE 1A. - ar := ActionReceiveer(directory) - sar.ReceiveBy(action, privAccount) - // sar.ReceiveBy() 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.Get(action.object).Receive(action) - - // CASE 2B: SignedActionReceiveer - // SignedActionReceiveer embeds an ActionReceiveer. - sar := SignedActionReceiveer() - sar.Receive(signed) - // sar.Receive() impl: - // signed.subject.Authenticate(signed) - // sar.dir.GetObject(action.object).Receive(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 @@ -88,13 +47,6 @@ type Book struct { var _ Object = &Book{} -func (bk *Book) ReceiveFrom(action *Action, actor Actor) { - // XXX how does Book know - // XXX whether actor authenticated it? - // XXX - XXX -} - func (bk *Book) Append(XXX) XXX { XXX } @@ -110,25 +62,26 @@ func (bk *Book) Get(n int) XXX { //---------------------------------------- // PrivActionBook -// This is a thought experiment to make books work for auth. -// Actions are considered authorized if appended in 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. +// 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. +// to be authenticated in an PrivActionBook, because the +// test of authorization is merely inclusion. // -// XXX consider: +// TODO implement: // 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?? +// Also, PrivActionBook. +// Also, ReadActionBook. +// All of these are backed by the same underlying "book". type PrivActionBook struct { // All actions must have this object. From 8128c2a5a210bc50665f61421a76418673824319 Mon Sep 17 00:00:00 2001 From: jaekwon Date: Sun, 12 Nov 2023 19:33:03 -0800 Subject: [PATCH 24/27] added node.gno --- examples/gno.land/p/jaekwon/book/node.gno | 335 ++++++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 examples/gno.land/p/jaekwon/book/node.gno diff --git a/examples/gno.land/p/jaekwon/book/node.gno b/examples/gno.land/p/jaekwon/book/node.gno new file mode 100644 index 00000000000..b9f7d6af81d --- /dev/null +++ b/examples/gno.land/p/jaekwon/book/node.gno @@ -0,0 +1,335 @@ +package book + +//---------------------------------------- +// Node + +type Node struct { + value interface{} + height int8 + size int + leftNode *Node + rightNode *Node +} + +func NewNode(value interface{}) *Node { + return &Node{ + 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) Value() interface{} { + return node.value +} + +func (node *Node) _copy() *Node { + if node.height == 0 { + panic("Why are you copying a value node?") + } + return &Node{ + height: node.height, + size: node.size, + leftNode: node.leftNode, + rightNode: node.rightNode, + } +} + +func (node *Node) GetByIndex(index int) (value interface{}) { + if node.height == 0 { + if index == 0 { + return node.value + } else { + panic("GetByIndex with invalid index") + } + } 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) + } + } +} + +// like splicing in an element at index, shifting the prior element to the right. +// keeps the tree balanced. +func (node *Node) SetByIndex(index int, value interface{}) (newSelf *Node) { + if node == nil { + if index != 0 { + panic("SetByIndex with invalid index") + } + return NewNode(value), false + } + if node.height == 0 { + if index == 0 { + return &Node{ + height: 1, + size: 2, + leftNode: NewNode(value), + rightNode: node, + }, false + } else if index == 1 { + return &Node{ + height: 1, + size: 2, + leftNode: node, + rightNode: NewNode(value), + }, false + } else { + panic("SetByIndex with invalid index") + } + } else { + leftNode := node.getLeftNode() + if index < leftNode.size { + node.leftNode = leftNode.SetByIndex(index, value) + } else { + rightNode := node.getRightNode() + node.rightNode = rightNode.SetByIndex(index-leftNode.size, value) + } + node.calcHeightAndSize() + return node.balance() + } +} + +// newNode: The new node to replace node after remove. +func (node *Node) RemoveByIndex(index string) ( + newNode *Node, value interface{}, +) { + if node == nil { + panic("RemoveByIndex on empty tree") + } + if node.height == 0 { + if index == 0 { + return nil, node.value + } else { + panic("RemoveByIndex with invalid index") + } + } else { + leftNode := node.getLeftNode() + if index < leftNode.size { + var newLeftNode *Node + newLeftNode, value = leftNode.RemoveByIndex(index) + if newLeftNode == nil { // left node held value, was removed + return node.getRightNode(), value + } + node = node._copy() + node.leftNode = newLeftNode + node.calcHeightAndSize() + node = node.balance() + return node, value + } else { + rightNode := node.getRightNode() + var newRightNode *Node + newRightNode, value = rightNode.RemoveByIndex(index - leftNode.size) + if newRightNode == nil { // right node held value, was removed + return leftNode, value + } + node = node._copy() + node.rightNode = newRightNode + node.calcHeightAndSize() + node = node.balance() + return node, value + } + } +} + +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 +} + +// 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 +} From d9e235c528a85198ddfa9c88cacaad3a62d6074a Mon Sep 17 00:00:00 2001 From: jaekwon Date: Sun, 12 Nov 2023 19:36:17 -0800 Subject: [PATCH 25/27] add book node --- examples/gno.land/p/jaekwon/book/node.gno | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/examples/gno.land/p/jaekwon/book/node.gno b/examples/gno.land/p/jaekwon/book/node.gno index b9f7d6af81d..37efde065f3 100644 --- a/examples/gno.land/p/jaekwon/book/node.gno +++ b/examples/gno.land/p/jaekwon/book/node.gno @@ -1,5 +1,17 @@ package book +/* +A Book is composed of nodes that behave much like avl nodes, +except nodes do not have keys. + +Insertion, deletion, and query all have best and worst case log(n) time. This +makes it better than indexable skiplists in the presence of an adversarial user +(where all the state is visible to everyone). + +This structure like avl.Tree is not concurrency safe. For concurrency consider +implementing an immutable version (like the avl in Tendermint). +*/ + //---------------------------------------- // Node From 62b91966f653e22da36ec623bafd249ab50cd331 Mon Sep 17 00:00:00 2001 From: jaekwon Date: Sun, 12 Nov 2023 19:38:03 -0800 Subject: [PATCH 26/27] add references --- examples/gno.land/p/jaekwon/book/node.gno | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/gno.land/p/jaekwon/book/node.gno b/examples/gno.land/p/jaekwon/book/node.gno index 37efde065f3..958e47ce7f7 100644 --- a/examples/gno.land/p/jaekwon/book/node.gno +++ b/examples/gno.land/p/jaekwon/book/node.gno @@ -10,6 +10,12 @@ makes it better than indexable skiplists in the presence of an adversarial user This structure like avl.Tree is not concurrency safe. For concurrency consider implementing an immutable version (like the avl in Tendermint). + +Related reading: + * https://people.computing.clemson.edu/~bcdean/skip_bst.pdf + * https://people.computing.clemson.edu/~bcdean/skip_bst.pdf + * https://en.wikipedia.org/wiki/Skip_list + * https://stackoverflow.com/a/28270537 */ //---------------------------------------- From 9eaea3a8f23486237c1571c5c7eecc2cc4c4eff2 Mon Sep 17 00:00:00 2001 From: jaekwon Date: Wed, 3 Jan 2024 14:58:03 -0800 Subject: [PATCH 27/27] ... --- examples/gno.land/p/jaekwon/book/gno.mod | 1 + .../gno.land/p/jaekwon/book/node_test.gno | 100 +++++ examples/gno.land/p/jaekwon/book/tree.gno | 82 ++++ .../gno.land/p/jaekwon/book/z_0_filetest.gno | 348 ++++++++++++++++ .../gno.land/p/jaekwon/book/z_1_filetest.gno | 373 ++++++++++++++++++ .../gno.land/p/jaekwon/book/z_2_filetest.gno | 292 ++++++++++++++ 6 files changed, 1196 insertions(+) create mode 100644 examples/gno.land/p/jaekwon/book/gno.mod create mode 100644 examples/gno.land/p/jaekwon/book/node_test.gno create mode 100644 examples/gno.land/p/jaekwon/book/tree.gno create mode 100644 examples/gno.land/p/jaekwon/book/z_0_filetest.gno create mode 100644 examples/gno.land/p/jaekwon/book/z_1_filetest.gno create mode 100644 examples/gno.land/p/jaekwon/book/z_2_filetest.gno diff --git a/examples/gno.land/p/jaekwon/book/gno.mod b/examples/gno.land/p/jaekwon/book/gno.mod new file mode 100644 index 00000000000..f7687c49462 --- /dev/null +++ b/examples/gno.land/p/jaekwon/book/gno.mod @@ -0,0 +1 @@ +module gno.land/p/jaekwon/book diff --git a/examples/gno.land/p/jaekwon/book/node_test.gno b/examples/gno.land/p/jaekwon/book/node_test.gno new file mode 100644 index 00000000000..a42cb3a97a8 --- /dev/null +++ b/examples/gno.land/p/jaekwon/book/node_test.gno @@ -0,0 +1,100 @@ +package avl + +import ( + "sort" + "strings" + "testing" +) + +func TestTraverseByOffset(t *testing.T) { + const testStrings = `Alfa +Alfred +Alpha +Alphabet +Beta +Beth +Book +Browser` + tt := []struct { + name string + desc bool + }{ + {"ascending", false}, + {"descending", true}, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + sl := strings.Split(testStrings, "\n") + + // sort a first time in the order opposite to how we'll be traversing + // the tree, to ensure that we are not just iterating through with + // insertion order. + sort.Sort(sort.StringSlice(sl)) + if !tc.desc { + reverseSlice(sl) + } + + r := NewNode(sl[0], nil) + for _, v := range sl[1:] { + r, _ = r.Set(v, nil) + } + + // then sort sl in the order we'll be traversing it, so that we can + // compare the result with sl. + reverseSlice(sl) + + var result []string + for i := 0; i < len(sl); i++ { + r.TraverseByOffset(i, 1, tc.desc, true, func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + } + + if !slicesEqual(sl, result) { + t.Errorf("want %v got %v", sl, result) + } + + for l := 2; l <= len(sl); l++ { + // "slices" + for i := 0; i <= len(sl); i++ { + max := i + l + if max > len(sl) { + max = len(sl) + } + exp := sl[i:max] + actual := []string{} + + r.TraverseByOffset(i, l, tc.desc, true, func(tr *Node) bool { + actual = append(actual, tr.Key()) + return false + }) + // t.Log(exp, actual) + if !slicesEqual(exp, actual) { + t.Errorf("want %v got %v", exp, actual) + } + } + } + }) + } +} + +func slicesEqual(w1, w2 []string) bool { + if len(w1) != len(w2) { + return false + } + for i := 0; i < len(w1); i++ { + if w1[0] != w2[0] { + return false + } + } + return true +} + +func reverseSlice(ss []string) { + for i := 0; i < len(ss)/2; i++ { + j := len(ss) - 1 - i + ss[i], ss[j] = ss[j], ss[i] + } +} diff --git a/examples/gno.land/p/jaekwon/book/tree.gno b/examples/gno.land/p/jaekwon/book/tree.gno new file mode 100644 index 00000000000..7b33d28fbe3 --- /dev/null +++ b/examples/gno.land/p/jaekwon/book/tree.gno @@ -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/examples/gno.land/p/jaekwon/book/z_0_filetest.gno b/examples/gno.land/p/jaekwon/book/z_0_filetest.gno new file mode 100644 index 00000000000..814e19d6d49 --- /dev/null +++ b/examples/gno.land/p/jaekwon/book/z_0_filetest.gno @@ -0,0 +1,348 @@ +// PKGPATH: gno.land/r/test +package test + +import ( + "gno.land/p/demo/avl" +) + +var node *avl.Node + +func init() { + node = avl.NewNode("key0", "value0") + // node, _ = node.Set("key0", "value0") +} + +func main() { + var updated bool + node, updated = node.Set("key1", "value1") + // println(node, updated) + println(updated, node.Size()) +} + +// Output: +// false 2 + +// Realm: +// switchrealm["gno.land/r/test"] +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key0" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "value0" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "ModTime": "5", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key1" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "value1" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key1" +// } +// }, +// {}, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AgAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "091729e38bda8724bce4c314f9624b91af679459", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// } +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "0b5493aa4ea42087780bdfcaebab2c3eec351c15", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// } +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ +// "Blank": {}, +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "IsEscaped": true, +// "ModTime": "4", +// "RefCount": "2" +// }, +// "Parent": null, +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "", +// "Line": "0", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Values": [ +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "init.0", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "main.gno", +// "Line": "10", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "main", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "main.gno", +// "Line": "15", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "6c9948281d4c60b2d95233b76388d54d8b1a2fad", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// } +// } +// } +// } +// ] +// } diff --git a/examples/gno.land/p/jaekwon/book/z_1_filetest.gno b/examples/gno.land/p/jaekwon/book/z_1_filetest.gno new file mode 100644 index 00000000000..410e9e93601 --- /dev/null +++ b/examples/gno.land/p/jaekwon/book/z_1_filetest.gno @@ -0,0 +1,373 @@ +// PKGPATH: gno.land/r/test +package test + +import ( + "gno.land/p/demo/avl" +) + +var node *avl.Node + +func init() { + node = avl.NewNode("key0", "value0") + node, _ = node.Set("key1", "value1") +} + +func main() { + var updated bool + node, updated = node.Set("key2", "value2") + // println(node, updated) + println(updated, node.Size()) +} + +// Output: +// false 3 + +// Realm: +// switchrealm["gno.land/r/test"] +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key2" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "value2" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key2" +// } +// }, +// {}, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AgAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "7a8a63e17a567d7b0891ac89d5cd90072a73787d", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// } +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "ab5a297f4eb033d88bdf1677f4dc151ccb9fde9f", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// } +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key1" +// } +// }, +// {}, +// { +// "N": "AgAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AwAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "627e8e517e7ae5db0f3b753e2a32b607989198b6", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// } +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "fe8afd501233fb95375016199f0443b3c6ab1fbc", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// } +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ +// "Blank": {}, +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "IsEscaped": true, +// "ModTime": "6", +// "RefCount": "2" +// }, +// "Parent": null, +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "", +// "Line": "0", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Values": [ +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "init.0", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "main.gno", +// "Line": "10", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "main", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "main.gno", +// "Line": "15", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "c5eefc40ed065461b4a920c1349ed734ffdead8f", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } +// } +// } +// } +// ] +// } +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:4] diff --git a/examples/gno.land/p/jaekwon/book/z_2_filetest.gno b/examples/gno.land/p/jaekwon/book/z_2_filetest.gno new file mode 100644 index 00000000000..65181bffcac --- /dev/null +++ b/examples/gno.land/p/jaekwon/book/z_2_filetest.gno @@ -0,0 +1,292 @@ +// PKGPATH: gno.land/r/test +package test + +import ( + "gno.land/p/demo/avl" +) + +var tree avl.Tree + +func init() { + tree.Set("key0", "value0") + tree.Set("key1", "value1") +} + +func main() { + var updated bool + updated = tree.Set("key2", "value2") + println(updated, tree.Size()) +} + +// Output: +// false 3 + +// Realm: +// switchrealm["gno.land/r/test"] +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:10]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key2" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "value2" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key2" +// } +// }, +// {}, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AgAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "213baed7e3326f2403b5f30e5d4397510ba4f37d", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "be751422ef4c2bc068a456f9467d2daca27db8ca", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10" +// } +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key1" +// } +// }, +// {}, +// { +// "N": "AgAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AwAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "af4d0b158681d85eb2a7f6888b39a05ca7b790ee", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// } +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "ef853d70e334fd2c807d6c2c751da1fcd1e5ad58", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// } +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "RefCount": "1" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "3a5af0895c2c45b8a5e894644bcd689f1fdc4785", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// } +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "ModTime": "7", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" +// } +// } +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]