Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ require (
github.com/cosmos/gogogateway v1.2.0
github.com/cosmos/gogoproto v1.7.2
github.com/cosmos/iavl v1.2.6
github.com/cosmos/ics23/go v0.11.0
github.com/cosmos/ledger-cosmos-go v0.16.0
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0
github.com/edsrzf/mmap-go v1.0.0
Expand Down Expand Up @@ -135,7 +136,6 @@ require (
github.com/cockroachdb/redact v1.1.6 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb // indirect
github.com/cometbft/cometbft-db v0.14.1 // indirect
github.com/cosmos/ics23/go v0.11.0 // indirect
github.com/danieljoos/wincred v1.2.2 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
Expand Down
2 changes: 1 addition & 1 deletion iavlx/commit_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"sync/atomic"

"cosmossdk.io/log"

Check failure on line 10 in iavlx/commit_tree.go

View workflow job for this annotation

GitHub Actions / golangci-lint

File is not properly formatted (gci)
pruningtypes "cosmossdk.io/store/pruning/types"
storetypes "cosmossdk.io/store/types"
)
Expand Down Expand Up @@ -336,7 +336,7 @@
}()
}

func (c *CommitTree) GetImmutable(version int64) (storetypes.KVStore, error) {
func (c *CommitTree) GetImmutable(version int64) (*ImmutableTree, error) {
var rootPtr *NodePointer
if version == c.lastCommitId.Version {
rootPtr = c.root
Expand Down
111 changes: 111 additions & 0 deletions iavlx/immutable_tree.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package iavlx

import (
"errors"
io "io"

ics23 "github.com/cosmos/ics23/go"

storetypes "cosmossdk.io/store/types"
)

Expand Down Expand Up @@ -68,6 +71,114 @@
return NewIterator(start, end, false, tree.root, true)
}

func (tree *ImmutableTree) GetMembershipProof(key []byte) (*ics23.CommitmentProof, error) {
root, err := tree.root.Resolve()
if err != nil {
return nil, err
}
exist, err := createExistenceProof(root, key)
if err != nil {
return nil, err
}
proof := &ics23.CommitmentProof{
Proof: &ics23.CommitmentProof_Exist{
Exist: exist,
},
}
return proof, nil
}

/*
GetNonMembershipProof will produce a CommitmentProof that the given key doesn't exist in the iavl tree.
If the key exists in the tree, this will return an error.
*/
func (t *ImmutableTree) GetNonMembershipProof(key []byte) (*ics23.CommitmentProof, error) {
// idx is one node right of what we want....
exists := t.Has(key)
if exists {
return nil, errors.New("cannot create non-membership proof with key that exists in tree")
}

nonexist := &ics23.NonExistenceProof{
Key: key,
}

root, err := t.root.Resolve()
if err != nil {
return nil, err
}
idx, err := nextIndex(root, key)
if err != nil {
return nil, err
}

if idx >= 1 {
leftNode, err := getByIndex(root, idx-1)
if err != nil {
return nil, err
}
leftKey, err := leftNode.Key()
if err != nil {
return nil, err
}

nonexist.Left, err = createExistenceProof(root, leftKey)
if err != nil {
return nil, err
}
}

// this will be nil if nothing right of the queried key
rightNode, err := getByIndex(root, idx)
if err != nil {
return nil, err
}
if rightNode != nil {
rightKey, err := rightNode.Key()

Check warning

Code scanning / CodeQL

Useless assignment to local variable Warning

This definition of err is never used.

Copilot Autofix

AI about 8 hours ago

The best way to fix this problem is to properly check and handle the error from the call to rightNode.Key(). Specifically, after line 137, add an error check: if err is not nil, return the error, mirroring how errors are treated after similar lines above (like for leftNode.Key() and others). This ensures any error from Key() does not go unnoticed, aligning with good Go error-handling practice.

  • Change required in file iavlx/immutable_tree.go, lines 137–139:
    • After assigning rightKey, err := rightNode.Key(), check if err is non-nil, and return as appropriate.
  • No new imports or definitions are needed; simply add the error check.

Suggested changeset 1
iavlx/immutable_tree.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/iavlx/immutable_tree.go b/iavlx/immutable_tree.go
--- a/iavlx/immutable_tree.go
+++ b/iavlx/immutable_tree.go
@@ -135,7 +135,9 @@
 	}
 	if rightNode != nil {
 		rightKey, err := rightNode.Key()
-
+		if err != nil {
+			return nil, err
+		}
 		if rightKey != nil {
 			nonexist.Right, err = createExistenceProof(root, rightKey)
 			if err != nil {
EOF
@@ -135,7 +135,9 @@
}
if rightNode != nil {
rightKey, err := rightNode.Key()

if err != nil {
return nil, err
}
if rightKey != nil {
nonexist.Right, err = createExistenceProof(root, rightKey)
if err != nil {
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated

if rightKey != nil {
nonexist.Right, err = createExistenceProof(root, rightKey)
if err != nil {
return nil, err
}
}
}

proof := &ics23.CommitmentProof{
Proof: &ics23.CommitmentProof_Nonexist{
Nonexist: nonexist,
},
}
return proof, nil
}

// VerifyMembership returns true iff proof is an ExistenceProof for the given key.
func (t *ImmutableTree) VerifyMembership(proof *ics23.CommitmentProof, key []byte) (bool, error) {
val := t.Get(key)
if val == nil {
return false, errors.New("key not found")
}
rootNode, err := t.root.Resolve()
if err != nil {
return false, err
}

root := rootNode.Hash()

return ics23.VerifyMembership(ics23.IavlSpec, root, proof, key, val), nil
}

// VerifyNonMembership returns true iff proof is a NonExistenceProof for the given key.
func (t *ImmutableTree) VerifyNonMembership(proof *ics23.CommitmentProof, key []byte) (bool, error) {
root, err := t.root.Resolve()
if err != nil {
return false, err
}
rootHash := root.Hash()

return ics23.VerifyNonMembership(ics23.IavlSpec, rootHash, proof, key), nil
}

var (
_ storetypes.KVStore = (*ImmutableTree)(nil)
)
243 changes: 243 additions & 0 deletions iavlx/proof.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
package iavlx

import (
"bytes"
"encoding/binary"
"errors"

ics23 "github.com/cosmos/ics23/go"
)

type proofInnerNode struct {
Height int8 `json:"height"`
Size int64 `json:"size"`
Version int64 `json:"version"`
Left []byte `json:"left"`
Right []byte `json:"right"`
}

type leafPath []proofInnerNode

// nextIndex returns the index that would be assigned to the key.
// This method assumes the key does not exist.
// Callers are expected to check that t.Has(key) == false.
func nextIndex(node Node, key []byte) (int64, error) {
if node == nil {
return 0, nil
}
nKey, err := node.Key()
if err != nil {
return 0, err
}
if node.IsLeaf() {
switch bytes.Compare(nKey, key) {
case -1:
return 1, nil
case 1:
return 0, nil
default:
return 0, nil
}
}
if bytes.Compare(key, nKey) < 0 {
leftNode, err := node.Left().Resolve()
if err != nil {
return 0, err
}

return nextIndex(leftNode, key)
}

rightNode, err := node.Right().Resolve()
if err != nil {
return 0, err
}

index, err := nextIndex(rightNode, key)
if err != nil {
return 0, err
}

index += node.Size() - rightNode.Size()
return index, nil
}

func createExistenceProof(root Node, key []byte) (*ics23.ExistenceProof, error) {
path := new(leafPath)
leafVersion := root.Version()

leaf, err := pathToLeaf(root, key, uint64(leafVersion), path)
if err != nil {
return nil, err
}
leafVersion = leaf.Version()

leafKey, err := leaf.Key()
if err != nil {
return nil, err
}

leafValue, err := leaf.Value()
if err != nil {
return nil, err
}
return &ics23.ExistenceProof{
Key: leafKey,
Value: leafValue,
Leaf: convertLeafOp(int64(leafVersion)),
Path: convertInnerOps(*path),
}, nil
}

func getByIndex(node Node, index int64) (Node, error) {
if node == nil {
return nil, nil
}
if node.IsLeaf() {
if index == 0 {
return node, nil
}
return nil, nil
}
leftNode, err := node.Left().Resolve()
if err != nil {
return nil, err
}

if index < leftNode.Size() {
return getByIndex(leftNode, index)
}

rightNode, err := node.Right().Resolve()
if err != nil {
return nil, err
}

return getByIndex(rightNode, index-leftNode.Size())
}
func convertLeafOp(version int64) *ics23.LeafOp {
var varintBuf [binary.MaxVarintLen64]byte
// this is adapted from iavl/proof.go:proofLeafNode.Hash()
prefix := convertVarIntToBytes(0, varintBuf)
prefix = append(prefix, convertVarIntToBytes(1, varintBuf)...)
prefix = append(prefix, convertVarIntToBytes(version, varintBuf)...)

return &ics23.LeafOp{
Hash: ics23.HashOp_SHA256,
PrehashValue: ics23.HashOp_SHA256,
Length: ics23.LengthOp_VAR_PROTO,
Prefix: prefix,
}
}

func convertVarIntToBytes(orig int64, buf [binary.MaxVarintLen64]byte) []byte {
n := binary.PutVarint(buf[:], orig)
return buf[:n]
}

func convertInnerOps(path leafPath) []*ics23.InnerOp {
steps := make([]*ics23.InnerOp, 0, len(path))

// lengthByte is the length prefix prepended to each of the sha256 sub-hashes
var lengthByte byte = 0x20

var varintBuf [binary.MaxVarintLen64]byte

// we need to go in reverse order, iavl starts from root to leaf,
// we want to go up from the leaf to the root
for i := len(path) - 1; i >= 0; i-- {
// this is adapted from iavl/proof.go:proofInnerNode.Hash()
// prefix = bytes of height-size-version ++ <length>-leftHash-<length>
// suffix = <length>-rightHash
prefix := convertVarIntToBytes(int64(path[i].Height), varintBuf)
prefix = append(prefix, convertVarIntToBytes(path[i].Size, varintBuf)...)
prefix = append(prefix, convertVarIntToBytes(path[i].Version, varintBuf)...)

var suffix []byte
if len(path[i].Left) > 0 {
// length prefixed left side
prefix = append(prefix, lengthByte)
prefix = append(prefix, path[i].Left...)
// prepend the length prefix for child
prefix = append(prefix, lengthByte)
} else {
// prepend the length prefix for child
prefix = append(prefix, lengthByte)
// length-prefixed right side
suffix = []byte{lengthByte}
suffix = append(suffix, path[i].Right...)
}

op := &ics23.InnerOp{
Hash: ics23.HashOp_SHA256,
Prefix: prefix,
Suffix: suffix,
}
steps = append(steps, op)
}
return steps
}

func pathToLeaf(node Node, key []byte, version uint64, path *leafPath) (Node, error) {
nodeKey, err := node.Key()
if err != nil {
return nil, err
}
if node.IsLeaf() {
if bytes.Equal(nodeKey, key) {
return node, nil
} else {
return node, errors.New("key does not exist")
}
}
nodeVersion := version
if node.ID().Index() != 0 {
nodeVersion = node.ID().Version()
}
if bytes.Compare(key, nodeKey) < 0 {
// left side
rightNodePtr := node.Right()
rightNode, err := rightNodePtr.Resolve()
if err != nil {
return nil, err
}
pin := proofInnerNode{
Height: int8(node.Height()),
Size: node.Size(),
Version: int64(nodeVersion),
Left: nil,
Right: rightNode.Hash(),
}
*path = append(*path, pin)

leftNodePtr := node.Left()

leftNode, err := leftNodePtr.Resolve()
if err != nil {
return nil, err
}
n, err := pathToLeaf(leftNode, key, version, path)
return n, err
}
// right side
leftNode, err := node.Left().Resolve()
if err != nil {
return nil, err
}
pin := proofInnerNode{
Height: int8(node.Height()),
Size: node.Size(),
Version: int64(nodeVersion),
Left: leftNode.Hash(),
Right: nil,
}
*path = append(*path, pin)

rightNode, err := node.Right().Resolve()
if err != nil {
return nil, err
}

n, err := pathToLeaf(rightNode, key, version, path)
return n, err
}
Loading
Loading