cleanup: bring the merkletrie in as trie.
This commit is contained in:
parent
ed4478bc72
commit
8e1ae77aad
14 changed files with 1147 additions and 20 deletions
30
claimtrie.go
30
claimtrie.go
|
@ -9,7 +9,7 @@ import (
|
||||||
"github.com/lbryio/claimtrie/claim"
|
"github.com/lbryio/claimtrie/claim"
|
||||||
"github.com/lbryio/claimtrie/claimnode"
|
"github.com/lbryio/claimtrie/claimnode"
|
||||||
|
|
||||||
"github.com/lbryio/merkletrie"
|
"github.com/lbryio/claimtrie/trie"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ClaimTrie implements a Merkle Trie supporting linear history of commits.
|
// ClaimTrie implements a Merkle Trie supporting linear history of commits.
|
||||||
|
@ -19,33 +19,33 @@ type ClaimTrie struct {
|
||||||
bestBlock claim.Height
|
bestBlock claim.Height
|
||||||
|
|
||||||
// Immutable linear history.
|
// Immutable linear history.
|
||||||
head *merkletrie.Commit
|
head *trie.Commit
|
||||||
|
|
||||||
// An overlay supporting Copy-on-Write to the current tip commit.
|
// An overlay supporting Copy-on-Write to the current tip commit.
|
||||||
stg *merkletrie.Stage
|
stg *trie.Stage
|
||||||
|
|
||||||
// pending keeps track update for future block height.
|
// pending keeps track update for future block height.
|
||||||
pending map[claim.Height][]string
|
pending map[claim.Height][]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitMeta implements merkletrie.CommitMeta with commit-specific metadata.
|
// CommitMeta implements trie.CommitMeta with commit-specific metadata.
|
||||||
type CommitMeta struct {
|
type CommitMeta struct {
|
||||||
Height claim.Height
|
Height claim.Height
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a ClaimTrie.
|
// New returns a ClaimTrie.
|
||||||
func New() *ClaimTrie {
|
func New() *ClaimTrie {
|
||||||
mt := merkletrie.New()
|
mt := trie.New()
|
||||||
return &ClaimTrie{
|
return &ClaimTrie{
|
||||||
head: merkletrie.NewCommit(nil, CommitMeta{0}, mt),
|
head: trie.NewCommit(nil, CommitMeta{0}, mt),
|
||||||
stg: merkletrie.NewStage(mt),
|
stg: trie.NewStage(mt),
|
||||||
pending: map[claim.Height][]string{},
|
pending: map[claim.Height][]string{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateStageNode(stg *merkletrie.Stage, name string, modifier func(n *claimnode.Node) error) error {
|
func updateStageNode(stg *trie.Stage, name string, modifier func(n *claimnode.Node) error) error {
|
||||||
v, err := stg.Get(merkletrie.Key(name))
|
v, err := stg.Get(trie.Key(name))
|
||||||
if err != nil && err != merkletrie.ErrKeyNotFound {
|
if err != nil && err != trie.ErrKeyNotFound {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var n *claimnode.Node
|
var n *claimnode.Node
|
||||||
|
@ -57,7 +57,7 @@ func updateStageNode(stg *merkletrie.Stage, name string, modifier func(n *claimn
|
||||||
if err = modifier(n); err != nil {
|
if err = modifier(n); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return stg.Update(merkletrie.Key(name), n)
|
return stg.Update(trie.Key(name), n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddClaim adds a Claim to the Stage of ClaimTrie.
|
// AddClaim adds a Claim to the Stage of ClaimTrie.
|
||||||
|
@ -102,7 +102,7 @@ func (ct *ClaimTrie) SpendSupport(name string, op wire.OutPoint) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traverse visits Nodes in the Stage of the ClaimTrie.
|
// Traverse visits Nodes in the Stage of the ClaimTrie.
|
||||||
func (ct *ClaimTrie) Traverse(visit merkletrie.Visit, update, valueOnly bool) error {
|
func (ct *ClaimTrie) Traverse(visit trie.Visit, update, valueOnly bool) error {
|
||||||
return ct.stg.Traverse(visit, update, valueOnly)
|
return ct.stg.Traverse(visit, update, valueOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ func (ct *ClaimTrie) Commit(h claim.Height) error {
|
||||||
|
|
||||||
// Reset reverts the Stage to a specified commit by height.
|
// Reset reverts the Stage to a specified commit by height.
|
||||||
func (ct *ClaimTrie) Reset(h claim.Height) error {
|
func (ct *ClaimTrie) Reset(h claim.Height) error {
|
||||||
visit := func(prefix merkletrie.Key, value merkletrie.Value) error {
|
visit := func(prefix trie.Key, value trie.Value) error {
|
||||||
n := value.(*claimnode.Node)
|
n := value.(*claimnode.Node)
|
||||||
return n.DecrementBlock(n.Height() - claim.Height(h))
|
return n.DecrementBlock(n.Height() - claim.Height(h))
|
||||||
}
|
}
|
||||||
|
@ -169,7 +169,7 @@ func (ct *ClaimTrie) Reset(h claim.Height) error {
|
||||||
if meta.Height <= h {
|
if meta.Height <= h {
|
||||||
ct.head = commit
|
ct.head = commit
|
||||||
ct.bestBlock = h
|
ct.bestBlock = h
|
||||||
ct.stg = merkletrie.NewStage(commit.MerkleTrie)
|
ct.stg = trie.NewStage(commit.MerkleTrie)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,6 +177,6 @@ func (ct *ClaimTrie) Reset(h claim.Height) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Head returns the current tip commit in the commit database.
|
// Head returns the current tip commit in the commit database.
|
||||||
func (ct *ClaimTrie) Head() *merkletrie.Commit {
|
func (ct *ClaimTrie) Head() *trie.Commit {
|
||||||
return ct.head
|
return ct.head
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,7 @@ import (
|
||||||
|
|
||||||
"github.com/lbryio/claimtrie"
|
"github.com/lbryio/claimtrie"
|
||||||
"github.com/lbryio/claimtrie/claim"
|
"github.com/lbryio/claimtrie/claim"
|
||||||
|
"github.com/lbryio/claimtrie/trie"
|
||||||
"github.com/lbryio/merkletrie"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -211,7 +210,7 @@ func cmdSpendSupport(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdShow(c *cli.Context) error {
|
func cmdShow(c *cli.Context) error {
|
||||||
dump := func(prefix merkletrie.Key, val merkletrie.Value) error {
|
dump := func(prefix trie.Key, val trie.Value) error {
|
||||||
if val == nil {
|
if val == nil {
|
||||||
fmt.Printf("%-8s:\n", prefix)
|
fmt.Printf("%-8s:\n", prefix)
|
||||||
return nil
|
return nil
|
||||||
|
@ -255,13 +254,13 @@ func cmdReset(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdLog(c *cli.Context) error {
|
func cmdLog(c *cli.Context) error {
|
||||||
commitVisit := func(c *merkletrie.Commit) {
|
commitVisit := func(c *trie.Commit) {
|
||||||
meta := c.Meta.(claimtrie.CommitMeta)
|
meta := c.Meta.(claimtrie.CommitMeta)
|
||||||
fmt.Printf("height: %d, commit %s\n", meta.Height, c.MerkleTrie.MerkleHash())
|
fmt.Printf("height: %d, commit %s\n", meta.Height, c.MerkleTrie.MerkleHash())
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
merkletrie.Log(ct.Head(), commitVisit)
|
trie.Log(ct.Head(), commitVisit)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
47
trie/README.md
Normal file
47
trie/README.md
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
# MerkleTrie
|
||||||
|
|
||||||
|
coming soon
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
coming soon
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
coming soon
|
||||||
|
|
||||||
|
## Running from Source
|
||||||
|
|
||||||
|
This project requires [Go v1.10](https://golang.org/doc/install) or higher.
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
go get -v github.com/lbryio/trie
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Refer to [triesh](https://github.com/lbryio/trie/blob/master/cmd/triesh)
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
go test -v github.com/lbryio/trie
|
||||||
|
gocov test -v github.com/lbryio/trie 1>/dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
coming soon
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is MIT licensed.
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
We take security seriously. Please contact security@lbry.io regarding any security issues.
|
||||||
|
Our PGP key is [here](https://keybase.io/lbry/key.asc) if you need it.
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
The primary contact for this project is [@lyoshenka](https://github.com/lyoshenka) (grin@lbry.io)
|
171
trie/cmd/triesh/README.md
Normal file
171
trie/cmd/triesh/README.md
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
# Triesh
|
||||||
|
|
||||||
|
An example Key-Value store to excercise the merkletree package
|
||||||
|
|
||||||
|
Currently, it's only in-memory.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
This project requires [Go v1.10](https://golang.org/doc/install) or higher.
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
go get -v github.com/lbryio/trie
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Adding values.
|
||||||
|
|
||||||
|
``` bloocks
|
||||||
|
triesh > u -k alex -v lion
|
||||||
|
alex=lion
|
||||||
|
triesh > u -k al -v tiger
|
||||||
|
al=tiger
|
||||||
|
triesh > u -k tess -v dolphin
|
||||||
|
tess=dolphin
|
||||||
|
triesh > u -k bob -v pig
|
||||||
|
bob=pig
|
||||||
|
triesh > u -k ted -v do
|
||||||
|
ted=do
|
||||||
|
triesh > u -k ted -v dog
|
||||||
|
ted=dog
|
||||||
|
```
|
||||||
|
|
||||||
|
Showing Merkle Hash.
|
||||||
|
|
||||||
|
``` blocks
|
||||||
|
triesh > merkle
|
||||||
|
bfa2927b147161146411b7f6187e1ed0c08c3dc19b200550c3458d44c0032285
|
||||||
|
|
||||||
|
triesh > u -k teddy -v bear
|
||||||
|
teddy=bear
|
||||||
|
|
||||||
|
triesh > merkle
|
||||||
|
94831650b8bf76d579ca4eda1cb35861c6f5c88eb4f5b089f60fe687defe8f3d
|
||||||
|
```
|
||||||
|
|
||||||
|
Showing all values.
|
||||||
|
|
||||||
|
``` blocks
|
||||||
|
triesh > s
|
||||||
|
[al ] tiger
|
||||||
|
[alex ] lion
|
||||||
|
[bob ] pig
|
||||||
|
[ted ] dog
|
||||||
|
[teddy ] bear
|
||||||
|
[tess ] dolphin
|
||||||
|
```
|
||||||
|
|
||||||
|
Showing all values and link nodes.
|
||||||
|
|
||||||
|
``` bloocks
|
||||||
|
triesh > s -a
|
||||||
|
[a ]
|
||||||
|
[al ] tiger
|
||||||
|
[ale ]
|
||||||
|
[alex ] lion
|
||||||
|
[b ]
|
||||||
|
[bo ]
|
||||||
|
[bob ] pig
|
||||||
|
[t ]
|
||||||
|
[te ]
|
||||||
|
[ted ] dog
|
||||||
|
[tedd ]
|
||||||
|
[teddy ] bear
|
||||||
|
[tes ]
|
||||||
|
[tess ] dolphin
|
||||||
|
```
|
||||||
|
|
||||||
|
Deleting values (setting key to nil / "").
|
||||||
|
|
||||||
|
``` blocks
|
||||||
|
triesh > u -k al
|
||||||
|
al=
|
||||||
|
triesh > u -k alex
|
||||||
|
alex=
|
||||||
|
```
|
||||||
|
|
||||||
|
Updating Values.
|
||||||
|
|
||||||
|
``` blocks
|
||||||
|
triesh > u -k bob -v cat
|
||||||
|
bob=cat
|
||||||
|
```
|
||||||
|
|
||||||
|
Showing all nodes, include non-pruned link nodes"
|
||||||
|
|
||||||
|
``` blocks
|
||||||
|
triesh > s -a
|
||||||
|
[a ]
|
||||||
|
[al ]
|
||||||
|
[ale ]
|
||||||
|
[alex ]
|
||||||
|
[b ]
|
||||||
|
[bo ]
|
||||||
|
[bob ] cat
|
||||||
|
[t ]
|
||||||
|
[te ]
|
||||||
|
[ted ] dog
|
||||||
|
[tedd ]
|
||||||
|
[teddy ] bear
|
||||||
|
[tes ]
|
||||||
|
[tess ] dolphin
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Calculate Merkle Hash.
|
||||||
|
|
||||||
|
``` blocks
|
||||||
|
triesh > merkle
|
||||||
|
c2fdce68a30e3cabf6efb3b7ebfd32afdaf09f9ebd062743fe91e181f682252b
|
||||||
|
```
|
||||||
|
|
||||||
|
Prune link nodes that do not reach to any values.
|
||||||
|
|
||||||
|
``` blocks
|
||||||
|
triesh > p
|
||||||
|
pruned
|
||||||
|
```
|
||||||
|
|
||||||
|
Show pruned Trie and caculate the Merkle Hash again.
|
||||||
|
|
||||||
|
``` blocks
|
||||||
|
triesh > s -a
|
||||||
|
[b ]
|
||||||
|
[bo ]
|
||||||
|
[bob ] cat
|
||||||
|
[t ]
|
||||||
|
[te ]
|
||||||
|
[ted ] dog
|
||||||
|
[tedd ]
|
||||||
|
[teddy ] bear
|
||||||
|
[tes ]
|
||||||
|
[tess ] dolphin
|
||||||
|
|
||||||
|
triesh > merkle
|
||||||
|
c2fdce68a30e3cabf6efb3b7ebfd32afdaf09f9ebd062743fe91e181f682252b
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running from Source
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
cd $(go env GOPATH)/src/github.com/lbryio/trie
|
||||||
|
go run cmd/triesh/*.go sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
coming soon
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is MIT licensed.
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
We take security seriously. Please contact security@lbry.io regarding any security issues.
|
||||||
|
Our PGP key is [here](https://keybase.io/lbry/key.asc) if you need it.
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
The primary contact for this project is [@lyoshenka](https://github.com/lyoshenka) (grin@lbry.io)
|
242
trie/cmd/triesh/main.go
Normal file
242
trie/cmd/triesh/main.go
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/lbryio/claimtrie/trie"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
flgKey = cli.StringFlag{Name: "key, k", Usage: "Key"}
|
||||||
|
flgValue = cli.StringFlag{Name: "value, v", Usage: "Value"}
|
||||||
|
flgAll = cli.BoolFlag{Name: "all, a", Usage: "Apply to non-value nodes"}
|
||||||
|
flgMessage = cli.StringFlag{Name: "message, m", Usage: "Commit Message"}
|
||||||
|
flgID = cli.StringFlag{Name: "id", Usage: "Commit ID"}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrNotImplemented is returned when a function is not implemented yet.
|
||||||
|
ErrNotImplemented = fmt.Errorf("not implemented")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := cli.NewApp()
|
||||||
|
|
||||||
|
app.Name = "triesh"
|
||||||
|
app.Usage = "A CLI tool for Merkle MerkleTrie"
|
||||||
|
app.Version = "0.0.1"
|
||||||
|
app.Action = cli.ShowAppHelp
|
||||||
|
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "update",
|
||||||
|
Aliases: []string{"u"},
|
||||||
|
Usage: "Update Value for Key",
|
||||||
|
Action: cmdUpdate,
|
||||||
|
Flags: []cli.Flag{flgKey, flgValue},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "get",
|
||||||
|
Aliases: []string{"g"},
|
||||||
|
Usage: "Get Value for specified Key",
|
||||||
|
Action: cmdGet,
|
||||||
|
Flags: []cli.Flag{flgKey, flgID},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "show",
|
||||||
|
Aliases: []string{"s"},
|
||||||
|
Usage: "Show Key-Value pairs of specified commit",
|
||||||
|
Action: cmdShow,
|
||||||
|
Flags: []cli.Flag{flgAll, flgID},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "merkle",
|
||||||
|
Aliases: []string{"m"},
|
||||||
|
Usage: "Show Merkle Hash of stage",
|
||||||
|
Action: cmdMerkle,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "prune",
|
||||||
|
Aliases: []string{"p"},
|
||||||
|
Usage: "Prune link nodes that doesn't reach to any value",
|
||||||
|
Action: cmdPrune,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "commit",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Usage: "Commit current stage to database",
|
||||||
|
Action: cmdCommit,
|
||||||
|
Flags: []cli.Flag{flgMessage},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "reset",
|
||||||
|
Aliases: []string{"r"},
|
||||||
|
Usage: "Reset HEAD & Stage to specified commit",
|
||||||
|
Action: cmdReset,
|
||||||
|
Flags: []cli.Flag{flgAll, flgID},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "log",
|
||||||
|
Aliases: []string{"l"},
|
||||||
|
Usage: "Show commit logs",
|
||||||
|
Action: cmdLog,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "shell",
|
||||||
|
Aliases: []string{"sh"},
|
||||||
|
Usage: "Enter interactive mode",
|
||||||
|
Action: func(c *cli.Context) { cmdShell(app) },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := app.Run(os.Args); err != nil {
|
||||||
|
fmt.Printf("error: %s\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type strValue string
|
||||||
|
|
||||||
|
func (s strValue) Hash() chainhash.Hash {
|
||||||
|
return chainhash.DoubleHashH([]byte(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
mt = trie.New()
|
||||||
|
head = trie.NewCommit(nil, "initial", mt)
|
||||||
|
stg = trie.NewStage(mt)
|
||||||
|
)
|
||||||
|
|
||||||
|
func commitVisit(c *trie.Commit) {
|
||||||
|
fmt.Printf("commit %s\n\n", c.MerkleTrie.MerkleHash())
|
||||||
|
fmt.Printf("\t%s\n\n", c.Meta.(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdUpdate(c *cli.Context) error {
|
||||||
|
key, value := c.String("key"), c.String("value")
|
||||||
|
fmt.Printf("%s=%s\n", key, value)
|
||||||
|
if len(value) == 0 {
|
||||||
|
return stg.Update(trie.Key(key), nil)
|
||||||
|
}
|
||||||
|
return stg.Update(trie.Key(key), strValue(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdGet(c *cli.Context) error {
|
||||||
|
key := c.String("key")
|
||||||
|
value, err := stg.Get(trie.Key(key))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if str, ok := value.(strValue); ok {
|
||||||
|
fmt.Printf("[%s]\n", str)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdShow(c *cli.Context) error {
|
||||||
|
dump := func(prefix trie.Key, val trie.Value) error {
|
||||||
|
if val == nil {
|
||||||
|
fmt.Printf("[%-8s]\n", prefix)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fmt.Printf("[%-8s] %v\n", prefix, val)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
id := c.String("id")
|
||||||
|
if len(id) == 0 {
|
||||||
|
return stg.Traverse(dump, false, !c.Bool("all"))
|
||||||
|
}
|
||||||
|
for commit := head; commit != nil; commit = commit.Prev {
|
||||||
|
if commit.MerkleTrie.MerkleHash().String() == id {
|
||||||
|
return commit.MerkleTrie.Traverse(dump, false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return fmt.Errorf("commit noot found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdMerkle(c *cli.Context) error {
|
||||||
|
fmt.Printf("%s\n", stg.MerkleHash())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdPrune(c *cli.Context) error {
|
||||||
|
stg.Prune()
|
||||||
|
fmt.Printf("pruned\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdCommit(c *cli.Context) error {
|
||||||
|
msg := c.String("message")
|
||||||
|
if len(msg) == 0 {
|
||||||
|
return fmt.Errorf("no message specified")
|
||||||
|
}
|
||||||
|
h, err := stg.Commit(head, msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
head = h
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdReset(c *cli.Context) error {
|
||||||
|
id := c.String("id")
|
||||||
|
for commit := head; commit != nil; commit = commit.Prev {
|
||||||
|
if commit.MerkleTrie.MerkleHash().String() != id {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
head = commit
|
||||||
|
stg = trie.NewStage(head.MerkleTrie)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("commit noot found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdLog(c *cli.Context) error {
|
||||||
|
commitVisit := func(c *trie.Commit) {
|
||||||
|
fmt.Printf("commit %s\n\n", c.MerkleTrie.MerkleHash())
|
||||||
|
fmt.Printf("\t%s\n\n", c.Meta.(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
trie.Log(head, commitVisit)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdShell(app *cli.App) {
|
||||||
|
cli.OsExiter = func(c int) {}
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
sigs := make(chan os.Signal, 1)
|
||||||
|
go func() {
|
||||||
|
for range sigs {
|
||||||
|
fmt.Printf("\n(type quit or q to exit)\n\n")
|
||||||
|
fmt.Printf("%s > ", app.Name)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer close(sigs)
|
||||||
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
for {
|
||||||
|
fmt.Printf("%s > ", app.Name)
|
||||||
|
text, err := reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %s\n", err)
|
||||||
|
}
|
||||||
|
text = strings.TrimSpace(text)
|
||||||
|
if text == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if text == "quit" || text == "q" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err := app.Run(append(os.Args[1:], strings.Split(text, " ")...)); err != nil {
|
||||||
|
fmt.Printf("errot: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
signal.Stop(sigs)
|
||||||
|
}
|
21
trie/commit.go
Normal file
21
trie/commit.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package trie
|
||||||
|
|
||||||
|
// CommitMeta ...
|
||||||
|
type CommitMeta interface{}
|
||||||
|
|
||||||
|
// NewCommit ...
|
||||||
|
func NewCommit(head *Commit, meta CommitMeta, mt *MerkleTrie) *Commit {
|
||||||
|
commit := &Commit{
|
||||||
|
Prev: head,
|
||||||
|
MerkleTrie: mt,
|
||||||
|
Meta: meta,
|
||||||
|
}
|
||||||
|
return commit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit ...
|
||||||
|
type Commit struct {
|
||||||
|
Prev *Commit
|
||||||
|
MerkleTrie *MerkleTrie
|
||||||
|
Meta CommitMeta
|
||||||
|
}
|
8
trie/errors.go
Normal file
8
trie/errors.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package trie
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrKeyNotFound is returned when the key doesn't exist in the MerkleTrie.
|
||||||
|
ErrKeyNotFound = errors.New("key not found")
|
||||||
|
)
|
96
trie/node.go
Normal file
96
trie/node.go
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package trie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
)
|
||||||
|
|
||||||
|
type node struct {
|
||||||
|
hash *chainhash.Hash
|
||||||
|
links [256]*node
|
||||||
|
value Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNode(val Value) *node {
|
||||||
|
return &node{links: [256]*node{}, value: val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We clear the Merkle Hash for every node along the path, including the root.
|
||||||
|
// Calculation of the hash happens much less frequently then updating to the MerkleTrie.
|
||||||
|
func update(n *node, key Key, val Value) {
|
||||||
|
// Follow the path to reach the node.
|
||||||
|
for _, k := range key {
|
||||||
|
if n.links[k] == nil {
|
||||||
|
// The path didn't exist yet. Build it.
|
||||||
|
n.links[k] = newNode(nil)
|
||||||
|
}
|
||||||
|
n.hash = nil
|
||||||
|
n = n.links[k]
|
||||||
|
}
|
||||||
|
|
||||||
|
n.value = val
|
||||||
|
n.hash = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func prune(n *node) *node {
|
||||||
|
if n == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var ret *node
|
||||||
|
for i, v := range n.links {
|
||||||
|
if n.links[i] = prune(v); n.links[i] != nil {
|
||||||
|
ret = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n.value != nil {
|
||||||
|
ret = n
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func traverse(n *node, prefix Key, visit Visit) error {
|
||||||
|
if n == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for i, v := range n.links {
|
||||||
|
if v == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p := append(prefix, byte(i))
|
||||||
|
if err := visit(p, v.value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := traverse(v, p, visit); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// merkle recursively caculates the Merkle Hash of a given node
|
||||||
|
// It works with both pruned or unpruned nodes.
|
||||||
|
func merkle(n *node) *chainhash.Hash {
|
||||||
|
if n.hash != nil {
|
||||||
|
return n.hash
|
||||||
|
}
|
||||||
|
buf := Key{}
|
||||||
|
for i, v := range n.links {
|
||||||
|
if v == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if h := merkle(v); h != nil {
|
||||||
|
buf = append(buf, byte(i))
|
||||||
|
buf = append(buf, h[:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n.value != nil {
|
||||||
|
h := n.value.Hash()
|
||||||
|
buf = append(buf, h[:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(buf) != 0 {
|
||||||
|
// At least one of the sub nodes has contributed a value hash.
|
||||||
|
h := chainhash.DoubleHashH(buf)
|
||||||
|
n.hash = &h
|
||||||
|
}
|
||||||
|
return n.hash
|
||||||
|
}
|
139
trie/node_test.go
Normal file
139
trie/node_test.go
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
package trie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_update(t *testing.T) {
|
||||||
|
res1 := buildNode(newNode(nil), pairs1())
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
res *node
|
||||||
|
exp *node
|
||||||
|
}{
|
||||||
|
{"test1", res1, unprunedNode()},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if !reflect.DeepEqual(tt.res, tt.exp) {
|
||||||
|
traverse(tt.res, Key{}, dump)
|
||||||
|
fmt.Println("")
|
||||||
|
traverse(tt.exp, Key{}, dump)
|
||||||
|
t.Errorf("update() = %v, want %v", tt.res, tt.exp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_nullify(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
res *node
|
||||||
|
exp *node
|
||||||
|
}{
|
||||||
|
{"test1", prune(unprunedNode()), prunedNode()},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if !reflect.DeepEqual(tt.res, tt.exp) {
|
||||||
|
t.Errorf("traverse() = %v, want %v", tt.res, tt.exp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_traverse(t *testing.T) {
|
||||||
|
res1 := []pair{}
|
||||||
|
fn := func(prefix Key, value Value) error {
|
||||||
|
res1 = append(res1, pair{string(prefix), value})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
traverse(unprunedNode(), Key{}, fn)
|
||||||
|
exp1 := []pair{
|
||||||
|
{"a", nil},
|
||||||
|
{"al", nil},
|
||||||
|
{"ale", nil},
|
||||||
|
{"alex", nil},
|
||||||
|
{"b", nil},
|
||||||
|
{"bo", nil},
|
||||||
|
{"bob", strValue("cat")},
|
||||||
|
{"t", nil},
|
||||||
|
{"te", nil},
|
||||||
|
{"ted", strValue("dog")},
|
||||||
|
{"tedd", nil},
|
||||||
|
{"teddy", strValue("bear")},
|
||||||
|
{"tes", nil},
|
||||||
|
{"tess", strValue("dolphin")},
|
||||||
|
}
|
||||||
|
|
||||||
|
res2 := []pair{}
|
||||||
|
fn2 := func(prefix Key, value Value) error {
|
||||||
|
res2 = append(res2, pair{string(prefix), value})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
traverse(prunedNode(), Key{}, fn2)
|
||||||
|
exp2 := []pair{
|
||||||
|
{"b", nil},
|
||||||
|
{"bo", nil},
|
||||||
|
{"bob", strValue("cat")},
|
||||||
|
{"t", nil},
|
||||||
|
{"te", nil},
|
||||||
|
{"ted", strValue("dog")},
|
||||||
|
{"tedd", nil},
|
||||||
|
{"teddy", strValue("bear")},
|
||||||
|
{"tes", nil},
|
||||||
|
{"tess", strValue("dolphin")},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
res []pair
|
||||||
|
exp []pair
|
||||||
|
}{
|
||||||
|
{"test1", res1, exp1},
|
||||||
|
{"test2", res2, exp2},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if !reflect.DeepEqual(tt.res, tt.exp) {
|
||||||
|
t.Errorf("traverse() = %v, want %v", tt.res, tt.exp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_merkle(t *testing.T) {
|
||||||
|
n1 := buildNode(newNode(nil), pairs1())
|
||||||
|
// n2 := func() *node {
|
||||||
|
// p1 := wire.OutPoint{Hash: *newHashFromStr("627ecfee2110b28fbc4b012944cadf66a72f394ad9fa9bb18fec30789e26c9ac"), Index: 0}
|
||||||
|
// p2 := wire.OutPoint{Hash: *newHashFromStr("c31bd469112abf04930879c6b6007d2b23224e042785d404bbeff1932dd94880"), Index: 0}
|
||||||
|
|
||||||
|
// n1 := claim.NewNode(&claim.Claim{OutPoint: p1, ClaimID: nil, Amount: 50, Height: 100, ValidAtHeight: 200})
|
||||||
|
// n2 := claim.NewNode(&claim.Claim{OutPoint: p2, ClaimID: nil, Amount: 50, Height: 100, ValidAtHeight: 200})
|
||||||
|
|
||||||
|
// pairs := []pair{
|
||||||
|
// {"test", n1},
|
||||||
|
// {"test2", n2},
|
||||||
|
// }
|
||||||
|
// return buildNode(newNode(nil), pairs)
|
||||||
|
// }()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
n *node
|
||||||
|
want *chainhash.Hash
|
||||||
|
}{
|
||||||
|
{"test1", n1, newHashFromStr("c2fdce68a30e3cabf6efb3b7ebfd32afdaf09f9ebd062743fe91e181f682252b")},
|
||||||
|
// {"test2", n2, newHashFromStr("71c7b8d35b9a3d7ad9a1272b68972979bbd18589f1efe6f27b0bf260a6ba78fa")},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := merkle(tt.n); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("merkle() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
61
trie/stage.go
Normal file
61
trie/stage.go
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package trie
|
||||||
|
|
||||||
|
// Stage implements Copy-on-Write staging area on top of a MerkleTrie.
|
||||||
|
type Stage struct {
|
||||||
|
*MerkleTrie
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStage returns a Stage initialized with a specified MerkleTrie.
|
||||||
|
func NewStage(t *MerkleTrie) *Stage {
|
||||||
|
s := &Stage{
|
||||||
|
MerkleTrie: New(),
|
||||||
|
}
|
||||||
|
s.mu = t.mu
|
||||||
|
s.root = newNode(nil)
|
||||||
|
*s.root = *t.root
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates the internal MerkleTrie in a Copy-on-Write manner.
|
||||||
|
func (s *Stage) Update(key Key, val Value) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
n := s.root
|
||||||
|
for _, k := range key {
|
||||||
|
org := n.links[k]
|
||||||
|
n.links[k] = newNode(nil)
|
||||||
|
if org != nil {
|
||||||
|
*n.links[k] = *org
|
||||||
|
}
|
||||||
|
n.hash = nil
|
||||||
|
n = n.links[k]
|
||||||
|
}
|
||||||
|
if n.value != val {
|
||||||
|
n.value = val
|
||||||
|
n.hash = nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit ...
|
||||||
|
func (s *Stage) Commit(head *Commit, meta CommitMeta) (*Commit, error) {
|
||||||
|
c := NewCommit(head, meta, s.MerkleTrie)
|
||||||
|
|
||||||
|
s.MerkleTrie = New()
|
||||||
|
s.mu = c.MerkleTrie.mu
|
||||||
|
s.root = newNode(nil)
|
||||||
|
*s.root = *c.MerkleTrie.root
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitVisit ...
|
||||||
|
type CommitVisit func(c *Commit)
|
||||||
|
|
||||||
|
// Log ...
|
||||||
|
func Log(commit *Commit, visit CommitVisit) {
|
||||||
|
for commit != nil {
|
||||||
|
visit(commit)
|
||||||
|
commit = commit.Prev
|
||||||
|
}
|
||||||
|
}
|
34
trie/stage_test.go
Normal file
34
trie/stage_test.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package trie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStage_Update(t *testing.T) {
|
||||||
|
tr1 := buildTrie(New(), pairs1())
|
||||||
|
|
||||||
|
s1 := NewStage(tr1)
|
||||||
|
s1.Update(Key("cook"), strValue("hello"))
|
||||||
|
s1.Update(Key("ted"), nil)
|
||||||
|
|
||||||
|
tr1Exp := buildTrie(New(), pairs1())
|
||||||
|
|
||||||
|
s1Exp := buildTrie(New(), pairs1())
|
||||||
|
s1Exp.Update(Key("cook"), strValue("hello"))
|
||||||
|
s1Exp.Update(Key("ted"), nil)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(tr1, tr1Exp) {
|
||||||
|
t.Errorf("Stage.Update() tr1 != tr1Exp")
|
||||||
|
traverse(tr1.root, Key{}, dump)
|
||||||
|
fmt.Println("")
|
||||||
|
traverse(tr1Exp.root, Key{}, dump)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(s1.MerkleTrie, s1Exp) {
|
||||||
|
t.Errorf("Stage.Update() s1 != s1Exp")
|
||||||
|
traverse(s1.root, Key{}, dump)
|
||||||
|
fmt.Println("")
|
||||||
|
traverse(s1Exp.root, Key{}, dump)
|
||||||
|
}
|
||||||
|
}
|
106
trie/test.go
Normal file
106
trie/test.go
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package trie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Internal utility functions to facilitate the tests.
|
||||||
|
|
||||||
|
type strValue string
|
||||||
|
|
||||||
|
func (s strValue) Hash() chainhash.Hash {
|
||||||
|
return chainhash.DoubleHashH([]byte(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func dump(prefix Key, value Value) error {
|
||||||
|
if value == nil {
|
||||||
|
fmt.Printf("[%-8s]\n", prefix)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fmt.Printf("[%-8s] %v\n", prefix, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildNode(n *node, pairs []pair) *node {
|
||||||
|
for _, val := range pairs {
|
||||||
|
update(n, Key(val.k), val.v)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildTrie(mt *MerkleTrie, pairs []pair) *MerkleTrie {
|
||||||
|
for _, val := range pairs {
|
||||||
|
mt.Update(Key(val.k), val.v)
|
||||||
|
}
|
||||||
|
return mt
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildMap(m map[string]Value, pairs []pair) map[string]Value {
|
||||||
|
for _, p := range pairs {
|
||||||
|
if p.v == nil {
|
||||||
|
delete(m, p.k)
|
||||||
|
} else {
|
||||||
|
m[p.k] = p.v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMap() map[string]Value {
|
||||||
|
return map[string]Value{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type pair struct {
|
||||||
|
k string
|
||||||
|
v Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func pairs1() []pair {
|
||||||
|
return []pair{
|
||||||
|
{"alex", strValue("lion")},
|
||||||
|
{"al", strValue("tiger")},
|
||||||
|
{"tess", strValue("dolphin")},
|
||||||
|
{"bob", strValue("pig")},
|
||||||
|
{"ted", strValue("dog")},
|
||||||
|
{"teddy", strValue("bear")},
|
||||||
|
{"al", nil},
|
||||||
|
{"alex", nil},
|
||||||
|
{"bob", strValue("cat")},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func prunedNode() *node {
|
||||||
|
n := newNode(nil)
|
||||||
|
n.links['b'] = newNode(nil)
|
||||||
|
n.links['b'].links['o'] = newNode(nil)
|
||||||
|
n.links['b'].links['o'].links['b'] = newNode(strValue("cat"))
|
||||||
|
n.links['t'] = newNode(nil)
|
||||||
|
n.links['t'].links['e'] = newNode(nil)
|
||||||
|
n.links['t'].links['e'].links['d'] = newNode(strValue("dog"))
|
||||||
|
n.links['t'].links['e'].links['d'].links['d'] = newNode(nil)
|
||||||
|
n.links['t'].links['e'].links['d'].links['d'].links['y'] = newNode(strValue("bear"))
|
||||||
|
n.links['t'].links['e'].links['s'] = newNode(nil)
|
||||||
|
n.links['t'].links['e'].links['s'].links['s'] = newNode(strValue("dolphin"))
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func unprunedNode() *node {
|
||||||
|
n := newNode(nil)
|
||||||
|
n.links['a'] = newNode(nil)
|
||||||
|
n.links['a'].links['l'] = newNode(nil)
|
||||||
|
n.links['a'].links['l'].links['e'] = newNode(nil)
|
||||||
|
n.links['a'].links['l'].links['e'].links['x'] = newNode(nil)
|
||||||
|
n.links['b'] = newNode(nil)
|
||||||
|
n.links['b'].links['o'] = newNode(nil)
|
||||||
|
n.links['b'].links['o'].links['b'] = newNode(strValue("cat"))
|
||||||
|
n.links['t'] = newNode(nil)
|
||||||
|
n.links['t'].links['e'] = newNode(nil)
|
||||||
|
n.links['t'].links['e'].links['d'] = newNode(strValue("dog"))
|
||||||
|
n.links['t'].links['e'].links['d'].links['d'] = newNode(nil)
|
||||||
|
n.links['t'].links['e'].links['d'].links['d'].links['y'] = newNode(strValue("bear"))
|
||||||
|
n.links['t'].links['e'].links['s'] = newNode(nil)
|
||||||
|
n.links['t'].links['e'].links['s'].links['s'] = newNode(strValue("dolphin"))
|
||||||
|
return n
|
||||||
|
}
|
128
trie/trie.go
Normal file
128
trie/trie.go
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
package trie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// EmptyTrieHash represent the Merkle Hash of an empty MerkleTrie.
|
||||||
|
EmptyTrieHash = *newHashFromStr("0000000000000000000000000000000000000000000000000000000000000001")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key defines the key type of the MerkleTrie.
|
||||||
|
type Key []byte
|
||||||
|
|
||||||
|
// Value implements value for the MerkleTrie.
|
||||||
|
type Value interface {
|
||||||
|
Hash() chainhash.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// MerkleTrie implements a 256-way prefix tree, which takes Key as key and any value that implements the Value interface.
|
||||||
|
type MerkleTrie struct {
|
||||||
|
mu *sync.RWMutex
|
||||||
|
root *node
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a MerkleTrie.
|
||||||
|
func New() *MerkleTrie {
|
||||||
|
return &MerkleTrie{
|
||||||
|
mu: &sync.RWMutex{},
|
||||||
|
root: newNode(nil),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the Value associated with the key, or nil with error.
|
||||||
|
// Most common error is ErrMissing, which indicates no Value is associated with the key.
|
||||||
|
// However, there could be other errors propagated from I/O layer (TBD).
|
||||||
|
func (t *MerkleTrie) Get(key Key) (Value, error) {
|
||||||
|
t.mu.RLock()
|
||||||
|
defer t.mu.RUnlock()
|
||||||
|
|
||||||
|
n := t.root
|
||||||
|
for _, k := range key {
|
||||||
|
if n.links[k] == nil {
|
||||||
|
// Path does not exist.
|
||||||
|
return nil, ErrKeyNotFound
|
||||||
|
}
|
||||||
|
n = n.links[k]
|
||||||
|
}
|
||||||
|
if n.value == nil {
|
||||||
|
// Path exists, but no Value is associated.
|
||||||
|
// This happens when the key had been deleted, but the MerkleTrie has not nullified yet.
|
||||||
|
return nil, ErrKeyNotFound
|
||||||
|
}
|
||||||
|
return n.value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates the MerkleTrie with specified key-value pair.
|
||||||
|
// Setting Value to nil deletes the Value, if exists, associated to the key.
|
||||||
|
func (t *MerkleTrie) Update(key Key, val Value) error {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
|
||||||
|
update(t.root, key, val)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prune removes nodes that do not reach to any value node.
|
||||||
|
func (t *MerkleTrie) Prune() {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
|
||||||
|
prune(t.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the number of values.
|
||||||
|
func (t *MerkleTrie) Size() int {
|
||||||
|
t.mu.RLock()
|
||||||
|
defer t.mu.RUnlock()
|
||||||
|
|
||||||
|
size := 0 // captured in the closure.
|
||||||
|
fn := func(prefix Key, v Value) error {
|
||||||
|
if v != nil {
|
||||||
|
size++
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
traverse(t.root, Key{}, fn)
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visit implements callback function invoked when the Value is visited.
|
||||||
|
// During the traversal, if a non-nil error is returned, the traversal ends early.
|
||||||
|
type Visit func(prefix Key, val Value) error
|
||||||
|
|
||||||
|
// Traverse visits every Value in the MerkleTrie and returns error defined by specified Visit function.
|
||||||
|
// update indicates if the visit function modify the state of MerkleTrie.
|
||||||
|
func (t *MerkleTrie) Traverse(visit Visit, update, valueOnly bool) error {
|
||||||
|
if update {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
} else {
|
||||||
|
t.mu.RLock()
|
||||||
|
defer t.mu.RUnlock()
|
||||||
|
}
|
||||||
|
fn := func(prefix Key, value Value) error {
|
||||||
|
if !valueOnly || value != nil {
|
||||||
|
return visit(prefix, value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return traverse(t.root, Key{}, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MerkleHash calculates the Merkle Hash of the MerkleTrie.
|
||||||
|
// If the MerkleTrie is empty, EmptyTrieHash is returned.
|
||||||
|
func (t *MerkleTrie) MerkleHash() chainhash.Hash {
|
||||||
|
if merkle(t.root) == nil {
|
||||||
|
return EmptyTrieHash
|
||||||
|
}
|
||||||
|
return *t.root.hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHashFromStr(s string) *chainhash.Hash {
|
||||||
|
h, _ := chainhash.NewHashFromStr(s)
|
||||||
|
return h
|
||||||
|
}
|
75
trie/trie_test.go
Normal file
75
trie/trie_test.go
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
package trie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTrie_Update(t *testing.T) {
|
||||||
|
mt := buildTrie(New(), pairs1())
|
||||||
|
m := buildMap(newMap(), pairs1())
|
||||||
|
|
||||||
|
for k := range m {
|
||||||
|
v, _ := mt.Get(Key(k))
|
||||||
|
if m[k] != v {
|
||||||
|
t.Errorf("exp %s got %s", m[k], v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrie_Hash(t *testing.T) {
|
||||||
|
tr1 := buildTrie(New(), pairs1())
|
||||||
|
// tr2 := func() *MerkleTrie {
|
||||||
|
// p1 := wire.OutPoint{Hash: *newHashFromStr("627ecfee2110b28fbc4b012944cadf66a72f394ad9fa9bb18fec30789e26c9ac"), Index: 0}
|
||||||
|
// p2 := wire.OutPoint{Hash: *newHashFromStr("c31bd469112abf04930879c6b6007d2b23224e042785d404bbeff1932dd94880"), Index: 0}
|
||||||
|
|
||||||
|
// n1 := claim.NewNode(&claim.Claim{OutPoint: p1, ClaimID: nil, Amount: 50, Height: 100, ValidAtHeight: 200})
|
||||||
|
// n2 := claim.NewNode(&claim.Claim{OutPoint: p2, ClaimID: nil, Amount: 50, Height: 100, ValidAtHeight: 200})
|
||||||
|
|
||||||
|
// pairs := []pair{
|
||||||
|
// {"test", n1},
|
||||||
|
// {"test2", n2},
|
||||||
|
// }
|
||||||
|
// return buildTrie(New(), pairs)
|
||||||
|
// }()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
mt *MerkleTrie
|
||||||
|
want chainhash.Hash
|
||||||
|
}{
|
||||||
|
{"empty", New(), *newHashFromStr("0000000000000000000000000000000000000000000000000000000000000001")},
|
||||||
|
{"test1", tr1, *newHashFromStr("c2fdce68a30e3cabf6efb3b7ebfd32afdaf09f9ebd062743fe91e181f682252b")},
|
||||||
|
// {"test2", tr2, *newHashFromStr("71c7b8d35b9a3d7ad9a1272b68972979bbd18589f1efe6f27b0bf260a6ba78fa")},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
mt := tt.mt
|
||||||
|
if got := mt.MerkleHash(); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("trie.MerkleHash() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrie_Size(t *testing.T) {
|
||||||
|
mt1 := buildTrie(New(), pairs1())
|
||||||
|
map1 := buildMap(newMap(), pairs1())
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
mt *MerkleTrie
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{"test1", mt1, len(map1)},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
mt := tt.mt
|
||||||
|
if got := mt.Size(); got != tt.want {
|
||||||
|
t.Errorf("trie.Size() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue