initial import

This commit is contained in:
Tzu-Jung Lee 2018-06-27 22:14:30 -07:00
commit 8eae587539
8 changed files with 927 additions and 0 deletions

47
README.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
}
})
}
}