initial import
This commit is contained in:
commit
8eae587539
8 changed files with 927 additions and 0 deletions
47
README.md
Normal file
47
README.md
Normal file
|
@ -0,0 +1,47 @@
|
|||
# ClaimTrie
|
||||
|
||||
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/claimtrie
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Refer to [triesh](https://github.com/lbryio/claimtrie/blob/master/cmd/claimtrie)
|
||||
|
||||
## Testing
|
||||
|
||||
``` bash
|
||||
go test -v github.com/lbryio/claimtrie
|
||||
gocov test -v github.com/lbryio/claimtrie 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)
|
150
claim.go
Normal file
150
claim.go
Normal file
|
@ -0,0 +1,150 @@
|
|||
package claimtrie
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
// NewClaim ...
|
||||
func NewClaim(op wire.OutPoint, amt Amount, accepted Height) *Claim {
|
||||
return &Claim{
|
||||
op: op,
|
||||
id: NewClaimID(op),
|
||||
amt: amt,
|
||||
accepted: accepted,
|
||||
}
|
||||
}
|
||||
|
||||
// Claim ...
|
||||
type Claim struct {
|
||||
op wire.OutPoint
|
||||
id ClaimID
|
||||
amt Amount
|
||||
effAmt Amount
|
||||
accepted Height
|
||||
}
|
||||
|
||||
// ActivateAt ...
|
||||
func (c *Claim) ActivateAt(best *Claim, curr, tookover Height) Height {
|
||||
if best == nil || best == c {
|
||||
return c.accepted
|
||||
}
|
||||
return calActiveHeight(c.accepted, curr, tookover)
|
||||
}
|
||||
|
||||
// MarshalJSON customizes the representation of JSON.
|
||||
func (c *Claim) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(&struct {
|
||||
OutPoint string
|
||||
ClaimID string
|
||||
Amount Amount
|
||||
EffectiveAmount Amount
|
||||
Accepted Height
|
||||
}{
|
||||
OutPoint: c.op.String(),
|
||||
ClaimID: c.id.String(),
|
||||
Amount: c.amt,
|
||||
EffectiveAmount: c.effAmt,
|
||||
Accepted: c.accepted,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Claim) String() string {
|
||||
b, err := json.MarshalIndent(c, "", " ")
|
||||
if err != nil {
|
||||
fmt.Printf("can't marshal, err :%s", err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// NewSupport ...
|
||||
func NewSupport(op wire.OutPoint, amt Amount, accepted Height, supported ClaimID) *Support {
|
||||
return &Support{
|
||||
op: op,
|
||||
amt: amt,
|
||||
accepted: accepted,
|
||||
supportedID: supported,
|
||||
}
|
||||
}
|
||||
|
||||
// Support ...
|
||||
type Support struct {
|
||||
op wire.OutPoint
|
||||
amt Amount
|
||||
accepted Height
|
||||
|
||||
supportedID ClaimID
|
||||
supportedClaim *Claim
|
||||
}
|
||||
|
||||
// ActivateAt ...
|
||||
func (s *Support) ActivateAt(best *Claim, curr, tookover Height) Height {
|
||||
if best == nil || best == s.supportedClaim {
|
||||
return s.accepted
|
||||
}
|
||||
return calActiveHeight(s.accepted, curr, tookover)
|
||||
}
|
||||
|
||||
// MarshalJSON ...
|
||||
func (s *Support) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(&struct {
|
||||
OutPoint string
|
||||
SupportedClaimID string
|
||||
Amount Amount
|
||||
Accepted Height
|
||||
}{
|
||||
OutPoint: s.op.String(),
|
||||
SupportedClaimID: s.supportedID.String(),
|
||||
Amount: s.amt,
|
||||
Accepted: s.accepted,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Support) String() string {
|
||||
b, err := json.MarshalIndent(s, "", " ")
|
||||
if err != nil {
|
||||
fmt.Printf("can't marshal, err :%s", err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// NewClaimID ...
|
||||
func NewClaimID(p wire.OutPoint) ClaimID {
|
||||
w := bytes.NewBuffer(p.Hash[:])
|
||||
if err := binary.Write(w, binary.BigEndian, p.Index); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var id ClaimID
|
||||
copy(id[:], btcutil.Hash160(w.Bytes()))
|
||||
return id
|
||||
}
|
||||
|
||||
// NewClaimIDFromString ...
|
||||
func NewClaimIDFromString(s string) (ClaimID, error) {
|
||||
b, err := hex.DecodeString(s)
|
||||
var id ClaimID
|
||||
copy(id[:], b)
|
||||
return id, err
|
||||
}
|
||||
|
||||
// ClaimID ...
|
||||
type ClaimID [20]byte
|
||||
|
||||
func (id ClaimID) String() string {
|
||||
return hex.EncodeToString(id[:])
|
||||
}
|
||||
|
||||
func calActiveHeight(accepted, curr, tookover Height) Height {
|
||||
factor := Height(32)
|
||||
delay := (curr - tookover) / factor
|
||||
if delay > 4032 {
|
||||
delay = 4032
|
||||
}
|
||||
return accepted + delay
|
||||
}
|
145
claimtrie.go
Normal file
145
claimtrie.go
Normal file
|
@ -0,0 +1,145 @@
|
|||
package claimtrie
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
|
||||
"github.com/lbryio/merkletrie"
|
||||
)
|
||||
|
||||
// Height ...
|
||||
type Height int64
|
||||
|
||||
// Amount ...
|
||||
type Amount int64
|
||||
|
||||
// ClaimTrie implements a Merkle Trie supporting linear history of commits.
|
||||
type ClaimTrie struct {
|
||||
|
||||
// The highest block number commited to the ClaimTrie.
|
||||
bestBlock Height
|
||||
|
||||
// Immutable linear history.
|
||||
head *merkletrie.Commit
|
||||
|
||||
// An overlay supporting Copy-on-Write to the current tip commit.
|
||||
stg *merkletrie.Stage
|
||||
}
|
||||
|
||||
// CommitMeta implements merkletrie.CommitMeta with commit-specific metadata.
|
||||
type CommitMeta struct {
|
||||
Height Height
|
||||
}
|
||||
|
||||
// New returns a ClaimTrie.
|
||||
func New() *ClaimTrie {
|
||||
mt := merkletrie.New()
|
||||
return &ClaimTrie{
|
||||
head: merkletrie.NewCommit(nil, CommitMeta{0}, mt),
|
||||
stg: merkletrie.NewStage(mt),
|
||||
}
|
||||
}
|
||||
|
||||
func updateStageNode(stg *merkletrie.Stage, name string, modifier func(n *node) error) error {
|
||||
v, err := stg.Get(merkletrie.Key(name))
|
||||
if err != nil && err != merkletrie.ErrKeyNotFound {
|
||||
return err
|
||||
}
|
||||
var n *node
|
||||
if v == nil {
|
||||
n = newNode()
|
||||
} else {
|
||||
n = v.(*node).clone()
|
||||
}
|
||||
if err = modifier(n); err != nil {
|
||||
return err
|
||||
}
|
||||
return stg.Update(merkletrie.Key(name), n)
|
||||
}
|
||||
|
||||
// AddClaim adds a Claim to the Stage of ClaimTrie.
|
||||
func (ct *ClaimTrie) AddClaim(name string, op wire.OutPoint, amt Amount, accepted Height) error {
|
||||
return updateStageNode(ct.stg, name, func(n *node) error {
|
||||
return n.addClaim(NewClaim(op, amt, accepted))
|
||||
})
|
||||
}
|
||||
|
||||
// AddSupport adds a Support to the Stage of ClaimTrie.
|
||||
func (ct *ClaimTrie) AddSupport(name string, op wire.OutPoint, amt Amount, accepted Height, supported ClaimID) error {
|
||||
return updateStageNode(ct.stg, name, func(n *node) error {
|
||||
return n.addSupport(NewSupport(op, amt, accepted, supported))
|
||||
})
|
||||
}
|
||||
|
||||
// SpendClaim removes a Claim in the Stage.
|
||||
func (ct *ClaimTrie) SpendClaim(name string, op wire.OutPoint) error {
|
||||
return updateStageNode(ct.stg, name, func(n *node) error {
|
||||
return n.removeClaim(op)
|
||||
})
|
||||
}
|
||||
|
||||
// SpendSupport removes a Support in the Stage.
|
||||
func (ct *ClaimTrie) SpendSupport(name string, op wire.OutPoint) error {
|
||||
return updateStageNode(ct.stg, name, func(n *node) error {
|
||||
return n.removeSupport(op)
|
||||
})
|
||||
}
|
||||
|
||||
// Traverse visits Nodes in the Stage of the ClaimTrie.
|
||||
func (ct *ClaimTrie) Traverse(visit merkletrie.Visit, update, valueOnly bool) error {
|
||||
// wrapper function to make sure the node is updated before it's observed externally.
|
||||
fn := func(prefix merkletrie.Key, v merkletrie.Value) error {
|
||||
v.(*node).updateBestClaim(ct.bestBlock)
|
||||
return visit(prefix, v)
|
||||
}
|
||||
return ct.stg.Traverse(fn, update, valueOnly)
|
||||
}
|
||||
|
||||
// MerkleHash returns the Merkle Hash of the Stage.
|
||||
func (ct *ClaimTrie) MerkleHash() chainhash.Hash {
|
||||
return ct.stg.MerkleHash()
|
||||
}
|
||||
|
||||
// BestBlock returns the highest height of blocks commited to the ClaimTrie.
|
||||
func (ct *ClaimTrie) BestBlock() Height {
|
||||
return ct.bestBlock
|
||||
}
|
||||
|
||||
// Commit commits the current Stage into commit database, and updates the BestBlock with the associated height.
|
||||
// The height must be higher than the current BestBlock, or ErrInvalidHeight is returned.
|
||||
func (ct *ClaimTrie) Commit(h Height) error {
|
||||
if h <= ct.bestBlock {
|
||||
return ErrInvalidHeight
|
||||
}
|
||||
visit := func(prefix merkletrie.Key, v merkletrie.Value) error {
|
||||
v.(*node).updateBestClaim(h)
|
||||
return nil
|
||||
}
|
||||
ct.Traverse(visit, true, true)
|
||||
|
||||
commit, err := ct.stg.Commit(ct.head, CommitMeta{Height: h})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ct.head = commit
|
||||
ct.bestBlock = h
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reset reverts the Stage to a specified commit by height.
|
||||
func (ct *ClaimTrie) Reset(h Height) error {
|
||||
for commit := ct.head; commit != nil; commit = commit.Prev {
|
||||
meta := commit.Meta.(CommitMeta)
|
||||
if meta.Height <= h {
|
||||
ct.head = commit
|
||||
ct.bestBlock = h
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrInvalidHeight
|
||||
}
|
||||
|
||||
// Head returns the current tip commit in the commit database.
|
||||
func (ct *ClaimTrie) Head() *merkletrie.Commit {
|
||||
return ct.head
|
||||
}
|
39
claimtrie_test.go
Normal file
39
claimtrie_test.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package claimtrie
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
|
||||
"github.com/lbryio/merkletrie"
|
||||
)
|
||||
|
||||
func TestClaimTrie_AddClaim(t *testing.T) {
|
||||
type fields struct {
|
||||
stg *merkletrie.Stage
|
||||
}
|
||||
type args struct {
|
||||
name string
|
||||
outPoint wire.OutPoint
|
||||
value Amount
|
||||
height Height
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ct := &ClaimTrie{
|
||||
stg: tt.fields.stg,
|
||||
}
|
||||
if err := ct.AddClaim(tt.args.name, tt.args.outPoint, tt.args.value, tt.args.height); (err != nil) != tt.wantErr {
|
||||
t.Errorf("ClaimTrie.AddClaim() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
293
cmd/claimtrie/main.go
Normal file
293
cmd/claimtrie/main.go
Normal file
|
@ -0,0 +1,293 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
|
||||
"github.com/lbryio/claimtrie"
|
||||
"github.com/lbryio/merkletrie"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var (
|
||||
flagAll = cli.BoolFlag{Name: "all, a", Usage: "apply to non-value nodes"}
|
||||
flagAmount = cli.Int64Flag{Name: "amount, a", Usage: "Amount"}
|
||||
flagHeight = cli.Int64Flag{Name: "height, ht", Usage: "Height"}
|
||||
flagName = cli.StringFlag{Name: "name, n", Value: "Hello", Usage: "Name"}
|
||||
flagID = cli.StringFlag{Name: "id", Usage: "Claim ID"}
|
||||
flagOutPoint = cli.StringFlag{Name: "outpoint, op", Usage: "Outpoint. (HASH:INDEX) "}
|
||||
)
|
||||
|
||||
var (
|
||||
errNotImplemented = fmt.Errorf("not implemented")
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
|
||||
app.Name = "claimtrie"
|
||||
app.Usage = "A CLI tool for ClaimTrie"
|
||||
app.Version = "0.0.1"
|
||||
app.Action = cli.ShowAppHelp
|
||||
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "add-claim",
|
||||
Aliases: []string{"ac"},
|
||||
Usage: "Claim a name with specified amount. (outPoint is generated randomly, if unspecified)",
|
||||
Action: cmdAddClaim,
|
||||
Flags: []cli.Flag{flagName, flagOutPoint, flagAmount, flagHeight},
|
||||
},
|
||||
{
|
||||
Name: "add-support",
|
||||
Aliases: []string{"as"},
|
||||
Usage: "Add support to a specified Claim. (outPoint is generated randomly, if unspecified)",
|
||||
Action: cmdAddSupport,
|
||||
Flags: []cli.Flag{flagName, flagOutPoint, flagAmount, flagHeight, flagID},
|
||||
},
|
||||
{
|
||||
Name: "spend-claim",
|
||||
Aliases: []string{"sc"},
|
||||
Usage: "Spend a specified Claim.",
|
||||
Action: cmdSpendClaim,
|
||||
Flags: []cli.Flag{flagName, flagOutPoint},
|
||||
},
|
||||
{
|
||||
Name: "spend-support",
|
||||
Aliases: []string{"ss"},
|
||||
Usage: "Spend a specified Support.",
|
||||
Action: cmdSpendSupport,
|
||||
Flags: []cli.Flag{flagName, flagOutPoint},
|
||||
},
|
||||
{
|
||||
Name: "show",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "Show the Key-Value pairs of the Stage or specified commit. (links nodes are showed if -a is also specified)",
|
||||
Action: cmdShow,
|
||||
Flags: []cli.Flag{flagAll, flagHeight},
|
||||
},
|
||||
{
|
||||
Name: "merkle",
|
||||
Aliases: []string{"m"},
|
||||
Usage: "Show the Merkle Hash of the Stage.",
|
||||
Action: cmdMerkle,
|
||||
},
|
||||
{
|
||||
Name: "commit",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "Commit the current Stage to commit database.",
|
||||
Action: cmdCommit,
|
||||
Flags: []cli.Flag{flagHeight},
|
||||
},
|
||||
{
|
||||
Name: "reset",
|
||||
Aliases: []string{"r"},
|
||||
Usage: "Reset the Stage to a specified commit.",
|
||||
Action: cmdReset,
|
||||
Flags: []cli.Flag{flagHeight},
|
||||
},
|
||||
{
|
||||
Name: "log",
|
||||
Aliases: []string{"l"},
|
||||
Usage: "List the commits in the coommit database.",
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
var ct = claimtrie.New()
|
||||
|
||||
// newOutPoint generates random OutPoint for the ease of testing.
|
||||
func newOutPoint(s string) (*wire.OutPoint, error) {
|
||||
if len(s) == 0 {
|
||||
var h chainhash.Hash
|
||||
if _, err := rand.Read(h[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wire.NewOutPoint(&h, uint32(h[0])), nil
|
||||
}
|
||||
fields := strings.Split(s, ":")
|
||||
h, err := chainhash.NewHashFromStr(fields[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idx, err := strconv.Atoi(fields[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wire.NewOutPoint(h, uint32(idx)), nil
|
||||
}
|
||||
|
||||
func cmdAddClaim(c *cli.Context) error {
|
||||
amount := claimtrie.Amount(c.Int64("amount"))
|
||||
if !c.IsSet("amount") {
|
||||
i, err := rand.Int(rand.Reader, big.NewInt(100))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
amount = 1 + claimtrie.Amount(i.Int64())
|
||||
}
|
||||
|
||||
height := claimtrie.Height(c.Int64("height"))
|
||||
if !c.IsSet("height") {
|
||||
height = ct.BestBlock()
|
||||
}
|
||||
|
||||
outPoint, err := newOutPoint(c.String("outpoint"))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return ct.AddClaim(c.String("name"), *outPoint, amount, height)
|
||||
}
|
||||
|
||||
func cmdAddSupport(c *cli.Context) error {
|
||||
amount := claimtrie.Amount(c.Int64("amount"))
|
||||
if !c.IsSet("amount") {
|
||||
i, err := rand.Int(rand.Reader, big.NewInt(100))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
amount = 1 + claimtrie.Amount(i.Int64())
|
||||
}
|
||||
|
||||
height := claimtrie.Height(c.Int64("height"))
|
||||
if !c.IsSet("height") {
|
||||
height = ct.BestBlock()
|
||||
}
|
||||
|
||||
outPoint, err := newOutPoint(c.String("outpoint"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !c.IsSet("id") {
|
||||
return fmt.Errorf("flag -id is required")
|
||||
}
|
||||
cid, err := claimtrie.NewClaimIDFromString(c.String("id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ct.AddSupport(c.String("name"), *outPoint, amount, height, cid)
|
||||
}
|
||||
|
||||
func cmdSpendClaim(c *cli.Context) error {
|
||||
outPoint, err := newOutPoint(c.String("outpoint"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ct.SpendClaim(c.String("name"), *outPoint)
|
||||
}
|
||||
func cmdSpendSupport(c *cli.Context) error {
|
||||
outPoint, err := newOutPoint(c.String("outpoint"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ct.SpendSupport(c.String("name"), *outPoint)
|
||||
}
|
||||
|
||||
func cmdShow(c *cli.Context) error {
|
||||
dump := func(prefix merkletrie.Key, val merkletrie.Value) error {
|
||||
if val == nil {
|
||||
fmt.Printf("%-8s:\n", prefix)
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("%-8s: %v\n", prefix, val)
|
||||
return nil
|
||||
}
|
||||
height := claimtrie.Height(c.Int64("height"))
|
||||
if !c.IsSet("height") {
|
||||
fmt.Printf("<BestBlock: %d>\n", ct.BestBlock())
|
||||
return ct.Traverse(dump, false, !c.Bool("all"))
|
||||
}
|
||||
for commit := ct.Head(); commit != nil; commit = commit.Prev {
|
||||
meta := commit.Meta.(claimtrie.CommitMeta)
|
||||
if height == meta.Height {
|
||||
return commit.MerkleTrie.Traverse(dump, false, !c.Bool("all"))
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("commit not found")
|
||||
}
|
||||
|
||||
func cmdMerkle(c *cli.Context) error {
|
||||
fmt.Printf("%s\n", (ct.MerkleHash()))
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdCommit(c *cli.Context) error {
|
||||
height := claimtrie.Height(c.Int64("height"))
|
||||
if !c.IsSet("height") {
|
||||
height = ct.BestBlock() + 1
|
||||
}
|
||||
return ct.Commit(height)
|
||||
}
|
||||
|
||||
func cmdReset(c *cli.Context) error {
|
||||
height := claimtrie.Height(c.Int64("height"))
|
||||
return ct.Reset(height)
|
||||
}
|
||||
|
||||
func cmdLog(c *cli.Context) error {
|
||||
commitVisit := func(c *merkletrie.Commit) {
|
||||
meta := c.Meta.(claimtrie.CommitMeta)
|
||||
fmt.Printf("height: %d, commit %s\n\n", meta.Height, c.MerkleTrie.MerkleHash())
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
merkletrie.Log(ct.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
|
||||
}
|
||||
err = app.Run(append(os.Args[1:], strings.Split(text, " ")...))
|
||||
if err != nil {
|
||||
fmt.Printf("error: %s\n", err)
|
||||
}
|
||||
}
|
||||
signal.Stop(sigs)
|
||||
}
|
14
error.go
Normal file
14
error.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package claimtrie
|
||||
|
||||
import "fmt"
|
||||
|
||||
var (
|
||||
// ErrInvalidHeight is returned when the height is invalid.
|
||||
ErrInvalidHeight = fmt.Errorf("invalid height")
|
||||
|
||||
// ErrNotFound is returned when the Claim or Support is not found.
|
||||
ErrNotFound = fmt.Errorf("not found")
|
||||
|
||||
// ErrDuplicate is returned when the Claim or Support already exists in the node.
|
||||
ErrDuplicate = fmt.Errorf("duplicate")
|
||||
)
|
185
node.go
Normal file
185
node.go
Normal file
|
@ -0,0 +1,185 @@
|
|||
package claimtrie
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
type node struct {
|
||||
tookover Height
|
||||
bestClaim *Claim
|
||||
|
||||
claims map[string]*Claim
|
||||
supports map[string]*Support
|
||||
}
|
||||
|
||||
func newNode() *node {
|
||||
return &node{
|
||||
claims: map[string]*Claim{},
|
||||
supports: map[string]*Support{},
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) addClaim(c *Claim) error {
|
||||
if _, ok := n.claims[c.op.String()]; ok {
|
||||
return ErrDuplicate
|
||||
}
|
||||
n.claims[c.op.String()] = c
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node) removeClaim(op wire.OutPoint) error {
|
||||
c, ok := n.claims[op.String()]
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
delete(n.claims, op.String())
|
||||
if n.bestClaim == c {
|
||||
n.bestClaim = nil
|
||||
}
|
||||
for _, v := range n.supports {
|
||||
if c.id == v.supportedID {
|
||||
v.supportedClaim = nil
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node) addSupport(s *Support) error {
|
||||
if _, ok := n.supports[s.op.String()]; ok {
|
||||
return ErrDuplicate
|
||||
}
|
||||
for _, v := range n.claims {
|
||||
if v.id == s.supportedID {
|
||||
s.supportedClaim = v
|
||||
n.supports[s.op.String()] = s
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
func (n *node) removeSupport(op wire.OutPoint) error {
|
||||
if _, ok := n.supports[op.String()]; !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
delete(n.supports, op.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Hash calculates the Hash value based on the OutPoint and at which height it tookover.
|
||||
func (n *node) Hash() chainhash.Hash {
|
||||
return calNodeHash(n.bestClaim.op, n.tookover)
|
||||
}
|
||||
|
||||
// MarshalJSON customizes JSON marshaling of the Node.
|
||||
func (n *node) MarshalJSON() ([]byte, error) {
|
||||
c := make([]*Claim, 0, len(n.claims))
|
||||
for _, v := range n.claims {
|
||||
c = append(c, v)
|
||||
}
|
||||
s := make([]*Support, 0, len(n.supports))
|
||||
for _, v := range n.supports {
|
||||
s = append(s, v)
|
||||
}
|
||||
return json.Marshal(&struct {
|
||||
Hash string
|
||||
Tookover Height
|
||||
BestClaim *Claim
|
||||
Claims []*Claim
|
||||
Supports []*Support
|
||||
}{
|
||||
Hash: n.Hash().String(),
|
||||
Tookover: n.tookover,
|
||||
BestClaim: n.bestClaim,
|
||||
Claims: c,
|
||||
Supports: s,
|
||||
})
|
||||
}
|
||||
|
||||
// String implements Stringer interface.
|
||||
func (n *node) String() string {
|
||||
b, err := json.MarshalIndent(n, "", " ")
|
||||
if err != nil {
|
||||
panic("can't marshal Node")
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (n *node) updateEffectiveAmounts(curr Height) {
|
||||
for _, v := range n.claims {
|
||||
v.effAmt = v.amt
|
||||
}
|
||||
for _, v := range n.supports {
|
||||
if v.ActivateAt(n.bestClaim, curr, n.tookover) <= curr {
|
||||
v.supportedClaim.effAmt += v.amt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) updateBestClaim(curr Height) {
|
||||
findCandiadte := func() *Claim {
|
||||
candidate := n.bestClaim
|
||||
for _, v := range n.claims {
|
||||
if v.ActivateAt(n.bestClaim, curr, n.tookover) > curr {
|
||||
continue
|
||||
}
|
||||
if candidate == nil || v.effAmt > candidate.effAmt {
|
||||
candidate = v
|
||||
}
|
||||
}
|
||||
return candidate
|
||||
}
|
||||
for {
|
||||
n.updateEffectiveAmounts(curr)
|
||||
candidate := findCandiadte()
|
||||
if n.bestClaim == nil || n.bestClaim == candidate {
|
||||
n.bestClaim = candidate
|
||||
return
|
||||
}
|
||||
n.tookover = curr
|
||||
n.bestClaim = candidate
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) clone() *node {
|
||||
clone := newNode()
|
||||
|
||||
// shallow copy of value fields.
|
||||
*clone = *n
|
||||
|
||||
// deep copy of reference and pointer fields.
|
||||
clone.claims = map[string]*Claim{}
|
||||
for k, v := range n.claims {
|
||||
clone.claims[k] = v
|
||||
}
|
||||
clone.supports = map[string]*Support{}
|
||||
for k, v := range n.supports {
|
||||
clone.supports[k] = v
|
||||
}
|
||||
return clone
|
||||
}
|
||||
|
||||
func calNodeHash(op wire.OutPoint, tookover Height) chainhash.Hash {
|
||||
txHash := chainhash.DoubleHashH(op.Hash[:])
|
||||
|
||||
nOut := []byte(strconv.Itoa(int(op.Index)))
|
||||
nOutHash := chainhash.DoubleHashH(nOut)
|
||||
|
||||
buf := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(buf, uint64(tookover))
|
||||
heightHash := chainhash.DoubleHashH(buf)
|
||||
|
||||
h := make([]byte, 0, sha256.Size*3)
|
||||
h = append(h, txHash[:]...)
|
||||
h = append(h, nOutHash[:]...)
|
||||
h = append(h, heightHash[:]...)
|
||||
|
||||
return chainhash.DoubleHashH(h)
|
||||
}
|
54
node_test.go
Normal file
54
node_test.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package claimtrie
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
func newHash(s string) *chainhash.Hash {
|
||||
h, _ := chainhash.NewHashFromStr(s)
|
||||
return h
|
||||
}
|
||||
|
||||
func Test_calNodeHash(t *testing.T) {
|
||||
type args struct {
|
||||
op wire.OutPoint
|
||||
h Height
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want chainhash.Hash
|
||||
}{
|
||||
{
|
||||
name: "test1",
|
||||
args: args{op: wire.OutPoint{Hash: *newHash("c73232a755bf015f22eaa611b283ff38100f2a23fb6222e86eca363452ba0c51"), Index: 0}, h: 0},
|
||||
want: *newHash("48a312fc5141ad648cb5dca99eaf221f7b1bc4d2fc559e1cde4664a46d8688a4"),
|
||||
},
|
||||
{
|
||||
name: "test2",
|
||||
args: args{op: wire.OutPoint{Hash: *newHash("71c7b8d35b9a3d7ad9a1272b68972979bbd18589f1efe6f27b0bf260a6ba78fa"), Index: 1}, h: 1},
|
||||
want: *newHash("9132cc5ff95ae67bee79281438e7d00c25c9ec8b526174eb267c1b63a55be67c"),
|
||||
},
|
||||
{
|
||||
name: "test3",
|
||||
args: args{op: wire.OutPoint{Hash: *newHash("c4fc0e2ad56562a636a0a237a96a5f250ef53495c2cb5edd531f087a8de83722"), Index: 0x12345678}, h: 0x87654321},
|
||||
want: *newHash("023c73b8c9179ffcd75bd0f2ed9784aab2a62647585f4b38e4af1d59cf0665d2"),
|
||||
},
|
||||
{
|
||||
name: "test4",
|
||||
args: args{op: wire.OutPoint{Hash: *newHash("baf52472bd7da19fe1e35116cfb3bd180d8770ffbe3ae9243df1fb58a14b0975"), Index: 0x11223344}, h: 0x88776655},
|
||||
want: *newHash("6a2d40f37cb2afea3b38dea24e1532e18cade5d1dc9c2f8bd635aca2bc4ac980"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := calNodeHash(tt.args.op, tt.args.h); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("calNodeHash() = %X, want %X", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue