fix: send tx with 2 notes

This commit is contained in:
Trinity 2025-10-22 08:44:24 +07:00
parent 2816e5333b
commit 2d9185a5ec
5 changed files with 247 additions and 256 deletions

View File

@ -18,6 +18,7 @@ var (
MagicDyckForPoint = []uint64{0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1}
MagicDyckForT8 = []uint64{0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}
MagicDyckForSeed = []uint64{0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}
MagicDyckForName = []uint64{0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1}
Tip5Zero = [5]uint64{1730770831742798981, 2676322185709933211, 8329210750824781744, 16756092452590401876, 3547445316740171466}
Tip5One = [5]uint64{6727110957294540849, 15606243244732609007, 11887284596344881785, 10646863421881571398, 8146872807338919620}
Tip5ZeroZero = [5]uint64{4372149332062030091, 17876920912185183887, 13348798570422431948, 8872865212694716527, 3385176510443841516}

View File

@ -61,6 +61,24 @@ func HashName(name *nockchain.NockchainName) [5]uint64 {
return crypto.Tip5RehashTenCell(firstNameHash, crypto.Tip5RehashTenCell(lastNameHash, crypto.Tip5Zero))
}
func HashNameVarLen(name *nockchain.NockchainName) [5]uint64 {
belts := []crypto.Belt{}
for _, i := range crypto.Base58ToTip5Hash(name.First) {
belts = append(belts, crypto.Belt{Value: i})
}
for _, i := range crypto.Base58ToTip5Hash(name.Last) {
belts = append(belts, crypto.Belt{Value: i})
}
belts = append(belts, crypto.BELT_ZERO)
size := len(belts)
belts = append([]crypto.Belt{{Value: uint64(size)}}, belts...)
for _, i := range crypto.MagicDyckForName {
belts = append(belts, crypto.Belt{Value: i})
}
res := crypto.Tip5HashBelts(belts)
return res
}
func HashNote(note *nockchain.NockchainNote) ([5]uint64, error) {
versionHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: uint64(note.Version)}})
blockHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: note.BlockHeight}})
@ -225,86 +243,36 @@ func HashSpend(spend *nockchain.NockchainSpend) ([5]uint64, error) {
if err != nil {
return [5]uint64{}, err
}
seedsCount := len(spend.Seeds)
var finalSeedHash [5]uint64
if seedsCount == 1 {
seedHash, err := HashSeedWithoutSource(spend.Seeds[0])
if err != nil {
return [5]uint64{}, err
}
finalSeedHash = crypto.Tip5RehashTenCell(seedHash, crypto.Tip5ZeroZero)
} else {
seed1Hash, err := HashSeedWithoutSource(spend.Seeds[0])
if err != nil {
return [5]uint64{}, err
}
seed1HashVarLen, err := HashSeedVarLen(spend.Seeds[0])
if err != nil {
return [5]uint64{}, err
}
seed1DoubleHash := crypto.Tip5RehashTenCell(seed1HashVarLen, seed1HashVarLen)
seed2Hash, err := HashSeedWithoutSource(spend.Seeds[1])
if err != nil {
return [5]uint64{}, err
}
seed2HashVarLen, err := HashSeedVarLen(spend.Seeds[1])
if err != nil {
return [5]uint64{}, err
}
seed2DoubleHash := crypto.Tip5RehashTenCell(seed2HashVarLen, seed2HashVarLen)
seedHash1BigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(seed1HashVarLen)))
seed1DoubleHashBigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(seed1DoubleHash)))
seedHash2BigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(seed2HashVarLen)))
seed2DoubleHashBigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(seed2DoubleHash)))
if seedHash1BigInt.Cmp(seedHash2BigInt) == -1 {
// seed 1 < seed 2
if seed1DoubleHashBigInt.Cmp(seed2DoubleHashBigInt) == -1 {
// seed1
// / \
// ~ seed2
// / \
// ~ ~
finalSeedHash = crypto.Tip5RehashTenCell(seed2Hash, crypto.Tip5ZeroZero)
finalSeedHash = crypto.Tip5RehashTenCell(crypto.Tip5Zero, finalSeedHash)
finalSeedHash = crypto.Tip5RehashTenCell(seed1Hash, finalSeedHash)
seedsTree := NewZTree(
func(i interface{}) [5]uint64 {
if seed, ok := i.(*nockchain.NockchainSeed); ok {
seedHash, err := HashSeedVarLen(seed)
if err != nil {
return [5]uint64{}
}
return seedHash
} else {
// seed2
// / \
// seed1 ~
// / \
// ~ ~
finalSeedHash = crypto.Tip5RehashTenCell(seed1Hash, crypto.Tip5ZeroZero)
finalSeedHash = crypto.Tip5RehashTenCell(finalSeedHash, crypto.Tip5Zero)
finalSeedHash = crypto.Tip5RehashTenCell(seed2Hash, finalSeedHash)
return [5]uint64{}
}
} else {
// seed 1 > seed 2
if seed1DoubleHashBigInt.Cmp(seed2DoubleHashBigInt) == -1 {
// seed1
// / \
// seed2 ~
// / \
// ~ ~
finalSeedHash = crypto.Tip5RehashTenCell(seed2Hash, crypto.Tip5ZeroZero)
finalSeedHash = crypto.Tip5RehashTenCell(finalSeedHash, crypto.Tip5Zero)
finalSeedHash = crypto.Tip5RehashTenCell(seed1Hash, finalSeedHash)
},
func(i interface{}) ([5]uint64, error) {
if seed, ok := i.(*nockchain.NockchainSeed); ok {
hash, err := HashSeedWithoutSource(seed)
return hash, err
} else {
// seed2
// / \
// ~ seed1
// / \
// ~ ~
finalSeedHash = crypto.Tip5RehashTenCell(seed1Hash, crypto.Tip5ZeroZero)
finalSeedHash = crypto.Tip5RehashTenCell(crypto.Tip5Zero, finalSeedHash)
finalSeedHash = crypto.Tip5RehashTenCell(seed2Hash, finalSeedHash)
return [5]uint64{}, fmt.Errorf("invalid input type")
}
}
},
)
for _, seed := range spend.Seeds {
seedsTree.Insert(seed, seed)
}
finalSeedHash, err := seedsTree.Hash()
if err != nil {
return [5]uint64{}, err
}
feeHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: spend.Fee}})
seedHashFee := crypto.Tip5RehashTenCell(finalSeedHash, feeHash)
return crypto.Tip5RehashTenCell(sigHash, seedHashFee), nil
@ -312,84 +280,33 @@ func HashSpend(spend *nockchain.NockchainSpend) ([5]uint64, error) {
}
func HashMsg(spend *nockchain.NockchainSpend) ([5]uint64, error) {
seedsCount := len(spend.Seeds)
var finalSeedHash [5]uint64
if seedsCount == 1 {
seedHash, err := HashSeed(spend.Seeds[0])
if err != nil {
return [5]uint64{}, err
}
finalSeedHash = crypto.Tip5RehashTenCell(seedHash, crypto.Tip5ZeroZero)
} else {
seed1HashVarLen, err := HashSeedVarLen(spend.Seeds[0])
if err != nil {
return [5]uint64{}, err
}
seed1Hash, err := HashSeed(spend.Seeds[0])
if err != nil {
return [5]uint64{}, err
}
seed1DoubleHash := crypto.Tip5RehashTenCell(seed1HashVarLen, seed1HashVarLen)
seed2HashVarLen, err := HashSeedVarLen(spend.Seeds[1])
if err != nil {
return [5]uint64{}, err
}
seed2Hash, err := HashSeed(spend.Seeds[1])
if err != nil {
return [5]uint64{}, err
}
seed2DoubleHash := crypto.Tip5RehashTenCell(seed2HashVarLen, seed2HashVarLen)
seedHash1BigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(seed1HashVarLen)))
seed1DoubleHashBigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(seed1DoubleHash)))
seedHash2BigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(seed2HashVarLen)))
seed2DoubleHashBigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(seed2DoubleHash)))
if seedHash1BigInt.Cmp(seedHash2BigInt) == -1 {
// seed 1 < seed 2
if seed1DoubleHashBigInt.Cmp(seed2DoubleHashBigInt) == -1 {
// seed1
// / \
// ~ seed2
// / \
// ~ ~
finalSeedHash = crypto.Tip5RehashTenCell(seed2Hash, crypto.Tip5ZeroZero)
finalSeedHash = crypto.Tip5RehashTenCell(crypto.Tip5Zero, finalSeedHash)
finalSeedHash = crypto.Tip5RehashTenCell(seed1Hash, finalSeedHash)
seedsTree := NewZTree(
func(i interface{}) [5]uint64 {
if seed, ok := i.(*nockchain.NockchainSeed); ok {
seedHash, err := HashSeedVarLen(seed)
if err != nil {
return [5]uint64{}
}
return seedHash
} else {
// seed2
// / \
// seed1 ~
// / \
// ~ ~
finalSeedHash = crypto.Tip5RehashTenCell(seed1Hash, crypto.Tip5ZeroZero)
finalSeedHash = crypto.Tip5RehashTenCell(finalSeedHash, crypto.Tip5Zero)
finalSeedHash = crypto.Tip5RehashTenCell(seed2Hash, finalSeedHash)
return [5]uint64{}
}
} else {
// seed 1 > seed 2
if seed1DoubleHashBigInt.Cmp(seed2DoubleHashBigInt) == -1 {
// seed1
// / \
// seed2 ~
// / \
// ~ ~
finalSeedHash = crypto.Tip5RehashTenCell(seed2Hash, crypto.Tip5ZeroZero)
finalSeedHash = crypto.Tip5RehashTenCell(finalSeedHash, crypto.Tip5Zero)
finalSeedHash = crypto.Tip5RehashTenCell(seed1Hash, finalSeedHash)
},
func(i interface{}) ([5]uint64, error) {
if seed, ok := i.(*nockchain.NockchainSeed); ok {
hash, err := HashSeed(seed)
return hash, err
} else {
// seed2
// / \
// ~ seed1
// / \
// ~ ~
finalSeedHash = crypto.Tip5RehashTenCell(seed1Hash, crypto.Tip5ZeroZero)
finalSeedHash = crypto.Tip5RehashTenCell(crypto.Tip5Zero, finalSeedHash)
finalSeedHash = crypto.Tip5RehashTenCell(seed2Hash, finalSeedHash)
return [5]uint64{}, fmt.Errorf("invalid input type")
}
}
},
)
for _, seed := range spend.Seeds {
seedsTree.Insert(seed, seed)
}
finalSeedHash, err := seedsTree.Hash()
if err != nil {
return [5]uint64{}, err
}
feeHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: spend.Fee}})
return crypto.Tip5RehashTenCell(finalSeedHash, feeHash), nil
@ -417,7 +334,7 @@ func ComputeTxId(inputs []*nockchain.NockchainInput, timelockRange *nockchain.Ti
inputTree := NewZTree(
func(i interface{}) [5]uint64 {
if name, ok := i.(*nockchain.NockchainName); ok {
return HashName(name)
return HashNameVarLen(name)
} else {
return [5]uint64{}
}

View File

@ -287,7 +287,7 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque
nameTree := NewZTree(
func(i interface{}) [5]uint64 {
if name, ok := i.(*nockchain.NockchainName); ok {
return HashName(name)
return HashNameVarLen(name)
} else {
return [5]uint64{}
}
@ -297,15 +297,17 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque
for _, name := range nnames {
nameTree.Insert(name, nil)
}
nameLeftMost := nameTree.KeyLeftMost().(*nockchain.NockchainName)
idxLeftMost := -1
for i, name := range nnames {
if name.First == nameLeftMost.First && name.Last == nameLeftMost.Last {
idxLeftMost = i
}
nameList := nameTree.Tap()
nameKeys := []string{}
for _, nameKey := range nameList {
name := nameKey.Key.(*nockchain.NockchainName)
nameKeys = append(nameKeys, name.First+" "+name.Last)
}
if idxLeftMost == -1 {
return nil, fmt.Errorf("unable to find left most node")
indices := make([]int, len(nnames))
for i, name := range nnames {
if idx := slices.Index(nameKeys, name.First+" "+name.Last); idx != -1 {
indices[idx] = i
}
}
recipents := []*nockchain.NockchainLock{}
@ -411,40 +413,40 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque
}
}
inputs := []*nockchain.NockchainInput{}
for i := 0; i < len(names); i++ {
if notes[i] == nil {
return nil, fmt.Errorf("notes scanned is missing at name: %s", names[i])
inputs := make([]*nockchain.NockchainInput, len(names))
isSpent := false
for i := 0; i < len(nameKeys); i++ {
idx := indices[i]
if notes[idx] == nil {
return nil, fmt.Errorf("notes scanned is missing at name: %s", names[idx])
}
if notes[i].Asset < gifts[i]+req.Fee {
return nil, fmt.Errorf("note not enough balance")
if notes[idx].Asset < gifts[idx] {
return nil, fmt.Errorf("note %s not enough balance", names[idx])
}
parentHash, err := HashNote(notes[i])
parentHash, err := HashNote(notes[idx])
if err != nil {
return nil, err
}
seeds := []*nockchain.NockchainSeed{
{
OutputSource: nil,
Recipient: recipents[i],
Recipient: recipents[idx],
TimelockIntent: req.TimelockIntent,
Gift: gifts[i],
Gift: gifts[idx],
ParentHash: crypto.Tip5HashToBase58(parentHash),
},
}
if notes[i].Asset < gifts[i] {
return nil, fmt.Errorf("insufficient funds for notes %s", names[i])
}
assetLeft := notes[i].Asset - gifts[i]
if i == idxLeftMost {
if assetLeft > req.Fee {
assetLeft -= req.Fee
} else {
return nil, fmt.Errorf("insufficient funds for notes %s", names[i])
}
assetLeft := notes[idx].Asset - gifts[idx]
fee := uint64(0)
if !isSpent && assetLeft >= req.Fee {
isSpent = true
fee = req.Fee
assetLeft -= req.Fee
}
if assetLeft != 0 {
seeds = append(seeds, &nockchain.NockchainSeed{
OutputSource: nil,
@ -457,19 +459,10 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque
ParentHash: crypto.Tip5HashToBase58(parentHash),
})
}
var spend nockchain.NockchainSpend
if i == idxLeftMost {
spend = nockchain.NockchainSpend{
Signatures: nil,
Seeds: seeds,
Fee: req.Fee,
}
} else {
spend = nockchain.NockchainSpend{
Signatures: nil,
Seeds: seeds,
Fee: 0,
}
spend := nockchain.NockchainSpend{
Signatures: nil,
Seeds: seeds,
Fee: fee,
}
msg, err := HashMsg(&spend)
@ -492,13 +485,16 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque
}
input := nockchain.NockchainInput{
Name: nnames[i],
Note: notes[i],
Name: nnames[idx],
Note: notes[idx],
Spend: &spend,
}
inputs = append(inputs, &input)
inputs[idx] = &input
}
if !isSpent {
return nil, fmt.Errorf("insufficient funds")
}
var timelockRange *nockchain.TimelockRange
if req.TimelockIntent == nil {
timelockRange = &nockchain.TimelockRange{

View File

@ -472,21 +472,5 @@ func TestCreateTx(t *testing.T) {
assert.NoError(t, err)
// the result is taken from create-tx scripts
//assert.Equal(t, res.RawTx.TxId, "8gjTa6H1MrKXPWgNJCF9fsYE7PUfqhYzxetUoTftz7zyHCv24ZYHDM3")
_, err = nc.WalletSendTransaction(res.RawTx)
if err != nil {
panic(err)
}
txAccepted, err := nc.TxAccepted(res.RawTx.TxId)
if err != nil {
panic(err)
}
fmt.Println("tx pass: ", res.RawTx.TxId)
if !txAccepted.GetAccepted() {
panic("tx not accepted")
}
// expected: A8vSeRde61B4sZccSnNPEnkQgTe15EssoFwyhQXbkhtk4UNm5hyGSid
assert.Equal(t, res.RawTx.TxId, "A8vSeRde61B4sZccSnNPEnkQgTe15EssoFwyhQXbkhtk4UNm5hyGSid")
}

View File

@ -1,8 +1,10 @@
package wallet
import (
"math/big"
"slices"
"github.com/btcsuite/btcd/btcutil/base58"
"github.com/phamminh0811/private-grpc/crypto"
)
@ -20,6 +22,11 @@ type ZTree struct {
HashValueFunc func(interface{}) ([5]uint64, error)
}
type ZPair struct {
Key interface{}
Value interface{}
}
func NewZTree(hashKeyFunc func(interface{}) [5]uint64, hashValueFunc func(interface{}) ([5]uint64, error)) *ZTree {
return &ZTree{
Root: nil,
@ -36,13 +43,13 @@ func (z *ZTree) Hash() ([5]uint64, error) {
return z.Root.HashNode(z.HashValueFunc)
}
func (z *ZTree) KeyLeftMost() interface{} {
node := z.Root
for node.Left != nil {
node = node.Left
}
return node.Key
func (t *ZTree) Tap() []ZPair {
tap := []ZPair{}
TapNode(t.Root, &tap)
slices.Reverse(tap)
return tap
}
func (node *ZNode) InsertNode(hashFunc func(interface{}) [5]uint64, key, value interface{}) *ZNode {
if node == nil {
node = &ZNode{
@ -53,42 +60,18 @@ func (node *ZNode) InsertNode(hashFunc func(interface{}) [5]uint64, key, value i
}
return node
}
keyHash := hashFunc(key)
keyDoubleHash := crypto.Tip5RehashTenCell(keyHash, keyHash)
nodeKeyHash := hashFunc(node.Key)
nodeKeyDoubleHash := crypto.Tip5RehashTenCell(nodeKeyHash, nodeKeyHash)
if slices.Compare(keyHash[:], nodeKeyHash[:]) == -1 {
keyHashBigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(keyHash)))
nodeKeyHashBigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(nodeKeyHash)))
keyDoubleHashBigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(keyDoubleHash)))
nodeKeyDoubleHashBigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(nodeKeyDoubleHash)))
if keyHashBigInt.Cmp(nodeKeyHashBigInt) == 1 {
// key < node key
if slices.Compare(keyDoubleHash[:], nodeKeyDoubleHash[:]) == -1 {
// reinsert in left
node.Left = node.Left.InsertNode(hashFunc, key, value)
} else {
// new key
// / \
// ~ old key
// / \
// ... ...
nodeKey := node.Key
nodeValue := node.Value
leftNode := node.Left
rightNode := node.Right
node = &ZNode{
Key: key,
Value: value,
Right: nil,
Left: &ZNode{
Key: nodeKey,
Value: nodeValue,
Left: leftNode,
Right: rightNode,
},
}
}
} else {
// key > node key
if slices.Compare(keyDoubleHash[:], nodeKeyDoubleHash[:]) == -1 {
if keyDoubleHashBigInt.Cmp(nodeKeyDoubleHashBigInt) == 1 {
// reinsert in right
node.Right = node.Right.InsertNode(hashFunc, key, value)
} else {
@ -101,16 +84,104 @@ func (node *ZNode) InsertNode(hashFunc func(interface{}) [5]uint64, key, value i
nodeValue := node.Value
leftNode := node.Left
rightNode := node.Right
node = &ZNode{
Key: key,
Value: value,
Right: &ZNode{
Key: nodeKey,
Value: nodeValue,
Left: leftNode,
Right: rightNode,
},
Left: nil,
if rightNode == nil {
node = &ZNode{
Key: key,
Value: value,
Right: nil,
Left: &ZNode{
Key: nodeKey,
Value: nodeValue,
Left: leftNode,
Right: nil,
},
}
} else {
rightNodeKeyHash := hashFunc(rightNode.Key)
rightNodeKeyHashBigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(rightNodeKeyHash)))
if rightNodeKeyHashBigInt.Cmp(keyHashBigInt) == -1 {
node = &ZNode{
Key: key,
Value: value,
Right: nil,
Left: &ZNode{
Key: nodeKey,
Value: nodeValue,
Left: leftNode,
Right: rightNode,
},
}
} else {
node = &ZNode{
Key: key,
Value: value,
Right: rightNode,
Left: &ZNode{
Key: nodeKey,
Value: nodeValue,
Left: leftNode,
Right: nil,
},
}
}
}
}
} else {
// key > node key
if keyDoubleHashBigInt.Cmp(nodeKeyDoubleHashBigInt) == 1 {
// reinsert in left
node.Left = node.Left.InsertNode(hashFunc, key, value)
} else {
// new key
// / \
// ~ old key
// / \
// ... ...
nodeKey := node.Key
nodeValue := node.Value
leftNode := node.Left
rightNode := node.Right
if leftNode == nil {
node = &ZNode{
Key: key,
Value: value,
Right: &ZNode{
Key: nodeKey,
Value: nodeValue,
Left: nil,
Right: rightNode,
},
Left: nil,
}
} else {
leftNodeKeyHash := hashFunc(leftNode.Key)
leftNodeKeyHashBigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(leftNodeKeyHash)))
if leftNodeKeyHashBigInt.Cmp(keyHashBigInt) == -1 {
node = &ZNode{
Key: key,
Value: value,
Right: &ZNode{
Key: nodeKey,
Value: nodeValue,
Left: nil,
Right: rightNode,
},
Left: leftNode,
}
} else {
node = &ZNode{
Key: key,
Value: value,
Right: &ZNode{
Key: nodeKey,
Value: nodeValue,
Left: leftNode,
Right: rightNode,
},
Left: nil,
}
}
}
}
}
@ -138,3 +209,25 @@ func (node *ZNode) HashNode(hashFunc func(interface{}) ([5]uint64, error)) ([5]u
}
return crypto.Tip5RehashTenCell(valHash, hashLeftRight), nil
}
func TapNode(node *ZNode, acc *[]ZPair) {
if node == nil {
return
}
stored := false
if node.Right != nil {
TapNode(node.Right, acc)
} else {
*acc = append(*acc, ZPair{Key: node.Key, Value: node.Value})
stored = true
}
if node.Left != nil {
TapNode(node.Left, acc)
}
if !stored {
*acc = append(*acc, ZPair{Key: node.Key, Value: node.Value})
}
}