From ab9ff72d2a2fe7bf776441df884ee5cb4782693f Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Mon, 19 Jun 2023 14:56:56 +0100 Subject: [PATCH 01/74] Add ics23 integration --- ibc/store/proofs_ics23.go | 143 ++++++++++++++++++++++ shared/core/types/error.go | 6 + shared/core/types/proto/commitments.proto | 11 ++ 3 files changed, 160 insertions(+) create mode 100644 ibc/store/proofs_ics23.go create mode 100644 shared/core/types/proto/commitments.proto diff --git a/ibc/store/proofs_ics23.go b/ibc/store/proofs_ics23.go new file mode 100644 index 000000000..498ad4a79 --- /dev/null +++ b/ibc/store/proofs_ics23.go @@ -0,0 +1,143 @@ +package store + +import ( + "bytes" + "crypto/sha256" + + ics23 "github.com/cosmos/ics23/go" + coreTypes "github.com/pokt-network/pocket/shared/core/types" + "github.com/pokt-network/smt" +) + +type position int + +const ( + left position = iota // 0 + right // 1 +) + +var ( + // Custom SMT spec as the store does not hash values + smtSpec *ics23.ProofSpec = &ics23.ProofSpec{ + LeafSpec: &ics23.LeafOp{ + Hash: ics23.HashOp_SHA256, + PrehashKey: ics23.HashOp_SHA256, + PrehashValue: ics23.HashOp_NO_HASH, + Length: ics23.LengthOp_NO_PREFIX, + Prefix: []byte{0}, + }, + InnerSpec: &ics23.InnerSpec{ + ChildOrder: []int32{0, 1}, + ChildSize: 32, + MinPrefixLength: 1, + MaxPrefixLength: 1, + EmptyChild: make([]byte, 32), + Hash: ics23.HashOp_SHA256, + }, + MaxDepth: 256, + PrehashKeyBeforeComparison: true, + } + innerPrefix = []byte{1} + + // defaultValue is the default placeholder value in a SparseMerkleTree + defaultValue = make([]byte, 32) +) + +// VerifyMembership verifies the CommitmentProof provided, checking whether it produces the same +// root as the one given. If it does, the key-value pair is a member of the tree +func VerifyMembership(root *coreTypes.CommitmentRoot, proof *ics23.CommitmentProof, key, value []byte) bool { + // verify the proof + return ics23.VerifyMembership(smtSpec, root.Hash, proof, key, value) +} + +// VerifyNonMembership verifies the CommitmentProof provided, checking whether it produces the same +// root as the one given. If it does, the key-value pair is not a member of the tree as the proof's +// value is either the default nil value for the SMT or an unrelated value at the path +func VerifyNonMembership(root *coreTypes.CommitmentRoot, proof *ics23.CommitmentProof, key []byte) bool { + // Verify the proof of the non-membership data doesn't belong to the key + valid := ics23.VerifyMembership(smtSpec, root.Hash, proof, key, proof.GetExist().GetValue()) + // Verify the key was actually empty + if bytes.Equal(proof.GetExist().GetValue(), defaultValue) { + return valid + } + // Verify the key was present with unrelated data + return !valid +} + +// createMembershipProof generates a CommitmentProof object verifying the membership of a key-value pair +// in the SMT provided +func createMembershipProof(tree *smt.SMT, key, value []byte) (*ics23.CommitmentProof, error) { + proof, err := tree.Prove(key) + if err != nil { + return nil, coreTypes.ErrCreatingProof(err.Error()) + } + return &ics23.CommitmentProof{ + Proof: &ics23.CommitmentProof_Exist{ + Exist: convertSMPToExistenceProof(&proof, key, value), + }, + }, nil +} + +// createNonMembershipProof generates a CommitmentProof object verifying the membership of an unrealted key at the given key in the SMT provided +func createNonMembershipProof(tree *smt.SMT, key []byte) (*ics23.CommitmentProof, error) { + proof, err := tree.Prove(key) + if err != nil { + return nil, coreTypes.ErrCreatingProof(err.Error()) + } + + value := defaultValue + if proof.NonMembershipLeafData != nil { + value = proof.NonMembershipLeafData[33:] + } + + return &ics23.CommitmentProof{ + Proof: &ics23.CommitmentProof_Exist{ + Exist: convertSMPToExistenceProof(&proof, key, value), + }, + }, nil +} + +// convertSMPToExistenceProof converts a SparseMerkleProof to an ICS23 ExistenceProof used for +// both membership and non-membership proof verification +func convertSMPToExistenceProof(proof *smt.SparseMerkleProof, key, value []byte) *ics23.ExistenceProof { + path := sha256.Sum256(key) + steps := make([]*ics23.InnerOp, 0, len(proof.SideNodes)) + for i := 0; i < len(proof.SideNodes); i++ { + var prefix, suffix []byte + prefix = append(prefix, innerPrefix...) + if getPathBit(path[:], len(proof.SideNodes)-1-i) == left { + suffix = make([]byte, 0, len(proof.SideNodes[i])) + suffix = append(suffix, proof.SideNodes[i]...) + } else { + prefix = append(prefix, proof.SideNodes[i]...) + } + op := &ics23.InnerOp{ + Hash: ics23.HashOp_SHA256, + Prefix: prefix, + Suffix: suffix, + } + steps = append(steps, op) + } + leaf := &ics23.LeafOp{ + Hash: ics23.HashOp_SHA256, + PrehashKey: ics23.HashOp_SHA256, + PrehashValue: ics23.HashOp_NO_HASH, + Length: ics23.LengthOp_NO_PREFIX, + Prefix: []byte{0}, + } + return &ics23.ExistenceProof{ + Key: key, + Value: value, + Leaf: leaf, + Path: steps, + } +} + +// getPathBit determines whether the hash of a node at a certain depth in the tree is the +// left or the right child of its parent +func getPathBit(data []byte, position int) position { + if int(data[position/8])&(1<<(8-1-uint(position)%8)) > 0 { + return right + } + return left +} diff --git a/shared/core/types/error.go b/shared/core/types/error.go index 4de0bc6ef..a7593489c 100644 --- a/shared/core/types/error.go +++ b/shared/core/types/error.go @@ -180,6 +180,7 @@ const ( CodeHostAlreadyExists Code = 138 CodeIBCInvalidID Code = 139 CodeIBCInvalidPath Code = 140 + CodeCreatingProofError Code = 141 ) const ( @@ -318,6 +319,7 @@ const ( HostAlreadyExistsError = "an ibc host already exists" IBCInvalidIDError = "invalid ibc identifier" IBCInvalidPathError = "invalid ibc path" + CreatingProofError = "an error occurred creating the CommitmentProof" ) func ErrUnknownParam(paramName string) Error { @@ -864,3 +866,7 @@ func ErrIBCInvalidID(identifier, msg string) Error { func ErrIBCInvalidPath(path string) Error { return NewError(CodeIBCInvalidPath, fmt.Sprintf("%s: %s", IBCInvalidPathError, path)) } + +func ErrCreatingProof(err error) Error { + return NewError(CodeCreatingProofError, fmt.Sprintf("%s: %s", CreatingProofError, err.Error())) +} diff --git a/shared/core/types/proto/commitments.proto b/shared/core/types/proto/commitments.proto new file mode 100644 index 000000000..aee5008b3 --- /dev/null +++ b/shared/core/types/proto/commitments.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package core; + +option go_package = "github.com/pokt-network/pocket/shared/core/types"; + +// CommitmentRoot represents the merkle root hash of a CommitmentState +// This is used in the verification of CommitmentProof objects +message CommitmentRoot { + bytes hash = 1; +} From b47402d33ec2b63daca60aa85c85e21511bbbd41 Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Mon, 19 Jun 2023 16:23:36 +0100 Subject: [PATCH 02/74] Add SMT proof conversion to ics23 Existence and Exclusion proofs with verification --- go.mod | 5 + go.sum | 2 + ibc/host/path_test.go | 18 +-- ibc/store/proofs_ics23.go | 110 +++++++------ ibc/store/proofs_ics23_test.go | 178 ++++++++++++++++++++++ shared/core/types/proto/commitments.proto | 11 -- 6 files changed, 256 insertions(+), 68 deletions(-) create mode 100644 ibc/store/proofs_ics23_test.go delete mode 100644 shared/core/types/proto/commitments.proto diff --git a/go.mod b/go.mod index 61a9ad1c1..937561931 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,9 @@ module github.com/pokt-network/pocket go 1.18 +// TECHDEBT: remove once upstream PR is merged (see: https://github.com/cosmos/ics23/pull/153) +replace github.com/cosmos/ics23/go => ../ics23/go + // TECHDEBT: remove once upstream PR is merged (see: https://github.com/regen-network/gocuke/pull/12) replace github.com/regen-network/gocuke => github.com/pokt-network/gocuke v0.0.1 @@ -21,6 +24,7 @@ require ( require ( github.com/benbjohnson/clock v1.3.0 + github.com/cosmos/ics23/go v0.10.0 github.com/deepmap/oapi-codegen v1.12.4 github.com/dgraph-io/badger/v3 v3.2103.2 github.com/foxcpp/go-mockdns v1.0.0 @@ -87,6 +91,7 @@ require ( github.com/cockroachdb/apd/v3 v3.1.0 // indirect github.com/containerd/cgroups v1.0.4 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/cosmos/gogoproto v1.4.3 // indirect github.com/cucumber/common/messages/go/v19 v19.1.2 // indirect github.com/cucumber/gherkin/go/v26 v26.0.3 // indirect github.com/cucumber/messages/go/v21 v21.0.1 // indirect diff --git a/go.sum b/go.sum index 7d7377ae4..4e8bb7827 100644 --- a/go.sum +++ b/go.sum @@ -126,6 +126,8 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cosmos/gogoproto v1.4.3 h1:RP3yyVREh9snv/lsOvmsAPQt8f44LgL281X0IOIhhcI= +github.com/cosmos/gogoproto v1.4.3/go.mod h1:0hLIG5TR7IvV1fme1HCFKjfzW9X2x0Mo+RooWXCnOWU= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= diff --git a/ibc/host/path_test.go b/ibc/host/path_test.go index 1c1c8d367..041b2a239 100644 --- a/ibc/host/path_test.go +++ b/ibc/host/path_test.go @@ -65,14 +65,14 @@ func TestPaths_CommitmentPrefix(t *testing.T) { result string }{ { - name: "Successfully applies and removes prefix to produce the same path", + // Successfully applies and removes prefix to produce the same path path: "path", prefix: coreTypes.CommitmentPrefix([]byte("test")), expected: []byte("test/path"), result: "path", }, { - name: "Fails to produce input path when given a different prefix", + // Fails to produce input path when given a different prefix path: "path", prefix: coreTypes.CommitmentPrefix([]byte("test2")), expected: []byte("test/path"), @@ -81,14 +81,12 @@ func TestPaths_CommitmentPrefix(t *testing.T) { } for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - commitment := ApplyPrefix(prefix, tc.path) - require.NotNil(t, commitment) - require.Equal(t, []byte(commitment), tc.expected) + commitment := ApplyPrefix(prefix, tc.path) + require.NotNil(t, commitment) + require.Equal(t, []byte(commitment), tc.expected) - path := RemovePrefix(tc.prefix, commitment) - require.NotNil(t, path) - require.Equal(t, path, tc.result) - }) + path := RemovePrefix(tc.prefix, commitment) + require.NotNil(t, path) + require.Equal(t, path, tc.result) } } diff --git a/ibc/store/proofs_ics23.go b/ibc/store/proofs_ics23.go index 498ad4a79..c1ab2f390 100644 --- a/ibc/store/proofs_ics23.go +++ b/ibc/store/proofs_ics23.go @@ -1,7 +1,6 @@ package store import ( - "bytes" "crypto/sha256" ics23 "github.com/cosmos/ics23/go" @@ -45,23 +44,17 @@ var ( // VerifyMembership verifies the CommitmentProof provided, checking whether it produces the same // root as the one given. If it does, the key-value pair is a member of the tree -func VerifyMembership(root *coreTypes.CommitmentRoot, proof *ics23.CommitmentProof, key, value []byte) bool { +func VerifyMembership(root ics23.CommitmentRoot, proof *ics23.CommitmentProof, key, value []byte) bool { // verify the proof - return ics23.VerifyMembership(smtSpec, root.Hash, proof, key, value) + return ics23.VerifyMembership(smtSpec, root, proof, key, value) } // VerifyNonMembership verifies the CommitmentProof provided, checking whether it produces the same // root as the one given. If it does, the key-value pair is not a member of the tree as the proof's // value is either the default nil value for the SMT or an unrelated value at the path -func VerifyNonMembership(root *coreTypes.CommitmentRoot, proof *ics23.CommitmentProof, key []byte) bool { - // Verify the proof of the non-membership data doesn't belong to the key - valid := ics23.VerifyMembership(smtSpec, root.Hash, proof, key, proof.GetExist().GetValue()) - // Verify the key was actually empty - if bytes.Equal(proof.GetExist().GetValue(), defaultValue) { - return valid - } - // Verify the key was present with unrelated data - return !valid +func VerifyNonMembership(root ics23.CommitmentRoot, proof *ics23.CommitmentProof, key []byte) bool { + // verify the proof + return ics23.VerifyNonMembership(smtSpec, root, proof, key) } // createMembershipProof generates a CommitmentProof object verifying the membership of a key-value pair @@ -69,47 +62,82 @@ func VerifyNonMembership(root *coreTypes.CommitmentRoot, proof *ics23.Commitment func createMembershipProof(tree *smt.SMT, key, value []byte) (*ics23.CommitmentProof, error) { proof, err := tree.Prove(key) if err != nil { - return nil, coreTypes.ErrCreatingProof(err.Error()) + return nil, coreTypes.ErrCreatingProof(err) } - return &ics23.CommitmentProof{ - Proof: &ics23.CommitmentProof_Exist{ - Exist: convertSMPToExistenceProof(&proof, key, value), - }, - }, nil + return convertSMPToExistenceProof(&proof, key, value), nil } // createNonMembershipProof generates a CommitmentProof object verifying the membership of an unrealted key at the given key in the SMT provided func createNonMembershipProof(tree *smt.SMT, key []byte) (*ics23.CommitmentProof, error) { proof, err := tree.Prove(key) if err != nil { - return nil, coreTypes.ErrCreatingProof(err.Error()) + return nil, coreTypes.ErrCreatingProof(err) } - value := defaultValue - if proof.NonMembershipLeafData != nil { - value = proof.NonMembershipLeafData[33:] - } + return convertSMPToExclusionProof(&proof, key), nil +} +// convertSMPToExistenceProof converts a SparseMerkleProof to an ics23 +// ExistenceProof to verify membership of an element +func convertSMPToExistenceProof(proof *smt.SparseMerkleProof, key, value []byte) *ics23.CommitmentProof { + path := sha256.Sum256(key) + steps := convertSideNodesToSteps(proof.SideNodes, path[:]) return &ics23.CommitmentProof{ Proof: &ics23.CommitmentProof_Exist{ - Exist: convertSMPToExistenceProof(&proof, key, value), + Exist: &ics23.ExistenceProof{ + Key: key, + Value: value, + Leaf: smtSpec.LeafSpec, + Path: steps, + }, }, - }, nil + } } -// convertSMPToExistenceProof converts a SparseMerkleProof to an ICS23 ExistenceProof used for -// both membership and non-membership proof verification -func convertSMPToExistenceProof(proof *smt.SparseMerkleProof, key, value []byte) *ics23.ExistenceProof { +// convertSMPToExistenceProof converts a SparseMerkleProof to an ics23 +// ExclusionProof to verify non-membership of an element +func convertSMPToExclusionProof(proof *smt.SparseMerkleProof, key []byte) *ics23.CommitmentProof { path := sha256.Sum256(key) - steps := make([]*ics23.InnerOp, 0, len(proof.SideNodes)) - for i := 0; i < len(proof.SideNodes); i++ { + steps := convertSideNodesToSteps(proof.SideNodes, path[:]) + leaf := &ics23.LeafOp{ + Hash: ics23.HashOp_SHA256, + // Do not re-hash already hashed fields from NonMembershipLeafData + PrehashKey: ics23.HashOp_NO_HASH, + PrehashValue: ics23.HashOp_NO_HASH, + Length: ics23.LengthOp_NO_PREFIX, + Prefix: []byte{0}, + } + actualPath := path[:] + actualValue := defaultValue + if proof.NonMembershipLeafData != nil { + actualPath = proof.NonMembershipLeafData[1:33] + actualValue = proof.NonMembershipLeafData[33:] + } + return &ics23.CommitmentProof{ + Proof: &ics23.CommitmentProof_Exclusion{ + Exclusion: &ics23.ExclusionProof{ + Key: key, + ActualPath: actualPath, + ActualValueHash: actualValue, + Leaf: leaf, + Path: steps, + }, + }, + } +} + +// convertSideNodesToSteps converts the SideNodes field in the SparseMerkleProof +// into a list of InnerOps for the ics23 CommitmentProof +func convertSideNodesToSteps(sideNodes [][]byte, path []byte) []*ics23.InnerOp { + steps := make([]*ics23.InnerOp, 0, len(sideNodes)) + for i := 0; i < len(sideNodes); i++ { var prefix, suffix []byte prefix = append(prefix, innerPrefix...) - if getPathBit(path[:], len(proof.SideNodes)-1-i) == left { - suffix = make([]byte, 0, len(proof.SideNodes[i])) - suffix = append(suffix, proof.SideNodes[i]...) + if getPathBit(path[:], len(sideNodes)-1-i) == left { + suffix = make([]byte, 0, len(sideNodes[i])) + suffix = append(suffix, sideNodes[i]...) } else { - prefix = append(prefix, proof.SideNodes[i]...) + prefix = append(prefix, sideNodes[i]...) } op := &ics23.InnerOp{ Hash: ics23.HashOp_SHA256, @@ -118,19 +146,7 @@ func convertSMPToExistenceProof(proof *smt.SparseMerkleProof, key, value []byte) } steps = append(steps, op) } - leaf := &ics23.LeafOp{ - Hash: ics23.HashOp_SHA256, - PrehashKey: ics23.HashOp_SHA256, - PrehashValue: ics23.HashOp_NO_HASH, - Length: ics23.LengthOp_NO_PREFIX, - Prefix: []byte{0}, - } - return &ics23.ExistenceProof{ - Key: key, - Value: value, - Leaf: leaf, - Path: steps, - } + return steps } // getPathBit determines whether the hash of a node at a certain depth in the tree is the diff --git a/ibc/store/proofs_ics23_test.go b/ibc/store/proofs_ics23_test.go new file mode 100644 index 000000000..97cd63011 --- /dev/null +++ b/ibc/store/proofs_ics23_test.go @@ -0,0 +1,178 @@ +package store + +import ( + "crypto/sha256" + "testing" + + ics23 "github.com/cosmos/ics23/go" + "github.com/pokt-network/pocket/persistence/kvstore" + "github.com/pokt-network/smt" + "github.com/stretchr/testify/require" +) + +// Proof generation cannot fail but verification can +func TestICS23Proofs_GenerateCommitmentProofs(t *testing.T) { + nodeStore := kvstore.NewMemKVStore() + tree := smt.NewSparseMerkleTree(nodeStore, sha256.New(), smt.WithValueHasher(nil)) + require.NotNil(t, tree) + + // Set a value in the store + err := tree.Update([]byte("foo"), []byte("bar")) + require.NoError(t, err) + err = tree.Update([]byte("bar"), []byte("foo")) + require.NoError(t, err) + err = tree.Update([]byte("testKey"), []byte("testValue")) + require.NoError(t, err) + err = tree.Update([]byte("testKey2"), []byte("testValue2")) + require.NoError(t, err) + + testCases := []struct { + key []byte + value []byte + nonmembership bool + fails bool + expected error + }{ + { + // Successfully generates a membership proof for a key stored + key: []byte("foo"), + value: []byte("bar"), + nonmembership: false, + fails: false, + expected: nil, + }, + { + // Successfully generates a non-membership proof for a key not stored + key: []byte("baz"), + value: []byte("testValue2"), // unrelated leaf data + nonmembership: true, + fails: false, + expected: nil, + }, + { + // Successfully generates a non-membership proof for an unset nil key + key: nil, + value: []byte("foo"), // unrelated leaf data + nonmembership: true, + fails: false, + expected: nil, + }, + } + + for _, tc := range testCases { + var proof *ics23.CommitmentProof + if tc.nonmembership { + proof, err = createNonMembershipProof(tree, tc.key) + } else { + proof, err = createMembershipProof(tree, tc.key, tc.value) + } + if tc.fails { + require.EqualError(t, err, tc.expected.Error()) + require.Nil(t, proof) + return + } + require.NoError(t, err) + require.NotNil(t, proof) + if tc.nonmembership { + require.Equal(t, tc.value, proof.GetExclusion().GetActualValueHash()) + require.NotNil(t, proof.GetExclusion().GetLeaf()) + require.NotNil(t, proof.GetExclusion().GetPath()) + } else { + require.Equal(t, tc.value, proof.GetExist().GetValue()) + require.NotNil(t, proof.GetExist().GetLeaf()) + require.NotNil(t, proof.GetExist().GetPath()) + } + } + + err = nodeStore.Stop() + require.NoError(t, err) +} + +func TestICS23Proofs_VerifyCommitmentProofs(t *testing.T) { + nodeStore := kvstore.NewMemKVStore() + tree := smt.NewSparseMerkleTree(nodeStore, sha256.New(), smt.WithValueHasher(nil)) + require.NotNil(t, tree) + + // Set a value in the store + err := tree.Update([]byte("foo"), []byte("bar")) + require.NoError(t, err) + err = tree.Update([]byte("bar"), []byte("foo")) + require.NoError(t, err) + err = tree.Update([]byte("testKey"), []byte("testValue")) + require.NoError(t, err) + err = tree.Update([]byte("testKey2"), []byte("testValue2")) + require.NoError(t, err) + + root := tree.Root() + require.NotNil(t, root) + + testCases := []struct { + key []byte + value []byte + nonmembership bool + valid bool + }{ + { + // Successfully verifies a membership proof for a key-value stored pair + key: []byte("foo"), + value: []byte("bar"), + nonmembership: false, + valid: true, + }, + { + // Successfully verifies a non-membership proof for a key-value pair not stored + key: []byte("not stored"), + value: nil, + nonmembership: true, + valid: true, + }, + { + // Fails to verify a membership proof for a key-value pair not stored + key: []byte("baz"), + value: []byte("bar"), + nonmembership: false, + valid: false, + }, + { + // Fails to verify a non-membership proof for a key stored in the tree + key: []byte("foo"), + value: nil, + nonmembership: true, + valid: false, + }, + } + + proof := new(ics23.CommitmentProof) + for _, tc := range testCases { + var err error + if tc.nonmembership { + proof, err = createNonMembershipProof(tree, tc.key) + } else { + proof, err = createMembershipProof(tree, tc.key, tc.value) + } + require.NoError(t, err) + require.NotNil(t, proof) + + if tc.nonmembership { + require.NotNil(t, proof.GetExclusion()) + } else { + require.NotNil(t, proof.GetExist()) + } + + var valid bool + if tc.nonmembership { + valid = VerifyNonMembership(root, proof, tc.key) + } else { + valid = VerifyMembership(root, proof, tc.key, tc.value) + } + + if tc.valid { + require.True(t, valid) + } else { + require.False(t, valid) + } + } + + err = nodeStore.Stop() + require.NoError(t, err) +} diff --git a/shared/core/types/proto/commitments.proto b/shared/core/types/proto/commitments.proto deleted file mode 100644 index aee5008b3..000000000 --- a/shared/core/types/proto/commitments.proto +++ /dev/null @@ -1,11 +0,0 @@ -syntax = "proto3"; - -package core; - -option go_package = "github.com/pokt-network/pocket/shared/core/types"; - -// CommitmentRoot represents the merkle root hash of a CommitmentState -// This is used in the verification of CommitmentProof objects -message CommitmentRoot { - bytes hash = 1; -} From 20fa2fc2307ce4d489bef343003bf8c4e2d881cb Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Mon, 19 Jun 2023 16:42:39 +0100 Subject: [PATCH 03/74] Add ICS23 docs --- ibc/docs/README.md | 9 +++++- ibc/docs/ics23.md | 78 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 ibc/docs/ics23.md diff --git a/ibc/docs/README.md b/ibc/docs/README.md index 26de85af7..0ea5dd98d 100644 --- a/ibc/docs/README.md +++ b/ibc/docs/README.md @@ -9,6 +9,7 @@ - [Persistence](#persistence) - [Components](#components) - [ICS-24 Host Requirements](#ics-24-host-requirements) + - [ICS-23 Vector Commitments](#ics-23-vector-commitments) ## Definitions @@ -106,6 +107,12 @@ The [IBC specification][ibc-spec] details numerous Interchain Standards (ICSs) t See: [ICS-24](./ics24.md) for more details on the specifics of the ICS-24 implementation for Pocket. +### ICS-23 Vector Commitments + +[ICS-23][ics23] defines the `CommitmentProof` type that is used to prove the membership/non-membership of a key-value pair in the IBC stores. As this type is serialisable the relayers can relay these proofs to a counterparty chain, as described above, for their light client (of the source chain) to verify the proof and thus react accordingly. In order to implement ICS-23, the `cosmos/ics23` library was used, specifically its `CommitmentProof` type and its methods to verify the proofs in a way that does not require the tree itself. + +See: [ICS-23](./ics23.md) for more details on the specifics of the ICS-23 implementation for Pocket. + [ibc-spec]: https://github.com/cosmos/ibc [ics24]: https://github.com/cosmos/ibc/blob/main/spec/core/ics-024-host-requirements/README.md -[smt]: https://github.com/pokt-network/smt +[ics23]: https://github.com/cosmos/ibc/blob/main/spec/core/ics-023-vector-commitments/README.md diff --git a/ibc/docs/ics23.md b/ibc/docs/ics23.md new file mode 100644 index 000000000..95cc2895e --- /dev/null +++ b/ibc/docs/ics23.md @@ -0,0 +1,78 @@ +# ICS-23 Vector Commitments + +- [Overview](#overview) +- [Implementation](#implementation) + - [Custom SMT `ProofSpec`](#custom-smt-proofspec) + - [Converting `SparseMerkleProof` to `CommitmentProof`](#converting-sparsemerkleproof-to-commitmentproof) + - [Proof Verification](#proof-verification) + +## Overview + +[ICS-23][ics23] defines the types and functions needed to verify membership of a key-value pair in a `CommitmentState`. As the Pocket IBC implementation uses the [SMT][smt] for its provable stores this is what is referred to as the `CommitmentState` object. Cosmos has a library `cosmos/ics23` which is already SDK agnostic and defines many of the types necessary for ICS-23. This library was able to be used _mostly_ out of the box, with some minor adjustments detailed below. + +## Implementation + +The benefit of using `cosmos/ics23` over implementing similar types ourselves is twofold: + +1. It is already SDK agnostic, so can be used by Pocket (a non-cosmos chain) without any issues. +2. The functions defined for proof verification are separate from the tree structures themselves + - This means we do not need to interact with an SMT instance in order to verify proofs. + +However, there were some changes made specifically for Pocket's implementation of ICS-23. + +### Custom SMT `ProofSpec` + +The `ProofSpec` type in `cosmos/ics23` is used to define the steps needed to verify a proof, what hashing functions should be used, any node prefixes, etc. This is then passed into the verification functions in order to verify a proof, instead of having to interact with the tree itself. This is useful as proofs must be verified on a light-client and as such being able to verify a proof without creating a tree is much more memory efficient. + +As the SMT used by Pocket Network only stores hashed values by default, the IBC stores use the `WithValueHasher(nil)` option which stores the raw bytes of the values in the tree. As such the following `ProofSpec` was created: + +```go +smtSpec *ics23.ProofSpec = &ics23.ProofSpec{ + LeafSpec: &ics23.LeafOp{ + Hash: ics23.HashOp_SHA256, + PrehashKey: ics23.HashOp_SHA256, + PrehashValue: ics23.HashOp_NO_HASH, + Length: ics23.LengthOp_NO_PREFIX, + Prefix: []byte{0}, + }, + InnerSpec: &ics23.InnerSpec{ + ChildOrder: []int32{0, 1}, + ChildSize: 32, + MinPrefixLength: 1, + MaxPrefixLength: 1, + EmptyChild: make([]byte, 32), + Hash: ics23.HashOp_SHA256, + }, + MaxDepth: 256, + PrehashKeyBeforeComparison: true, +} +``` + +The main change from the `cosmos/ics23` `SmtSpec` object is that the `PrehashValue` field is set to not hash values prior to hashing the key-value pair. + +### Converting `SparseMerkleProof` to `CommitmentProof` + +In order to convert the proofs generated by the SMT into a serialisable proof used by `cosmos/ics23`, the `SideNodes` field of the `SparseMerkleProof` must be converted into a list of `InnerOp` types which define the order of the hashes. The order of the hashes is important as depending on whether the next hash is the left or right neighbour of the current hash, they will be hashed in a different order, ultimately creating a different root hash. This conversion allows the verification to produce the same root hash as the SMT would have produced when verifying the proof. + +As `SparseMerkleProof` objects represent both inclusion and exclusion proofs as defined in the [JMT whitepaper][jmt] the conversion step will convert the SMT proof into either a `ExistenceProof` or `ExclusionProof` as defined in `cosmos/ics23`. + +### Proof Verification + +When verifying membership of an element the logic is as follows: + +1. Use the key-value pair to generate a leaf hash +2. Hash the leaf with the `SideNodes` found in the `path` field of the `ExistenceProof` to generate the root hash +3. Compare the root hash with the one provided + +For non-membership the logic is as follows: + +1. If the `ActualValueHash` field in the `ExclusionProof` is the SMT's placeholder value, then use the placeholder value as the leaf node hash and follow steps 3-4 +2. If the `ActualValueHash` field is not the placeholder value, then use the `ActualPath` and `ActualValueHash` fields to generate the leaf node hash - do not hash these values before hashing the node as they are populated from the SMT proof's `NonMembershipLeafData` field and thus are already hashed +3. Hash the leaf node hash with the `SideNodes` found in the `Path` field of the `ExclusionProof` to generate the root hash +4. Compare the root hash with the one provided + +The full implementation of this logic can be found [here](../store/proofs_ics23.go) as well as in the `cosmos/ics23` [library](https://github.com/h5law/ics23/blob/56d948cafb83ded78dc4b9de3c8b04582734851a/go/proof.go#L171). + +[ics23]: https://github.com/cosmos/ibc/blob/main/spec/core/ics-023-vector-commitments/README.md +[smt]: https://github.com/pokt-network/smt +[jmt]: https://developers.diem.com/papers/jellyfish-merkle-tree/2021-01-14.pdf From 52e62e4ad68914c4b7833d677731032d2e3b754a Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Wed, 21 Jun 2023 13:18:23 +0100 Subject: [PATCH 04/74] Fix errors --- go.mod | 2 +- go.sum | 2 ++ ibc/store/proofs_ics23.go | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 937561931..28281e5ed 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/pokt-network/pocket go 1.18 // TECHDEBT: remove once upstream PR is merged (see: https://github.com/cosmos/ics23/pull/153) -replace github.com/cosmos/ics23/go => ../ics23/go +replace github.com/cosmos/ics23/go => github.com/h5law/ics23/go v0.0.0-20230619152251-56d948cafb83 // TECHDEBT: remove once upstream PR is merged (see: https://github.com/regen-network/gocuke/pull/12) replace github.com/regen-network/gocuke => github.com/pokt-network/gocuke v0.0.1 diff --git a/go.sum b/go.sum index 4e8bb7827..271c59bc7 100644 --- a/go.sum +++ b/go.sum @@ -358,6 +358,8 @@ github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQ github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/h5law/ics23/go v0.0.0-20230619152251-56d948cafb83 h1:uG97IfYQttG5iVt/jHK2wnGZgKUxHUjnzAlWY6EDso8= +github.com/h5law/ics23/go v0.0.0-20230619152251-56d948cafb83/go.mod h1:ZfJSmng/TBNTBkFemHHHj5YY7VAU/MBU980F4VU1NG0= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/ibc/store/proofs_ics23.go b/ibc/store/proofs_ics23.go index c1ab2f390..1ae2675e5 100644 --- a/ibc/store/proofs_ics23.go +++ b/ibc/store/proofs_ics23.go @@ -133,7 +133,7 @@ func convertSideNodesToSteps(sideNodes [][]byte, path []byte) []*ics23.InnerOp { for i := 0; i < len(sideNodes); i++ { var prefix, suffix []byte prefix = append(prefix, innerPrefix...) - if getPathBit(path[:], len(sideNodes)-1-i) == left { + if getPathBit(path, len(sideNodes)-1-i) == left { suffix = make([]byte, 0, len(sideNodes[i])) suffix = append(suffix, sideNodes[i]...) } else { From 06566581aef0ab80b2f4ff87845e75936e914633 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Tue, 27 Jun 2023 09:20:53 +0100 Subject: [PATCH 05/74] Update next error comment --- shared/core/types/error.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/core/types/error.go b/shared/core/types/error.go index a7593489c..870b6b733 100644 --- a/shared/core/types/error.go +++ b/shared/core/types/error.go @@ -38,7 +38,7 @@ func NewError(code Code, msg string) Error { } } -// NextCode: 141 +// NextCode: 142 type Code float64 // CONSIDERATION: Should these be a proto enum or a golang iota? //nolint:gosec // G101 - Not hard-coded credentials From d631bb3c9676f165ef2d99cca8a1fc0d66478e0a Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Tue, 27 Jun 2023 09:50:50 +0100 Subject: [PATCH 06/74] Address comments in docs --- ibc/docs/README.md | 2 +- ibc/docs/ics23.md | 47 ++++++++++++++++++++++++++++------------------ 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/ibc/docs/README.md b/ibc/docs/README.md index 0ea5dd98d..44c1ac1d6 100644 --- a/ibc/docs/README.md +++ b/ibc/docs/README.md @@ -109,7 +109,7 @@ See: [ICS-24](./ics24.md) for more details on the specifics of the ICS-24 implem ### ICS-23 Vector Commitments -[ICS-23][ics23] defines the `CommitmentProof` type that is used to prove the membership/non-membership of a key-value pair in the IBC stores. As this type is serialisable the relayers can relay these proofs to a counterparty chain, as described above, for their light client (of the source chain) to verify the proof and thus react accordingly. In order to implement ICS-23, the `cosmos/ics23` library was used, specifically its `CommitmentProof` type and its methods to verify the proofs in a way that does not require the tree itself. +[ICS-23][ics23] defines the `CommitmentProof` type that is used to prove the membership/non-membership of a key-value pair in the IBC stores. As this type is serialisable the relayers can relay these proofs to a counterparty chain, as described above, for their ibc specific light client (of the source chain) to verify the proof and thus react accordingly. In order to implement ICS-23, the `cosmos/ics23` library was used, specifically its `CommitmentProof` type and its methods to verify the proofs in a way that does not require the tree itself. See: [ICS-23](./ics23.md) for more details on the specifics of the ICS-23 implementation for Pocket. diff --git a/ibc/docs/ics23.md b/ibc/docs/ics23.md index 95cc2895e..6f6839132 100644 --- a/ibc/docs/ics23.md +++ b/ibc/docs/ics23.md @@ -2,29 +2,37 @@ - [Overview](#overview) - [Implementation](#implementation) - - [Custom SMT `ProofSpec`](#custom-smt-proofspec) - - [Converting `SparseMerkleProof` to `CommitmentProof`](#converting-sparsemerkleproof-to-commitmentproof) - - [Proof Verification](#proof-verification) + - [Custom SMT `ProofSpec`](#custom-smt-proofspec) + - [Converting `SparseMerkleProof` to `CommitmentProof`](#converting-sparsemerkleproof-to-commitmentproof) + - [Proof Verification](#proof-verification) ## Overview -[ICS-23][ics23] defines the types and functions needed to verify membership of a key-value pair in a `CommitmentState`. As the Pocket IBC implementation uses the [SMT][smt] for its provable stores this is what is referred to as the `CommitmentState` object. Cosmos has a library `cosmos/ics23` which is already SDK agnostic and defines many of the types necessary for ICS-23. This library was able to be used _mostly_ out of the box, with some minor adjustments detailed below. +[ICS-23][ics23] defines the types and functions needed to verify membership of a key-value pair in a `CommitmentState`. As the Pocket IBC implementation uses the [SMT][smt] for its provable stores, this is referred to as the `CommitmentState` object. Cosmos has a library `cosmos/ics23` which is already SDK agnostic and defines many of the types necessary for ICS-23. This library was able to be used _mostly_ out of the box, with some minor adjustments detailed below. ## Implementation The benefit of using `cosmos/ics23` over implementing similar types ourselves is twofold: -1. It is already SDK agnostic, so can be used by Pocket (a non-cosmos chain) without any issues. -2. The functions defined for proof verification are separate from the tree structures themselves - - This means we do not need to interact with an SMT instance in order to verify proofs. +1. It is already SDK agnostic, so can be used by Pocket (a non-cosmos chain) without any issues or major changes. +2. The functions defined for proof verification are decoupled from the underlying tree structure, meaning proof verification is tree agnostic. However, there were some changes made specifically for Pocket's implementation of ICS-23. +See: [`cosmos/ics23` #152](https://github.com/cosmos/ics23/issues/152) and [`cosmos/ics23` #153](https://github.com/cosmos/ics23/pull/153ß) for the details of the changes made to allow for `ExclusionProof` verification. + ### Custom SMT `ProofSpec` -The `ProofSpec` type in `cosmos/ics23` is used to define the steps needed to verify a proof, what hashing functions should be used, any node prefixes, etc. This is then passed into the verification functions in order to verify a proof, instead of having to interact with the tree itself. This is useful as proofs must be verified on a light-client and as such being able to verify a proof without creating a tree is much more memory efficient. +The `ProofSpec` type in `cosmos/ics23` is used to define: + +1. The steps needed to verify a proof +2. The hash functions used +3. Node prefixes +4. Etc... + +The `ProofSpec` is then passed into the verification functions in order to verify a proof instead of having to interact with the tree itself. This is useful as proofs must be verified via an (IBC) light client, and as such being able to verify a proof without reconstructing a tree is much more memory efficient. -As the SMT used by Pocket Network only stores hashed values by default, the IBC stores use the `WithValueHasher(nil)` option which stores the raw bytes of the values in the tree. As such the following `ProofSpec` was created: +As the SMT used by Pocket Network only stores hashed values by default, the IBC store uses the `WithValueHasher(nil)` option which stores the source value (as raw bytes) in the tree. The following `ProofSpec` was created to support this: ```go smtSpec *ics23.ProofSpec = &ics23.ProofSpec{ @@ -33,14 +41,14 @@ smtSpec *ics23.ProofSpec = &ics23.ProofSpec{ PrehashKey: ics23.HashOp_SHA256, PrehashValue: ics23.HashOp_NO_HASH, Length: ics23.LengthOp_NO_PREFIX, - Prefix: []byte{0}, + Prefix: []byte{0}, }, InnerSpec: &ics23.InnerSpec{ ChildOrder: []int32{0, 1}, ChildSize: 32, MinPrefixLength: 1, MaxPrefixLength: 1, - EmptyChild: make([]byte, 32), + EmptyChild: make([]byte, 32), Hash: ics23.HashOp_SHA256, }, MaxDepth: 256, @@ -48,28 +56,31 @@ smtSpec *ics23.ProofSpec = &ics23.ProofSpec{ } ``` -The main change from the `cosmos/ics23` `SmtSpec` object is that the `PrehashValue` field is set to not hash values prior to hashing the key-value pair. +The main difference from the `cosmos/ics23` `SmtSpec` object is that the `PrehashValue` field is set to not hash values before hashing the key-value pair. ### Converting `SparseMerkleProof` to `CommitmentProof` In order to convert the proofs generated by the SMT into a serialisable proof used by `cosmos/ics23`, the `SideNodes` field of the `SparseMerkleProof` must be converted into a list of `InnerOp` types which define the order of the hashes. The order of the hashes is important as depending on whether the next hash is the left or right neighbour of the current hash, they will be hashed in a different order, ultimately creating a different root hash. This conversion allows the verification to produce the same root hash as the SMT would have produced when verifying the proof. -As `SparseMerkleProof` objects represent both inclusion and exclusion proofs as defined in the [JMT whitepaper][jmt] the conversion step will convert the SMT proof into either a `ExistenceProof` or `ExclusionProof` as defined in `cosmos/ics23`. +As `SparseMerkleProof` objects represent both inclusion and exclusion proofs as defined in the [JMT whitepaper][jmt]. The conversion step will convert the SMT proof into either an `ExistenceProof` or `ExclusionProof` as defined in `cosmos/ics23`. ### Proof Verification -When verifying membership of an element the logic is as follows: +Membership proofs are verified as follows: 1. Use the key-value pair to generate a leaf hash 2. Hash the leaf with the `SideNodes` found in the `path` field of the `ExistenceProof` to generate the root hash -3. Compare the root hash with the one provided +3. Compare the root hash with the one provided and expect them to be identical -For non-membership the logic is as follows: +Non-membership proofs are verified as follows: -1. If the `ActualValueHash` field in the `ExclusionProof` is the SMT's placeholder value, then use the placeholder value as the leaf node hash and follow steps 3-4 -2. If the `ActualValueHash` field is not the placeholder value, then use the `ActualPath` and `ActualValueHash` fields to generate the leaf node hash - do not hash these values before hashing the node as they are populated from the SMT proof's `NonMembershipLeafData` field and thus are already hashed +1. If the `ActualValueHash` field in the `ExclusionProof` is the SMT's placeholder value, then use the placeholder value as the leaf node hash and skip to step 3 below +2. If the `ActualValueHash` field is not the placeholder value, then use the `ActualPath` and `ActualValueHash` fields to generate the leaf node hash. + - **IMPORTANT**: DO NOT hash these values before hashing the node as they are populated from the SMT proof's `NonMembershipLeafData` field and thus are already hashed 3. Hash the leaf node hash with the `SideNodes` found in the `Path` field of the `ExclusionProof` to generate the root hash 4. Compare the root hash with the one provided + - As the non-membership proof uses the `ActualValueHash` field to generate the leaf node hash, the non-membership proof is actually proving membership of either a placeholder key or an unrelated key in the tree. + - This means that if the root hash computed and the one provided are equal then the key we were looking for was not in the tree. If they are not equal the proof is invalid and the key is in the tree. The full implementation of this logic can be found [here](../store/proofs_ics23.go) as well as in the `cosmos/ics23` [library](https://github.com/h5law/ics23/blob/56d948cafb83ded78dc4b9de3c8b04582734851a/go/proof.go#L171). From 1beb4bdb83a33cc1b3e7bf9a22617ee36b0f5fcd Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Tue, 27 Jun 2023 09:54:18 +0100 Subject: [PATCH 07/74] Add names back to tests --- ibc/host/path_test.go | 18 ++--- ibc/store/proofs_ics23_test.go | 118 +++++++++++++++++---------------- 2 files changed, 72 insertions(+), 64 deletions(-) diff --git a/ibc/host/path_test.go b/ibc/host/path_test.go index 041b2a239..1c1c8d367 100644 --- a/ibc/host/path_test.go +++ b/ibc/host/path_test.go @@ -65,14 +65,14 @@ func TestPaths_CommitmentPrefix(t *testing.T) { result string }{ { - // Successfully applies and removes prefix to produce the same path + name: "Successfully applies and removes prefix to produce the same path", path: "path", prefix: coreTypes.CommitmentPrefix([]byte("test")), expected: []byte("test/path"), result: "path", }, { - // Fails to produce input path when given a different prefix + name: "Fails to produce input path when given a different prefix", path: "path", prefix: coreTypes.CommitmentPrefix([]byte("test2")), expected: []byte("test/path"), @@ -81,12 +81,14 @@ func TestPaths_CommitmentPrefix(t *testing.T) { } for _, tc := range testCases { - commitment := ApplyPrefix(prefix, tc.path) - require.NotNil(t, commitment) - require.Equal(t, []byte(commitment), tc.expected) + t.Run(tc.name, func(t *testing.T) { + commitment := ApplyPrefix(prefix, tc.path) + require.NotNil(t, commitment) + require.Equal(t, []byte(commitment), tc.expected) - path := RemovePrefix(tc.prefix, commitment) - require.NotNil(t, path) - require.Equal(t, path, tc.result) + path := RemovePrefix(tc.prefix, commitment) + require.NotNil(t, path) + require.Equal(t, path, tc.result) + }) } } diff --git a/ibc/store/proofs_ics23_test.go b/ibc/store/proofs_ics23_test.go index 97cd63011..cff486b9d 100644 --- a/ibc/store/proofs_ics23_test.go +++ b/ibc/store/proofs_ics23_test.go @@ -27,6 +27,7 @@ func TestICS23Proofs_GenerateCommitmentProofs(t *testing.T) { require.NoError(t, err) testCases := []struct { + name string key []byte value []byte nonmembership bool @@ -34,7 +35,7 @@ func TestICS23Proofs_GenerateCommitmentProofs(t *testing.T) { expected error }{ { - // Successfully generates a membership proof for a key stored + name: "Successfully generates a membership proof for a key stored", key: []byte("foo"), value: []byte("bar"), nonmembership: false, @@ -42,7 +43,7 @@ func TestICS23Proofs_GenerateCommitmentProofs(t *testing.T) { expected: nil, }, { - // Successfully generates a non-membership proof for a key not stored + name: "Successfully generates a non-membership proof for a key not stored", key: []byte("baz"), value: []byte("testValue2"), // unrelated leaf data nonmembership: true, @@ -50,7 +51,7 @@ func TestICS23Proofs_GenerateCommitmentProofs(t *testing.T) { expected: nil, }, { - // Successfully generates a non-membership proof for an unset nil key + name: "Successfully generates a non-membership proof for an unset nil key", key: nil, value: []byte("foo"), // unrelated leaf data nonmembership: true, @@ -60,28 +61,30 @@ func TestICS23Proofs_GenerateCommitmentProofs(t *testing.T) { } for _, tc := range testCases { - var proof *ics23.CommitmentProof - if tc.nonmembership { - proof, err = createNonMembershipProof(tree, tc.key) - } else { - proof, err = createMembershipProof(tree, tc.key, tc.value) - } - if tc.fails { - require.EqualError(t, err, tc.expected.Error()) - require.Nil(t, proof) - return - } - require.NoError(t, err) - require.NotNil(t, proof) - if tc.nonmembership { - require.Equal(t, tc.value, proof.GetExclusion().GetActualValueHash()) - require.NotNil(t, proof.GetExclusion().GetLeaf()) - require.NotNil(t, proof.GetExclusion().GetPath()) - } else { - require.Equal(t, tc.value, proof.GetExist().GetValue()) - require.NotNil(t, proof.GetExist().GetLeaf()) - require.NotNil(t, proof.GetExist().GetPath()) - } + t.Run(tc.name, func(t *testing.T) { + var proof *ics23.CommitmentProof + if tc.nonmembership { + proof, err = createNonMembershipProof(tree, tc.key) + } else { + proof, err = createMembershipProof(tree, tc.key, tc.value) + } + if tc.fails { + require.EqualError(t, err, tc.expected.Error()) + require.Nil(t, proof) + return + } + require.NoError(t, err) + require.NotNil(t, proof) + if tc.nonmembership { + require.Equal(t, tc.value, proof.GetExclusion().GetActualValueHash()) + require.NotNil(t, proof.GetExclusion().GetLeaf()) + require.NotNil(t, proof.GetExclusion().GetPath()) + } else { + require.Equal(t, tc.value, proof.GetExist().GetValue()) + require.NotNil(t, proof.GetExist().GetLeaf()) + require.NotNil(t, proof.GetExist().GetPath()) + } + }) } err = nodeStore.Stop() @@ -107,34 +110,35 @@ func TestICS23Proofs_VerifyCommitmentProofs(t *testing.T) { require.NotNil(t, root) testCases := []struct { + name string key []byte value []byte nonmembership bool valid bool }{ { - // Successfully verifies a membership proof for a key-value stored pair + name: "Successfully verifies a membership proof for a key-value stored pair", key: []byte("foo"), value: []byte("bar"), nonmembership: false, valid: true, }, { - // Successfully verifies a non-membership proof for a key-value pair not stored + name: "Successfully verifies a non-membership proof for a key-value pair not stored", key: []byte("not stored"), value: nil, nonmembership: true, valid: true, }, { - // Fails to verify a membership proof for a key-value pair not stored + name: "Fails to verify a membership proof for a key-value pair not stored", key: []byte("baz"), value: []byte("bar"), nonmembership: false, valid: false, }, { - // Fails to verify a non-membership proof for a key stored in the tree + name: "Fails to verify a non-membership proof for a key stored in the tree", key: []byte("foo"), value: nil, nonmembership: true, @@ -144,33 +148,35 @@ func TestICS23Proofs_VerifyCommitmentProofs(t *testing.T) { proof := new(ics23.CommitmentProof) for _, tc := range testCases { - var err error - if tc.nonmembership { - proof, err = createNonMembershipProof(tree, tc.key) - } else { - proof, err = createMembershipProof(tree, tc.key, tc.value) - } - require.NoError(t, err) - require.NotNil(t, proof) - - if tc.nonmembership { - require.NotNil(t, proof.GetExclusion()) - } else { - require.NotNil(t, proof.GetExist()) - } - - var valid bool - if tc.nonmembership { - valid = VerifyNonMembership(root, proof, tc.key) - } else { - valid = VerifyMembership(root, proof, tc.key, tc.value) - } - - if tc.valid { - require.True(t, valid) - } else { - require.False(t, valid) - } + t.Run(tc.name, func(t *testing.T) { + var err error + if tc.nonmembership { + proof, err = createNonMembershipProof(tree, tc.key) + } else { + proof, err = createMembershipProof(tree, tc.key, tc.value) + } + require.NoError(t, err) + require.NotNil(t, proof) + + if tc.nonmembership { + require.NotNil(t, proof.GetExclusion()) + } else { + require.NotNil(t, proof.GetExist()) + } + + var valid bool + if tc.nonmembership { + valid = VerifyNonMembership(root, proof, tc.key) + } else { + valid = VerifyMembership(root, proof, tc.key, tc.value) + } + + if tc.valid { + require.True(t, valid) + } else { + require.False(t, valid) + } + }) } err = nodeStore.Stop() From 9a7a578edbcf70899d7a88941f58b612178e7e21 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Tue, 27 Jun 2023 10:24:23 +0100 Subject: [PATCH 08/74] Address comments --- ibc/store/proofs_ics23.go | 38 ++++++++--- ibc/store/proofs_ics23_test.go | 114 ++++++++++++++++----------------- 2 files changed, 83 insertions(+), 69 deletions(-) diff --git a/ibc/store/proofs_ics23.go b/ibc/store/proofs_ics23.go index 1ae2675e5..1d209f3a6 100644 --- a/ibc/store/proofs_ics23.go +++ b/ibc/store/proofs_ics23.go @@ -8,11 +8,14 @@ import ( "github.com/pokt-network/smt" ) +// position refers to whether the node is either the left or right child of its parent +// Ref: https://github.com/pokt-network/smt/blob/main/types.go type position int const ( - left position = iota // 0 - right // 1 + left position = iota // 0 + right // 1 + hashSize = 32 ) var ( @@ -27,10 +30,10 @@ var ( }, InnerSpec: &ics23.InnerSpec{ ChildOrder: []int32{0, 1}, - ChildSize: 32, + ChildSize: hashSize, MinPrefixLength: 1, MaxPrefixLength: 1, - EmptyChild: make([]byte, 32), + EmptyChild: make([]byte, hashSize), Hash: ics23.HashOp_SHA256, }, MaxDepth: 256, @@ -39,7 +42,7 @@ var ( innerPrefix = []byte{1} // defaultValue is the default placeholder value in a SparseMerkleTree - defaultValue = make([]byte, 32) + defaultValue = make([]byte, hashSize) ) // VerifyMembership verifies the CommitmentProof provided, checking whether it produces the same @@ -94,7 +97,7 @@ func convertSMPToExistenceProof(proof *smt.SparseMerkleProof, key, value []byte) } } -// convertSMPToExistenceProof converts a SparseMerkleProof to an ics23 +// convertSMPToExclusionProof converts a SparseMerkleProof to an ics23 // ExclusionProof to verify non-membership of an element func convertSMPToExclusionProof(proof *smt.SparseMerkleProof, key []byte) *ics23.CommitmentProof { path := sha256.Sum256(key) @@ -110,8 +113,8 @@ func convertSMPToExclusionProof(proof *smt.SparseMerkleProof, key []byte) *ics23 actualPath := path[:] actualValue := defaultValue if proof.NonMembershipLeafData != nil { - actualPath = proof.NonMembershipLeafData[1:33] - actualValue = proof.NonMembershipLeafData[33:] + actualPath = proof.NonMembershipLeafData[1 : 1+hashSize] // len(prefix): len(prefix) + hashSize + actualValue = proof.NonMembershipLeafData[1+hashSize:] } return &ics23.CommitmentProof{ Proof: &ics23.CommitmentProof_Exclusion{ @@ -134,9 +137,11 @@ func convertSideNodesToSteps(sideNodes [][]byte, path []byte) []*ics23.InnerOp { var prefix, suffix []byte prefix = append(prefix, innerPrefix...) if getPathBit(path, len(sideNodes)-1-i) == left { + // path is on the left so sidenode must be on the right suffix = make([]byte, 0, len(sideNodes[i])) suffix = append(suffix, sideNodes[i]...) } else { + // path is on the right so sidenode must be on the left prefix = append(prefix, sideNodes[i]...) } op := &ics23.InnerOp{ @@ -149,9 +154,22 @@ func convertSideNodesToSteps(sideNodes [][]byte, path []byte) []*ics23.InnerOp { return steps } -// getPathBit determines whether the hash of a node at a certain depth in the tree is the -// left or the right child of its parent +// getPathBit takes the hash of a key (the path) and a position (depth) and returns whether at +// that position in the tree the path goes left or right. This is used to determine the order +// of child nodes and the order in which they are hashed when verifying proofs. +// Ref: https://github.com/pokt-network/smt/blob/main/utils.go func getPathBit(data []byte, position int) position { + // get the byte at the position and then left shift one by the offset of the position + // from the leftmost bit in the byte. Check if the bitwise and is the same + // Path: []byte{ {0 1 0 1 1 0 1 0}, {0 1 1 0 1 1 0 1}, {1 0 0 1 0 0 1 0} } (length = 24 bits / 3 bytes) + // Position: 13 - 13/8=1 + // Path[1] = {0 1 1 0 1 1 0 1} + // uint(13)%8 = 5, 8-1-5=2 + // 00000001 << 2 = 00000100 + // {0 1 1 0 1 1 0 1} + // & {0 0 0 0 0 1 0 0} + // = {0 0 0 0 0 1 0 0} + // > 0 so Path is on the right at position 13 if int(data[position/8])&(1<<(8-1-uint(position)%8)) > 0 { return right } diff --git a/ibc/store/proofs_ics23_test.go b/ibc/store/proofs_ics23_test.go index cff486b9d..0695ac681 100644 --- a/ibc/store/proofs_ics23_test.go +++ b/ibc/store/proofs_ics23_test.go @@ -27,43 +27,43 @@ func TestICS23Proofs_GenerateCommitmentProofs(t *testing.T) { require.NoError(t, err) testCases := []struct { - name string - key []byte - value []byte - nonmembership bool - fails bool - expected error + name string + key []byte + value []byte + isNonMembership bool + fails bool + expected error }{ { - name: "Successfully generates a membership proof for a key stored", - key: []byte("foo"), - value: []byte("bar"), - nonmembership: false, - fails: false, - expected: nil, + name: "Successfully generates a membership proof for a key stored", + key: []byte("foo"), + value: []byte("bar"), + isNonMembership: false, + fails: false, + expected: nil, }, { - name: "Successfully generates a non-membership proof for a key not stored", - key: []byte("baz"), - value: []byte("testValue2"), // unrelated leaf data - nonmembership: true, - fails: false, - expected: nil, + name: "Successfully generates a non-membership proof for a key not stored", + key: []byte("baz"), + value: []byte("testValue2"), // unrelated leaf data + isNonMembership: true, + fails: false, + expected: nil, }, { - name: "Successfully generates a non-membership proof for an unset nil key", - key: nil, - value: []byte("foo"), // unrelated leaf data - nonmembership: true, - fails: false, - expected: nil, + name: "Successfully generates a non-membership proof for an unset nil key", + key: nil, + value: []byte("foo"), // unrelated leaf data + isNonMembership: true, + fails: false, + expected: nil, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { var proof *ics23.CommitmentProof - if tc.nonmembership { + if tc.isNonMembership { proof, err = createNonMembershipProof(tree, tc.key) } else { proof, err = createMembershipProof(tree, tc.key, tc.value) @@ -75,7 +75,7 @@ func TestICS23Proofs_GenerateCommitmentProofs(t *testing.T) { } require.NoError(t, err) require.NotNil(t, proof) - if tc.nonmembership { + if tc.isNonMembership { require.Equal(t, tc.value, proof.GetExclusion().GetActualValueHash()) require.NotNil(t, proof.GetExclusion().GetLeaf()) require.NotNil(t, proof.GetExclusion().GetPath()) @@ -110,39 +110,39 @@ func TestICS23Proofs_VerifyCommitmentProofs(t *testing.T) { require.NotNil(t, root) testCases := []struct { - name string - key []byte - value []byte - nonmembership bool - valid bool + name string + key []byte + value []byte + isNonMembership bool + valid bool }{ { - name: "Successfully verifies a membership proof for a key-value stored pair", - key: []byte("foo"), - value: []byte("bar"), - nonmembership: false, - valid: true, + name: "Successfully verifies a membership proof for a key-value stored pair", + key: []byte("foo"), + value: []byte("bar"), + isNonMembership: false, + valid: true, }, { - name: "Successfully verifies a non-membership proof for a key-value pair not stored", - key: []byte("not stored"), - value: nil, - nonmembership: true, - valid: true, + name: "Successfully verifies a non-membership proof for a key-value pair not stored", + key: []byte("not stored"), + value: nil, + isNonMembership: true, + valid: true, }, { - name: "Fails to verify a membership proof for a key-value pair not stored", - key: []byte("baz"), - value: []byte("bar"), - nonmembership: false, - valid: false, + name: "Fails to verify a membership proof for a key-value pair not stored", + key: []byte("baz"), + value: []byte("bar"), + isNonMembership: false, + valid: false, }, { - name: "Fails to verify a non-membership proof for a key stored in the tree", - key: []byte("foo"), - value: nil, - nonmembership: true, - valid: false, + name: "Fails to verify a non-membership proof for a key stored in the tree", + key: []byte("foo"), + value: nil, + isNonMembership: true, + valid: false, }, } @@ -150,7 +150,7 @@ func TestICS23Proofs_VerifyCommitmentProofs(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { var err error - if tc.nonmembership { + if tc.isNonMembership { proof, err = createNonMembershipProof(tree, tc.key) } else { proof, err = createMembershipProof(tree, tc.key, tc.value) @@ -158,24 +158,20 @@ func TestICS23Proofs_VerifyCommitmentProofs(t *testing.T) { require.NoError(t, err) require.NotNil(t, proof) - if tc.nonmembership { + if tc.isNonMembership { require.NotNil(t, proof.GetExclusion()) } else { require.NotNil(t, proof.GetExist()) } var valid bool - if tc.nonmembership { + if tc.isNonMembership { valid = VerifyNonMembership(root, proof, tc.key) } else { valid = VerifyMembership(root, proof, tc.key, tc.value) } - if tc.valid { - require.True(t, valid) - } else { - require.False(t, valid) - } + require.Equal(t, tc.valid, valid) }) } From 327ed964fe66b2d7f016d4203e4bb6329ba721df Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 11:11:34 +0100 Subject: [PATCH 09/74] Address comments --- ibc/docs/README.md | 1 + ibc/docs/ics23.md | 35 ++++++++++++++++++++++++++++++---- ibc/store/proofs_ics23.go | 2 ++ ibc/store/proofs_ics23_test.go | 4 ++-- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/ibc/docs/README.md b/ibc/docs/README.md index 44c1ac1d6..01a04c4ed 100644 --- a/ibc/docs/README.md +++ b/ibc/docs/README.md @@ -116,3 +116,4 @@ See: [ICS-23](./ics23.md) for more details on the specifics of the ICS-23 implem [ibc-spec]: https://github.com/cosmos/ibc [ics24]: https://github.com/cosmos/ibc/blob/main/spec/core/ics-024-host-requirements/README.md [ics23]: https://github.com/cosmos/ibc/blob/main/spec/core/ics-023-vector-commitments/README.md +[smt]: https://github.com/pokt-network/smt diff --git a/ibc/docs/ics23.md b/ibc/docs/ics23.md index 6f6839132..b6c5ff943 100644 --- a/ibc/docs/ics23.md +++ b/ibc/docs/ics23.md @@ -74,13 +74,40 @@ Membership proofs are verified as follows: Non-membership proofs are verified as follows: -1. If the `ActualValueHash` field in the `ExclusionProof` is the SMT's placeholder value, then use the placeholder value as the leaf node hash and skip to step 3 below -2. If the `ActualValueHash` field is not the placeholder value, then use the `ActualPath` and `ActualValueHash` fields to generate the leaf node hash. +1. If the `ActualValueHash` field in the `ExclusionProof` is the SMT's placeholder value (`[32]byte`, i.e. the key is not set in the tree), then use the placeholder value as the leaf node hash and skip to step 3 below +2. If the `ActualValueHash` field is not the placeholder value, then use the `ActualPath` and `ActualValueHash` fields (provided via `NonMembershipLeafData`) to generate the leaf node hash. - **IMPORTANT**: DO NOT hash these values before hashing the node as they are populated from the SMT proof's `NonMembershipLeafData` field and thus are already hashed 3. Hash the leaf node hash with the `SideNodes` found in the `Path` field of the `ExclusionProof` to generate the root hash 4. Compare the root hash with the one provided - - As the non-membership proof uses the `ActualValueHash` field to generate the leaf node hash, the non-membership proof is actually proving membership of either a placeholder key or an unrelated key in the tree. - - This means that if the root hash computed and the one provided are equal then the key we were looking for was not in the tree. If they are not equal the proof is invalid and the key is in the tree. + - if `computedRootHash == providedRootHash` + - `key` not in tree -> `Proof` is valid -> exclusion QED + - if `computedRootHash != providedRootHash` + - `key` is in tree -> `Proof` is invalid -> exclusion QED + +```mermaid +flowchart TD + I["Proof,Key"] + NMD{"proof.NonMembershipLeafData == nil ?"} + KP1["actualPath = sha256(key) \n actualValue = placeholder\ncurrentHash = [32]byte"] + KP2["actualPath = ProvidedKeyHash \n actualValue = ProvidedValueHash\ncurrentHash = sha256([]byte{0}+actualPath+actualValueHash)"] + C["nextHash = sha256(currentHash+sideNodeHash)"] + Compare{"ComputedRootHash == ProvidedRootHash ?"} + EV["Exclusion Prove VALID"] + EI["Exclusion Prove INVALID"] + + I --> NMD + NMD -- Yes --> KP1 + NMD -- No --> KP2 + + KP1 -- CurrentHash --> C + KP2 -- CurrentHash --> C + + C -- while NextSideNode != nil --> C + + C -- ComputedRootHash --> Compare + Compare -- Yes --> EV + Compare -- No --> EI +``` The full implementation of this logic can be found [here](../store/proofs_ics23.go) as well as in the `cosmos/ics23` [library](https://github.com/h5law/ics23/blob/56d948cafb83ded78dc4b9de3c8b04582734851a/go/proof.go#L171). diff --git a/ibc/store/proofs_ics23.go b/ibc/store/proofs_ics23.go index 1d209f3a6..a7930dd1c 100644 --- a/ibc/store/proofs_ics23.go +++ b/ibc/store/proofs_ics23.go @@ -9,6 +9,7 @@ import ( ) // position refers to whether the node is either the left or right child of its parent +// for the binary SMT // Ref: https://github.com/pokt-network/smt/blob/main/types.go type position int @@ -154,6 +155,7 @@ func convertSideNodesToSteps(sideNodes [][]byte, path []byte) []*ics23.InnerOp { return steps } +// TECHDEBT(smt #14): Use exposed method from SMT library // getPathBit takes the hash of a key (the path) and a position (depth) and returns whether at // that position in the tree the path goes left or right. This is used to determine the order // of child nodes and the order in which they are hashed when verifying proofs. diff --git a/ibc/store/proofs_ics23_test.go b/ibc/store/proofs_ics23_test.go index 0695ac681..3dff554e4 100644 --- a/ibc/store/proofs_ics23_test.go +++ b/ibc/store/proofs_ics23_test.go @@ -16,7 +16,7 @@ func TestICS23Proofs_GenerateCommitmentProofs(t *testing.T) { tree := smt.NewSparseMerkleTree(nodeStore, sha256.New(), smt.WithValueHasher(nil)) require.NotNil(t, tree) - // Set a value in the store + // Prepare a tree with a predetermined set of key-value pairs err := tree.Update([]byte("foo"), []byte("bar")) require.NoError(t, err) err = tree.Update([]byte("bar"), []byte("foo")) @@ -96,7 +96,7 @@ func TestICS23Proofs_VerifyCommitmentProofs(t *testing.T) { tree := smt.NewSparseMerkleTree(nodeStore, sha256.New(), smt.WithValueHasher(nil)) require.NotNil(t, tree) - // Set a value in the store + // Prepare a tree with a predetermined set of key-value pairs err := tree.Update([]byte("foo"), []byte("bar")) require.NoError(t, err) err = tree.Update([]byte("bar"), []byte("foo")) From bc5a878ab7f98f3c9004e963dbb2096975269375 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 20:31:09 +0100 Subject: [PATCH 10/74] add isLeft helper and use smt.GetPathBit() --- ibc/store/proofs_ics23.go | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/ibc/store/proofs_ics23.go b/ibc/store/proofs_ics23.go index a7930dd1c..f07c9d545 100644 --- a/ibc/store/proofs_ics23.go +++ b/ibc/store/proofs_ics23.go @@ -137,7 +137,7 @@ func convertSideNodesToSteps(sideNodes [][]byte, path []byte) []*ics23.InnerOp { for i := 0; i < len(sideNodes); i++ { var prefix, suffix []byte prefix = append(prefix, innerPrefix...) - if getPathBit(path, len(sideNodes)-1-i) == left { + if isLeft(path, len(sideNodes)-1-i) { // path is on the left so sidenode must be on the right suffix = make([]byte, 0, len(sideNodes[i])) suffix = append(suffix, sideNodes[i]...) @@ -155,25 +155,7 @@ func convertSideNodesToSteps(sideNodes [][]byte, path []byte) []*ics23.InnerOp { return steps } -// TECHDEBT(smt #14): Use exposed method from SMT library -// getPathBit takes the hash of a key (the path) and a position (depth) and returns whether at -// that position in the tree the path goes left or right. This is used to determine the order -// of child nodes and the order in which they are hashed when verifying proofs. -// Ref: https://github.com/pokt-network/smt/blob/main/utils.go -func getPathBit(data []byte, position int) position { - // get the byte at the position and then left shift one by the offset of the position - // from the leftmost bit in the byte. Check if the bitwise and is the same - // Path: []byte{ {0 1 0 1 1 0 1 0}, {0 1 1 0 1 1 0 1}, {1 0 0 1 0 0 1 0} } (length = 24 bits / 3 bytes) - // Position: 13 - 13/8=1 - // Path[1] = {0 1 1 0 1 1 0 1} - // uint(13)%8 = 5, 8-1-5=2 - // 00000001 << 2 = 00000100 - // {0 1 1 0 1 1 0 1} - // & {0 0 0 0 0 1 0 0} - // = {0 0 0 0 0 1 0 0} - // > 0 so Path is on the right at position 13 - if int(data[position/8])&(1<<(8-1-uint(position)%8)) > 0 { - return right - } - return left +// isLeft returns true is the i-th bit of path is a left child in the SMT +func isLeft(path []byte, i int) bool { + return smt.GetPathBit(path, i) == left } From 2f67dcd61c61fb92e1beceabd7f1aeb8febc98c8 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 21:40:00 +0100 Subject: [PATCH 11/74] go.mod --- go.mod | 2 +- go.sum | 7 +++++-- ibc/store/proofs_ics23.go | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 28281e5ed..8a4e44748 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( github.com/manifoldco/promptui v0.9.0 github.com/mitchellh/mapstructure v1.5.0 github.com/multiformats/go-multiaddr v0.8.0 - github.com/pokt-network/smt v0.5.0 + github.com/pokt-network/smt v0.6.0 github.com/quasilyte/go-ruleguard/dsl v0.3.21 github.com/regen-network/gocuke v0.6.2 github.com/rs/zerolog v1.27.0 diff --git a/go.sum b/go.sum index 271c59bc7..eb0a2dba4 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,8 @@ github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-ecvrf v0.0.1 h1:wv45+kZ0mG4G9oSTMjAlbgKqa4tPbNr4WLoCWqz5/bo= github.com/ProtonMail/go-ecvrf v0.0.1/go.mod h1:fhZbiRYn62/JGnBG2NGwCx0oT+gr/+I5R/hwiyAFpAU= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -150,6 +152,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= github.com/deepmap/oapi-codegen v1.12.4 h1:pPmn6qI9MuOtCz82WY2Xaw46EQjgvxednXXrP7g5Q2s= @@ -702,8 +705,8 @@ github.com/pokt-network/go-mockdns v0.0.1 h1:1Kb/kIFH6bNtY9F1bFhJyMRMCc7WyiqfGg0 github.com/pokt-network/go-mockdns v0.0.1/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= github.com/pokt-network/gocuke v0.0.1 h1:qJ/Ryf+hi5L6T9lsOZDNbiAclHkLlDio5/eVKQEYhgE= github.com/pokt-network/gocuke v0.0.1/go.mod h1:BowLKW4++696gTTU33teodtIhjjyaphEbhQT9D5Refw= -github.com/pokt-network/smt v0.5.0 h1:rNTW3FB6i0pNMnafDqsBySgs0zpbjs0spP3p7ltVjAE= -github.com/pokt-network/smt v0.5.0/go.mod h1:CWgC9UzDxXJNkL7TEADnJXutZVMYzK/+dmBb37RWkeQ= +github.com/pokt-network/smt v0.6.0 h1:W4NS6L0N5N0sDRq+2MskbWtCTnBiwzsgIJHxlHbwbgk= +github.com/pokt-network/smt v0.6.0/go.mod h1:CWgC9UzDxXJNkL7TEADnJXutZVMYzK/+dmBb37RWkeQ= github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= diff --git a/ibc/store/proofs_ics23.go b/ibc/store/proofs_ics23.go index f07c9d545..f07367224 100644 --- a/ibc/store/proofs_ics23.go +++ b/ibc/store/proofs_ics23.go @@ -68,7 +68,7 @@ func createMembershipProof(tree *smt.SMT, key, value []byte) (*ics23.CommitmentP if err != nil { return nil, coreTypes.ErrCreatingProof(err) } - return convertSMPToExistenceProof(&proof, key, value), nil + return convertSMPToExistenceProof(proof, key, value), nil } // createNonMembershipProof generates a CommitmentProof object verifying the membership of an unrealted key at the given key in the SMT provided @@ -78,7 +78,7 @@ func createNonMembershipProof(tree *smt.SMT, key []byte) (*ics23.CommitmentProof return nil, coreTypes.ErrCreatingProof(err) } - return convertSMPToExclusionProof(&proof, key), nil + return convertSMPToExclusionProof(proof, key), nil } // convertSMPToExistenceProof converts a SparseMerkleProof to an ics23 From 17e24199b5421e305c8cbaf4a83fa5b7d13bafea Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 21:46:20 +0100 Subject: [PATCH 12/74] Fix SMT repo --- go.mod | 2 +- go.sum | 7 ++----- ibc/store/proofs_ics23.go | 6 ++---- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 8a4e44748..1be7fb2dd 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( github.com/manifoldco/promptui v0.9.0 github.com/mitchellh/mapstructure v1.5.0 github.com/multiformats/go-multiaddr v0.8.0 - github.com/pokt-network/smt v0.6.0 + github.com/pokt-network/smt v0.6.1 github.com/quasilyte/go-ruleguard/dsl v0.3.21 github.com/regen-network/gocuke v0.6.2 github.com/rs/zerolog v1.27.0 diff --git a/go.sum b/go.sum index eb0a2dba4..925ffa331 100644 --- a/go.sum +++ b/go.sum @@ -57,8 +57,6 @@ github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-ecvrf v0.0.1 h1:wv45+kZ0mG4G9oSTMjAlbgKqa4tPbNr4WLoCWqz5/bo= github.com/ProtonMail/go-ecvrf v0.0.1/go.mod h1:fhZbiRYn62/JGnBG2NGwCx0oT+gr/+I5R/hwiyAFpAU= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -152,7 +150,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= github.com/deepmap/oapi-codegen v1.12.4 h1:pPmn6qI9MuOtCz82WY2Xaw46EQjgvxednXXrP7g5Q2s= @@ -705,8 +702,8 @@ github.com/pokt-network/go-mockdns v0.0.1 h1:1Kb/kIFH6bNtY9F1bFhJyMRMCc7WyiqfGg0 github.com/pokt-network/go-mockdns v0.0.1/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= github.com/pokt-network/gocuke v0.0.1 h1:qJ/Ryf+hi5L6T9lsOZDNbiAclHkLlDio5/eVKQEYhgE= github.com/pokt-network/gocuke v0.0.1/go.mod h1:BowLKW4++696gTTU33teodtIhjjyaphEbhQT9D5Refw= -github.com/pokt-network/smt v0.6.0 h1:W4NS6L0N5N0sDRq+2MskbWtCTnBiwzsgIJHxlHbwbgk= -github.com/pokt-network/smt v0.6.0/go.mod h1:CWgC9UzDxXJNkL7TEADnJXutZVMYzK/+dmBb37RWkeQ= +github.com/pokt-network/smt v0.6.1 h1:u5yTGNNND6edXv3vMQrAcjku1Ig4osehdu+EMYSXHUU= +github.com/pokt-network/smt v0.6.1/go.mod h1:CWgC9UzDxXJNkL7TEADnJXutZVMYzK/+dmBb37RWkeQ= github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= diff --git a/ibc/store/proofs_ics23.go b/ibc/store/proofs_ics23.go index f07367224..4f0d43fba 100644 --- a/ibc/store/proofs_ics23.go +++ b/ibc/store/proofs_ics23.go @@ -11,11 +11,9 @@ import ( // position refers to whether the node is either the left or right child of its parent // for the binary SMT // Ref: https://github.com/pokt-network/smt/blob/main/types.go -type position int - const ( - left position = iota // 0 - right // 1 + left int = iota // 0 + right // 1 hashSize = 32 ) From cf92d93fa247d095f34f7913448a20d48d63bdff Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 10:38:32 +0100 Subject: [PATCH 13/74] Add IBC proto types to protogen --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 96407cbf6..4174d4ecf 100644 --- a/Makefile +++ b/Makefile @@ -299,6 +299,9 @@ protogen_local: go_protoc-go-inject-tag ## Generate go structures for all of the # P2P $(PROTOC_SHARED) -I=./p2p/raintree/types/proto --go_out=./p2p/types ./p2p/raintree/types/proto/*.proto + # IBC + $(PROTOC_SHARED) -I=./ibc/types/proto --go_out=./ibc/types ./ibc/types/proto/*.proto + # echo "View generated proto files by running: make protogen_show" # CONSIDERATION: Some proto files contain unused gRPC services so we may need to add the following From a194cef1a132d46b391d9af9c3be3c2d86b2d004 Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 10:38:57 +0100 Subject: [PATCH 14/74] Add provable stores and HandleMessage --- ibc/module.go | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/ibc/module.go b/ibc/module.go index c4f4c9c56..e60dfd6f0 100644 --- a/ibc/module.go +++ b/ibc/module.go @@ -1,11 +1,18 @@ package ibc import ( + "fmt" + "sync" + + ibcTypes "github.com/pokt-network/pocket/ibc/types" "github.com/pokt-network/pocket/logger" "github.com/pokt-network/pocket/runtime/configs" + "github.com/pokt-network/pocket/shared/codec" coreTypes "github.com/pokt-network/pocket/shared/core/types" + "github.com/pokt-network/pocket/shared/messaging" "github.com/pokt-network/pocket/shared/modules" "github.com/pokt-network/pocket/shared/modules/base_modules" + "google.golang.org/protobuf/types/known/anypb" ) var _ modules.IBCModule = &ibcModule{} @@ -13,7 +20,9 @@ var _ modules.IBCModule = &ibcModule{} type ibcModule struct { base_modules.IntegratableModule - cfg *configs.IBCConfig + cfg *configs.IBCConfig + m sync.Mutex + logger *modules.Logger // Only a single host is allowed at a time @@ -75,6 +84,59 @@ func (m *ibcModule) GetModuleName() string { return modules.IBCModuleName } +// HandleMessage accepts a generic IBC message and routes it to the specific handler +func (m *ibcModule) HandleMessage(message *anypb.Any) error { + m.m.Lock() + defer m.m.Unlock() + + switch message.MessageName() { + + case messaging.IbcMessageContentType: + msg, err := codec.GetCodec().FromAny(message) + if err != nil { + return err + } + ibcMessage, ok := msg.(*ibcTypes.IbcMessage) + if !ok { + return fmt.Errorf("failed to cast message to IBCMessage") + } + return m.handleIBCMessage(ibcMessage) + + default: + return coreTypes.ErrUnknownIBCMessageType(string(message.MessageName())) + } +} + +// handleIBCMessage unpacks the IBC message to its type and calls the appropriate handler +func (m *ibcModule) handleIBCMessage(message *ibcTypes.IbcMessage) error { + switch msg := message.Event.(type) { + case *ibcTypes.IbcMessage_Update: + return m.handleUpdateMessage(msg.Update) + case *ibcTypes.IbcMessage_Prune: + return m.handlePruneMessage(msg.Prune) + default: + return coreTypes.ErrUnknownIBCMessageType(fmt.Sprintf("%T", msg)) + } +} + +// handleUpdateMessage adds the updated store entry to the IBC store change mempool +func (m *ibcModule) handleUpdateMessage(message *ibcTypes.UpdateIbcStore) error { + if m.host == nil { + return coreTypes.ErrHostDoesNotExist() + } + // TODO: implement this + return nil +} + +// handlePruneMessage adds a removal entry to the IBC store change mempool +func (m *ibcModule) handlePruneMessage(message *ibcTypes.PruneIbcStore) error { + if m.host == nil { + return coreTypes.ErrHostDoesNotExist() + } + // TODO: implement this + return nil +} + // newHost returns a new IBC host instance if one is not already created func (m *ibcModule) newHost() error { if m.host != nil { From c7206fcf2110b0b1b9da4e200f17c61027bc2893 Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 10:39:14 +0100 Subject: [PATCH 15/74] Export treestore trees --- persistence/trees/trees.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/persistence/trees/trees.go b/persistence/trees/trees.go index de51f0184..b2ef8fd76 100644 --- a/persistence/trees/trees.go +++ b/persistence/trees/trees.go @@ -23,9 +23,14 @@ import ( "github.com/pokt-network/smt" ) -// smtTreeHasher sets the hasher used by the tree SMT trees -// as a package level variable for visibility and internal use. -var smtTreeHasher hash.Hash = sha256.New() +var ( + // smtTreeHasher sets the hasher used by the tree SMT trees + // as a package level variable for visibility and internal use. + smtTreeHasher hash.Hash = sha256.New() + // smtValueHasher sets the hasher used by the tree SMT trees + // to be nil, which means that values are not prehashed + smtValueHasher smt.Option = smt.WithValueHasher(nil) +) const ( RootTreeName = "root" @@ -38,6 +43,7 @@ const ( TransactionsTreeName = "transactions" ParamsTreeName = "params" FlagsTreeName = "flags" + IbcTreeName = "ibc" ) var actorTypeToMerkleTreeName = map[coreTypes.ActorType]string{ @@ -60,7 +66,7 @@ var stateTreeNames = []string{ // Account Trees AccountTreeName, PoolTreeName, // Data Trees - TransactionsTreeName, ParamsTreeName, FlagsTreeName, + TransactionsTreeName, ParamsTreeName, FlagsTreeName, IbcTreeName, } // stateTree is a wrapper around the SMT that contains an identifying @@ -121,7 +127,7 @@ func (t *treeStore) DebugClearAll() error { if err := nodeStore.ClearAll(); err != nil { return fmt.Errorf("failed to clear %s node store: %w", treeName, err) } - stateTree.tree = smt.NewSparseMerkleTree(nodeStore, smtTreeHasher) + stateTree.tree = smt.NewSparseMerkleTree(nodeStore, smtTreeHasher, smtValueHasher) } return nil } @@ -190,7 +196,9 @@ func (t *treeStore) updateMerkleTrees(pgtx pgx.Tx, txi indexer.TxIndexer, height if err := t.updateFlagsTree(flags); err != nil { return "", fmt.Errorf("failed to update flags tree - %w", err) } - // Default should not happen; panic and log the treeType - this is a strong code smell. + case IbcTreeName: + // TODO: Implement IBC tree + // Default default: t.logger.Panic().Msgf("unhandled merkle tree type: %s", treeName) } From 8c9f38a220309159a4afc14404722cd69571cfe7 Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 10:40:10 +0100 Subject: [PATCH 16/74] Add IBC message handling --- shared/messaging/events.go | 3 +++ shared/node.go | 2 ++ 2 files changed, 5 insertions(+) diff --git a/shared/messaging/events.go b/shared/messaging/events.go index a726a55a0..68a5eddec 100644 --- a/shared/messaging/events.go +++ b/shared/messaging/events.go @@ -12,6 +12,9 @@ const ( // Utility TxGossipMessageContentType = "utility.TxGossipMessage" + + // IBC + IbcMessageContentType = "ibc.IbcMessage" ) // Helper logger for state sync tranition events diff --git a/shared/node.go b/shared/node.go index ccdb806f7..c644cc793 100644 --- a/shared/node.go +++ b/shared/node.go @@ -189,6 +189,8 @@ func (node *Node) handleEvent(message *messaging.PocketEnvelope) error { err_p2p := node.GetBus().GetP2PModule().HandleEvent(message.Content) // TODO: Remove this lib once we move to Go 1.2 return multierr.Combine(err_consensus, err_p2p) + case messaging.IbcMessageContentType: + return node.GetBus().GetIBCModule().HandleMessage(message.Content) default: logger.Global.Warn().Msgf("Unsupported message content type: %s", contentType) } From 9b3d286877610d1d1ef9a78d18d8b714d6bb2c9c Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 10:40:28 +0100 Subject: [PATCH 17/74] Add ibc message types --- ibc/messages.go | 29 +++++++++++++++++++++++++++++ ibc/types/proto/messages.proto | 26 ++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 ibc/messages.go create mode 100644 ibc/types/proto/messages.proto diff --git a/ibc/messages.go b/ibc/messages.go new file mode 100644 index 000000000..020aa46ff --- /dev/null +++ b/ibc/messages.go @@ -0,0 +1,29 @@ +package ibc + +import ( + "github.com/pokt-network/pocket/ibc/types" + coreTypes "github.com/pokt-network/pocket/shared/core/types" +) + +func CreateUpdateStoreMessage(prefix coreTypes.CommitmentPrefix, key, value []byte) *types.IbcMessage { + return &types.IbcMessage{ + Event: &types.IbcMessage_Update{ + Update: &types.UpdateIbcStore{ + Prefix: prefix, + Key: key, + Value: value, + }, + }, + } +} + +func CreatePruneStoreMessage(prefix coreTypes.CommitmentPrefix, key []byte) *types.IbcMessage { + return &types.IbcMessage{ + Event: &types.IbcMessage_Prune{ + Prune: &types.PruneIbcStore{ + Prefix: prefix, + Key: key, + }, + }, + } +} diff --git a/ibc/types/proto/messages.proto b/ibc/types/proto/messages.proto new file mode 100644 index 000000000..a436515b5 --- /dev/null +++ b/ibc/types/proto/messages.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package types; + +option go_package = "github.com/pokt-network/pocket/ibc/types"; + +// update_ibc_store defines a message representing the addition of a key/value pair to the IBC store +message update_ibc_store { + bytes prefix = 1; + bytes key = 2; + bytes value = 3; +} + +// prune_ibc_store defines a message representing the removal of a key from the IBC store +message prune_ibc_store { + bytes prefix = 1; + bytes key = 2; +} + +// ibc_message defines the different types of IBC message that can be sent across the network +message ibc_message { + oneof event { + update_ibc_store update = 1; + prune_ibc_store prune = 2; + } +} From 797ac03910da47bffee5dd50afaded709e4be330 Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 10:40:56 +0100 Subject: [PATCH 18/74] Add provable stores and HandleMessage --- shared/modules/ibc_module.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/shared/modules/ibc_module.go b/shared/modules/ibc_module.go index d006ee194..c8593e225 100644 --- a/shared/modules/ibc_module.go +++ b/shared/modules/ibc_module.go @@ -1,5 +1,10 @@ package modules +import ( + ics23 "github.com/cosmos/ics23/go" + "google.golang.org/protobuf/types/known/anypb" +) + //go:generate mockgen -destination=./mocks/ibc_module_mock.go github.com/pokt-network/pocket/shared/modules IBCModule,IBCHost,IBCHandler const IBCModuleName = "ibc" @@ -9,6 +14,9 @@ type IBCModule interface { // GetHost returns the IBC host of the modules GetHost() IBCHost + + // HandleMessage handles the given IBC message + HandleMessage(*anypb.Any) error } // IBCHost is the interface used by the host machine (a Pocket node) to interact with the IBC module @@ -196,3 +204,13 @@ type IBCHandler interface { ) (Packet, error) **/ } + +type ProvableStore interface { + Get(key []byte) ([]byte, error) + Set(key, value []byte) error + Delete(key []byte) error + Root() []byte + GetCommitmentPrefix() coreTypes.CommitmentPrefix + CreateMembershipProof(key, value []byte) (*ics23.CommitmentProof, error) + CreateNonMembershipProof(key []byte) (*ics23.CommitmentProof, error) +} From b1f9a8875927dc74ab7e3e1732df585b5d97a18a Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 10:41:10 +0100 Subject: [PATCH 19/74] Add more IBC errors --- shared/core/types/error.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/shared/core/types/error.go b/shared/core/types/error.go index 870b6b733..1f707e605 100644 --- a/shared/core/types/error.go +++ b/shared/core/types/error.go @@ -38,7 +38,7 @@ func NewError(code Code, msg string) Error { } } -// NextCode: 142 +// NextCode: 144 type Code float64 // CONSIDERATION: Should these be a proto enum or a golang iota? //nolint:gosec // G101 - Not hard-coded credentials @@ -178,9 +178,11 @@ const ( CodeUnknownMessageType Code = 131 CodeProposalBlockNotSet Code = 133 CodeHostAlreadyExists Code = 138 - CodeIBCInvalidID Code = 139 - CodeIBCInvalidPath Code = 140 - CodeCreatingProofError Code = 141 + CodeHostDoesNotExist Code = 139 + CodeIBCInvalidID Code = 140 + CodeIBCInvalidPath Code = 141 + CodeCreatingProofError Code = 142 + CodeUnknownIBCMessageTypeError Code = 143 ) const ( @@ -317,9 +319,11 @@ const ( UnknownActorTypeError = "the actor type is not recognized" UnknownMessageTypeError = "the message being by the utility message is not recognized" HostAlreadyExistsError = "an ibc host already exists" + HostDoesNotExistError = "an ibc host does not exist" IBCInvalidIDError = "invalid ibc identifier" IBCInvalidPathError = "invalid ibc path" CreatingProofError = "an error occurred creating the CommitmentProof" + UnknownIBCMessageTypeError = "the ibc message type is not recognized" ) func ErrUnknownParam(paramName string) Error { @@ -859,6 +863,10 @@ func ErrHostAlreadyExists() Error { return NewError(CodeHostAlreadyExists, HostAlreadyExistsError) } +func ErrHostDoesNotExist() Error { + return NewError(CodeHostDoesNotExist, HostDoesNotExistError) +} + func ErrIBCInvalidID(identifier, msg string) Error { return NewError(CodeIBCInvalidID, fmt.Sprintf("%s: %s (%s)", IBCInvalidIDError, identifier, msg)) } @@ -870,3 +878,7 @@ func ErrIBCInvalidPath(path string) Error { func ErrCreatingProof(err error) Error { return NewError(CodeCreatingProofError, fmt.Sprintf("%s: %s", CreatingProofError, err.Error())) } + +func ErrUnknownIBCMessageType(messageType string) Error { + return NewError(CodeUnknownIBCMessageTypeError, fmt.Sprintf("%s: %s", UnknownIBCMessageTypeError, messageType)) +} From 1dca7d80d1c325b1e1edde3c3d71d045761cb944 Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 11:16:55 +0100 Subject: [PATCH 20/74] Add GetProvableStore --- ibc/host.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ibc/host.go b/ibc/host.go index 603580cac..35b7d64b1 100644 --- a/ibc/host.go +++ b/ibc/host.go @@ -3,6 +3,7 @@ package ibc import ( "time" + coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/modules" ) @@ -16,3 +17,8 @@ type host struct { func (h *host) GetTimestamp() uint64 { return uint64(time.Now().Unix()) } + +// GetProvableStore returns +func (h *host) GetProvableStore(prefix coreTypes.CommitmentPrefix) (modules.ProvableStore, error) { + return nil, nil +} From ff2d440c94206606a47045a59559a015ae1d48e9 Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 11:17:19 +0100 Subject: [PATCH 21/74] Implement provableStore --- ibc/store/provable_store.go | 74 ++++++++++++++++++++++++++++++++++++ shared/modules/ibc_module.go | 1 + 2 files changed, 75 insertions(+) create mode 100644 ibc/store/provable_store.go diff --git a/ibc/store/provable_store.go b/ibc/store/provable_store.go new file mode 100644 index 000000000..421ebb32d --- /dev/null +++ b/ibc/store/provable_store.go @@ -0,0 +1,74 @@ +package store + +import ( + "bytes" + + ics23 "github.com/cosmos/ics23/go" + "github.com/pokt-network/pocket/ibc/host" + "github.com/pokt-network/pocket/persistence/kvstore" + coreTypes "github.com/pokt-network/pocket/shared/core/types" + "github.com/pokt-network/pocket/shared/modules" + "github.com/pokt-network/smt" +) + +var _ modules.ProvableStore = &provableStore{} + +// provableStore implements the ProvableStore interface and wraps an SMT +type provableStore struct { + prefix coreTypes.CommitmentPrefix + tree *smt.SMT + nodeStore kvstore.KVStore +} + +// GetCommitmentPrefix returns the commitment prefix of the provable store +func (p *provableStore) GetCommitmentPrefix() coreTypes.CommitmentPrefix { + return p.prefix +} + +// Get returns the value in the tree of the key prefixed with the CommitmentPrefix +func (p *provableStore) Get(key []byte) ([]byte, error) { + prefixed := applyPrefix(p.prefix, key) + return p.tree.Get(prefixed) +} + +// Set sets the value in the tree of the key prefixed with the CommitmentPrefix +func (p *provableStore) Set(key, value []byte) error { + prefixed := applyPrefix(p.prefix, key) + return p.tree.Update(prefixed, value) +} + +// Delete deletes the value in the tree of the key prefixed with the CommitmentPrefix +func (p *provableStore) Delete(key []byte) error { + prefixed := applyPrefix(p.prefix, key) + return p.tree.Delete(prefixed) +} + +// CreateMembershipProof creates a membership proof for the key-value pair with the key +// prefixed with the CommitmentPrefix +func (p *provableStore) CreateMembershipProof(key, value []byte) (*ics23.CommitmentProof, error) { + prefixed := applyPrefix(p.prefix, key) + return createMembershipProof(p.tree, prefixed, value) +} + +// CreateNonMembershipProof creates a non-membership proof for the key prefixed with the CommitmentPrefix +func (p *provableStore) CreateNonMembershipProof(key []byte) (*ics23.CommitmentProof, error) { + prefixed := applyPrefix(p.prefix, key) + return createNonMembershipProof(p.tree, prefixed) +} + +// Root returns the root of the entire tree +// NOTE: Root does not work on a per-prefix basis but returns the root of the entire tree +func (p *provableStore) Root() []byte { + return p.tree.Root() +} + +// applyPrefix will apply the CommitmentPrefix to the key provided if not already applied +func applyPrefix(prefix coreTypes.CommitmentPrefix, key []byte) coreTypes.CommitmentPath { + slashed := make([]byte, 0, len(key)+1) + slashed = append(slashed, key...) + slashed = append(slashed, []byte("/")...) + if bytes.Equal(prefix[:len(slashed)], slashed) { + return key + } + return host.ApplyPrefix(prefix, string(key)) +} diff --git a/shared/modules/ibc_module.go b/shared/modules/ibc_module.go index c8593e225..52427a2e1 100644 --- a/shared/modules/ibc_module.go +++ b/shared/modules/ibc_module.go @@ -2,6 +2,7 @@ package modules import ( ics23 "github.com/cosmos/ics23/go" + coreTypes "github.com/pokt-network/pocket/shared/core/types" "google.golang.org/protobuf/types/known/anypb" ) From 83fb7a1118064613ee6f47108f5603ff7cfa52fd Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 16:40:37 +0100 Subject: [PATCH 22/74] Add private key to ibc config --- build/config/config.validator1.json | 3 ++- build/config/config.validator2.json | 3 ++- build/config/config.validator3.json | 3 ++- build/config/config.validator4.json | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/build/config/config.validator1.json b/build/config/config.validator1.json index 702ad4232..746e74881 100644 --- a/build/config/config.validator1.json +++ b/build/config/config.validator1.json @@ -59,6 +59,7 @@ "chains": ["0001"] }, "ibc": { - "enabled": true + "enabled": true, + "private_key": "0ca1a40ddecdab4f5b04fa0bfed1d235beaa2b8082e7554425607516f0862075dfe357de55649e6d2ce889acf15eb77e94ab3c5756fe46d3c7538d37f27f115e" } } diff --git a/build/config/config.validator2.json b/build/config/config.validator2.json index 3e2ef6f6e..052d8eeb7 100644 --- a/build/config/config.validator2.json +++ b/build/config/config.validator2.json @@ -52,6 +52,7 @@ "use_cors": false }, "ibc": { - "enabled": true + "enabled": true, + "private_key": "0ca1a40ddecdab4f5b04fa0bfed1d235beaa2b8082e7554425607516f0862075dfe357de55649e6d2ce889acf15eb77e94ab3c5756fe46d3c7538d37f27f115e" } } diff --git a/build/config/config.validator3.json b/build/config/config.validator3.json index 5cd45e544..834a72ff2 100644 --- a/build/config/config.validator3.json +++ b/build/config/config.validator3.json @@ -52,6 +52,7 @@ "use_cors": false }, "ibc": { - "enabled": true + "enabled": true, + "private_key": "0ca1a40ddecdab4f5b04fa0bfed1d235beaa2b8082e7554425607516f0862075dfe357de55649e6d2ce889acf15eb77e94ab3c5756fe46d3c7538d37f27f115e" } } diff --git a/build/config/config.validator4.json b/build/config/config.validator4.json index 2eb7747d7..df37cb69a 100644 --- a/build/config/config.validator4.json +++ b/build/config/config.validator4.json @@ -52,6 +52,7 @@ "use_cors": false }, "ibc": { - "enabled": true + "enabled": true, + "private_key": "0ca1a40ddecdab4f5b04fa0bfed1d235beaa2b8082e7554425607516f0862075dfe357de55649e6d2ce889acf15eb77e94ab3c5756fe46d3c7538d37f27f115e" } } From aa56b42e423cfdcf77345f3ed2b0e4b7046707d9 Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 16:41:11 +0100 Subject: [PATCH 23/74] Allow conversion of IbcMessage to Transaction --- ibc/messages.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/ibc/messages.go b/ibc/messages.go index 020aa46ff..7a97aae4d 100644 --- a/ibc/messages.go +++ b/ibc/messages.go @@ -1,8 +1,13 @@ package ibc import ( + "fmt" + "github.com/pokt-network/pocket/ibc/types" + "github.com/pokt-network/pocket/shared/codec" coreTypes "github.com/pokt-network/pocket/shared/core/types" + "github.com/pokt-network/pocket/shared/crypto" + "google.golang.org/protobuf/types/known/anypb" ) func CreateUpdateStoreMessage(prefix coreTypes.CommitmentPrefix, key, value []byte) *types.IbcMessage { @@ -27,3 +32,23 @@ func CreatePruneStoreMessage(prefix coreTypes.CommitmentPrefix, key []byte) *typ }, } } + +func ConvertIBCMessageToTx(ibcMessage *types.IbcMessage) (*coreTypes.Transaction, error) { + var anyMsg *anypb.Any + var err error + switch event := ibcMessage.Event.(type) { + case *types.IbcMessage_Update: + anyMsg, err = codec.GetCodec().ToAny(event.Update) + case *types.IbcMessage_Prune: + anyMsg, err = codec.GetCodec().ToAny(event.Prune) + default: + return nil, coreTypes.ErrUnknownIBCMessageType(fmt.Sprintf("%T", event)) + } + if err != nil { + return nil, err + } + return &coreTypes.Transaction{ + Msg: anyMsg, + Nonce: fmt.Sprintf("%d", crypto.GetNonce()), + }, nil +} From 454db6eb3980b06cc9b9d275f95753d4e54289cc Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 16:41:57 +0100 Subject: [PATCH 24/74] HandleEvent adds ibc message to TxMempool --- ibc/module.go | 88 ++++++++++++++++++++++++++++----------------------- 1 file changed, 49 insertions(+), 39 deletions(-) diff --git a/ibc/module.go b/ibc/module.go index e60dfd6f0..6390479a5 100644 --- a/ibc/module.go +++ b/ibc/module.go @@ -1,6 +1,7 @@ package ibc import ( + "encoding/hex" "fmt" "sync" @@ -9,6 +10,7 @@ import ( "github.com/pokt-network/pocket/runtime/configs" "github.com/pokt-network/pocket/shared/codec" coreTypes "github.com/pokt-network/pocket/shared/core/types" + "github.com/pokt-network/pocket/shared/crypto" "github.com/pokt-network/pocket/shared/messaging" "github.com/pokt-network/pocket/shared/modules" "github.com/pokt-network/pocket/shared/modules/base_modules" @@ -20,9 +22,9 @@ var _ modules.IBCModule = &ibcModule{} type ibcModule struct { base_modules.IntegratableModule - cfg *configs.IBCConfig - m sync.Mutex + m sync.Mutex + cfg *configs.IBCConfig logger *modules.Logger // Only a single host is allowed at a time @@ -89,51 +91,59 @@ func (m *ibcModule) HandleMessage(message *anypb.Any) error { m.m.Lock() defer m.m.Unlock() - switch message.MessageName() { - - case messaging.IbcMessageContentType: - msg, err := codec.GetCodec().FromAny(message) - if err != nil { - return err - } - ibcMessage, ok := msg.(*ibcTypes.IbcMessage) - if !ok { - return fmt.Errorf("failed to cast message to IBCMessage") - } - return m.handleIBCMessage(ibcMessage) - - default: + // Check the message is actually a valid IBC message + if message.MessageName() != messaging.IbcMessageContentType { return coreTypes.ErrUnknownIBCMessageType(string(message.MessageName())) } -} + msg, err := codec.GetCodec().FromAny(message) + if err != nil { + return err + } + ibcMessage, ok := msg.(*ibcTypes.IbcMessage) + if !ok { + return fmt.Errorf("failed to cast message to IBCMessage") + } + if err := ibcMessage.ValidateBasic(); err != nil { + return err + } -// handleIBCMessage unpacks the IBC message to its type and calls the appropriate handler -func (m *ibcModule) handleIBCMessage(message *ibcTypes.IbcMessage) error { - switch msg := message.Event.(type) { - case *ibcTypes.IbcMessage_Update: - return m.handleUpdateMessage(msg.Update) - case *ibcTypes.IbcMessage_Prune: - return m.handlePruneMessage(msg.Prune) - default: - return coreTypes.ErrUnknownIBCMessageType(fmt.Sprintf("%T", msg)) + // Convert IBC message to a utility Transaction + tx, err := ConvertIBCMessageToTx(ibcMessage) + if err != nil { + return err } -} -// handleUpdateMessage adds the updated store entry to the IBC store change mempool -func (m *ibcModule) handleUpdateMessage(message *ibcTypes.UpdateIbcStore) error { - if m.host == nil { - return coreTypes.ErrHostDoesNotExist() + // Sign the transaction + pkBz, err := hex.DecodeString(m.cfg.PrivateKey) + if err != nil { + return err + } + pk, err := crypto.NewPrivateKeyFromBytes(pkBz) + if err != nil { + return err + } + signableBz, err := tx.SignableBytes() + if err != nil { + return err + } + signature, err := pk.Sign(signableBz) + if err != nil { + return err + } + tx.Signature = &coreTypes.Signature{ + Signature: signature, + PublicKey: pk.PublicKey().Bytes(), } - // TODO: implement this - return nil -} -// handlePruneMessage adds a removal entry to the IBC store change mempool -func (m *ibcModule) handlePruneMessage(message *ibcTypes.PruneIbcStore) error { - if m.host == nil { - return coreTypes.ErrHostDoesNotExist() + // Marshall the Transaction and send it to the utility module + txBz, err := codec.GetCodec().Marshal(tx) + if err != nil { + return err + } + if err := m.GetBus().GetUtilityModule().HandleTransaction(txBz); err != nil { + return err } - // TODO: implement this + m.logger.Info().Str("message_type", "IbcMessage").Msg("Successfully added a new message to the mempool!") return nil } From 0d867f364197df4fd151fe4f7a170782b719815e Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 16:42:20 +0100 Subject: [PATCH 25/74] Add signer to messages --- ibc/types/proto/messages.proto | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ibc/types/proto/messages.proto b/ibc/types/proto/messages.proto index a436515b5..e2a62b0f8 100644 --- a/ibc/types/proto/messages.proto +++ b/ibc/types/proto/messages.proto @@ -6,15 +6,17 @@ option go_package = "github.com/pokt-network/pocket/ibc/types"; // update_ibc_store defines a message representing the addition of a key/value pair to the IBC store message update_ibc_store { - bytes prefix = 1; - bytes key = 2; - bytes value = 3; + bytes signer = 1; + bytes prefix = 2; + bytes key = 3; + bytes value = 4; } // prune_ibc_store defines a message representing the removal of a key from the IBC store message prune_ibc_store { - bytes prefix = 1; - bytes key = 2; + bytes signer = 1; + bytes prefix = 2; + bytes key = 3; } // ibc_message defines the different types of IBC message that can be sent across the network From a3178e4b77657d6bb0e802e28b2c305ed951e6af Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 16:42:50 +0100 Subject: [PATCH 26/74] Implement utility Message interface for IbcMessage types --- ibc/types/messages.go | 91 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 ibc/types/messages.go diff --git a/ibc/types/messages.go b/ibc/types/messages.go new file mode 100644 index 000000000..8a38613b6 --- /dev/null +++ b/ibc/types/messages.go @@ -0,0 +1,91 @@ +package types + +import ( + "fmt" + "log" + + "github.com/pokt-network/pocket/shared/codec" + coreTypes "github.com/pokt-network/pocket/shared/core/types" + utilityTypes "github.com/pokt-network/pocket/utility/types" +) + +// Implement the Message interface +var ( + _ utilityTypes.Message = &UpdateIbcStore{} + _ utilityTypes.Message = &PruneIbcStore{} +) + +func (m *IbcMessage) ValidateBasic() coreTypes.Error { + switch msg := m.Event.(type) { + case *IbcMessage_Update: + return msg.Update.ValidateBasic() + case *IbcMessage_Prune: + return msg.Prune.ValidateBasic() + default: + return coreTypes.ErrUnknownIBCMessageType(fmt.Sprintf("%T", msg)) + } +} + +func (m *UpdateIbcStore) ValidateBasic() coreTypes.Error { + if m.Key == nil { + return coreTypes.ErrNilField("key") + } + if m.Prefix == nil { + return coreTypes.ErrNilField("prefix") + } + if m.Value == nil { + return coreTypes.ErrNilField("value") + } + return nil +} + +func (m *PruneIbcStore) ValidateBasic() coreTypes.Error { + if m.Key == nil { + return coreTypes.ErrNilField("key") + } + if m.Prefix == nil { + return coreTypes.ErrNilField("prefix") + } + return nil +} + +func (m *UpdateIbcStore) SetSigner(signer []byte) { m.Signer = signer } +func (m *PruneIbcStore) SetSigner(signer []byte) { m.Signer = signer } + +func (m *UpdateIbcStore) GetMessageName() string { + return string(m.ProtoReflect().Descriptor().Name()) +} + +func (m *PruneIbcStore) GetMessageName() string { + return string(m.ProtoReflect().Descriptor().Name()) +} + +func (m *UpdateIbcStore) GetMessageRecipient() string { return "" } +func (m *PruneIbcStore) GetMessageRecipient() string { return "" } + +func (m *UpdateIbcStore) GetSigner() []byte { return m.Signer } +func (m *PruneIbcStore) GetSigner() []byte { return m.Signer } + +func (m *UpdateIbcStore) GetActorType() coreTypes.ActorType { + return coreTypes.ActorType_ACTOR_TYPE_VAL +} + +func (m *PruneIbcStore) GetActorType() coreTypes.ActorType { + return coreTypes.ActorType_ACTOR_TYPE_VAL +} + +func (m *UpdateIbcStore) GetCanonicalBytes() []byte { + bz, err := codec.GetCodec().Marshal(m) + if err != nil { + log.Fatalf("must marshal %v", err) + } + return bz // DISCUSS(#142): should we also sort the JSON like in V0? +} + +func (m *PruneIbcStore) GetCanonicalBytes() []byte { + bz, err := codec.GetCodec().Marshal(m) + if err != nil { + log.Fatalf("must marshal %v", err) + } + return bz // DISCUSS(#142): should we also sort the JSON like in V0? +} From 3570e52a3dcae367a17f9f6c7c1a5c2329350d02 Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 16:43:15 +0100 Subject: [PATCH 27/74] Update IBC config to have a private key --- runtime/configs/config.go | 1 + runtime/configs/proto/ibc_config.proto | 1 + 2 files changed, 2 insertions(+) diff --git a/runtime/configs/config.go b/runtime/configs/config.go index 3e387a8c8..ee2f6f382 100644 --- a/runtime/configs/config.go +++ b/runtime/configs/config.go @@ -181,6 +181,7 @@ func WithPK(pk string) func(*Config) { cfg.PrivateKey = pk cfg.Consensus.PrivateKey = pk cfg.P2P.PrivateKey = pk + cfg.IBC.PrivateKey = pk } } diff --git a/runtime/configs/proto/ibc_config.proto b/runtime/configs/proto/ibc_config.proto index 389e19528..b83d4f406 100644 --- a/runtime/configs/proto/ibc_config.proto +++ b/runtime/configs/proto/ibc_config.proto @@ -12,4 +12,5 @@ message IBCConfig { // 2. The node is a servicer and thus when IBC enabled is they are enabled // to relay IBC packets using an IBC relayer binary bool enabled = 1; + string private_key = 2; // hex encoded } From 22970b12d73af0b5c541a16d018c47808fde0483 Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 16:43:29 +0100 Subject: [PATCH 28/74] Add IBC nil field errors --- shared/core/types/error.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/shared/core/types/error.go b/shared/core/types/error.go index 1f707e605..fbd9323c3 100644 --- a/shared/core/types/error.go +++ b/shared/core/types/error.go @@ -38,7 +38,7 @@ func NewError(code Code, msg string) Error { } } -// NextCode: 144 +// NextCode: 145 type Code float64 // CONSIDERATION: Should these be a proto enum or a golang iota? //nolint:gosec // G101 - Not hard-coded credentials @@ -183,6 +183,7 @@ const ( CodeIBCInvalidPath Code = 141 CodeCreatingProofError Code = 142 CodeUnknownIBCMessageTypeError Code = 143 + CodeNilFieldError Code = 144 ) const ( @@ -324,6 +325,7 @@ const ( IBCInvalidPathError = "invalid ibc path" CreatingProofError = "an error occurred creating the CommitmentProof" UnknownIBCMessageTypeError = "the ibc message type is not recognized" + NilFieldError = "field cannot be nil" ) func ErrUnknownParam(paramName string) Error { @@ -882,3 +884,7 @@ func ErrCreatingProof(err error) Error { func ErrUnknownIBCMessageType(messageType string) Error { return NewError(CodeUnknownIBCMessageTypeError, fmt.Sprintf("%s: %s", UnknownIBCMessageTypeError, messageType)) } + +func ErrNilField(field string) Error { + return NewError(CodeNilFieldError, fmt.Sprintf("%s: %s", NilFieldError, field)) +} From f72f51391ac1e86bbafe71e841b307fec5d14880 Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 16:43:46 +0100 Subject: [PATCH 29/74] Add IbcMessage Tx handling logic --- utility/unit_of_work/gov.go | 8 ++++---- utility/unit_of_work/transaction.go | 2 +- utility/unit_of_work/tx_message_handler.go | 15 +++++++++++++++ utility/unit_of_work/tx_message_signers.go | 13 +++++++++++++ utility/utility_message_handler.go | 1 - 5 files changed, 33 insertions(+), 6 deletions(-) diff --git a/utility/unit_of_work/gov.go b/utility/unit_of_work/gov.go index 2e18914ff..e1b893ac1 100644 --- a/utility/unit_of_work/gov.go +++ b/utility/unit_of_work/gov.go @@ -4,6 +4,7 @@ import ( "math/big" "strings" + ibcTypes "github.com/pokt-network/pocket/ibc/types" "github.com/pokt-network/pocket/logger" "github.com/pokt-network/pocket/persistence" coreTypes "github.com/pokt-network/pocket/shared/core/types" @@ -24,9 +25,7 @@ func init() { } } -var ( - govParamTypes map[string]int -) +var govParamTypes map[string]int const ( BIGINT int = iota @@ -151,7 +150,8 @@ func (u *baseUtilityUnitOfWork) getParamOwner(paramName string) ([]byte, coreTyp func (u *baseUtilityUnitOfWork) getFee(msg typesUtil.Message, actorType coreTypes.ActorType) (amount *big.Int, err coreTypes.Error) { switch x := msg.(type) { - case *typesUtil.MessageSend: + // DISCUSS: What fee should be deducted for IBC store update messages + case *typesUtil.MessageSend, *ibcTypes.UpdateIbcStore, *ibcTypes.PruneIbcStore: return getGovParam[*big.Int](u, typesUtil.MessageSendFee) case *typesUtil.MessageStake: switch actorType { diff --git a/utility/unit_of_work/transaction.go b/utility/unit_of_work/transaction.go index 3e91c0c14..6e2d04c87 100644 --- a/utility/unit_of_work/transaction.go +++ b/utility/unit_of_work/transaction.go @@ -33,7 +33,7 @@ func (u *baseUtilityUnitOfWork) basicValidateTransaction(tx *coreTypes.Transacti // Get the address of the transaction signer pubKey, er := crypto.NewPublicKeyFromBytes(tx.Signature.PublicKey) - if err != nil { + if er != nil { return nil, coreTypes.ErrNewPublicKeyFromBytes(er) } address := pubKey.Address() diff --git a/utility/unit_of_work/tx_message_handler.go b/utility/unit_of_work/tx_message_handler.go index 9b3080af9..42de09a07 100644 --- a/utility/unit_of_work/tx_message_handler.go +++ b/utility/unit_of_work/tx_message_handler.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "math/big" + ibcTypes "github.com/pokt-network/pocket/ibc/types" "github.com/pokt-network/pocket/shared/codec" coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/crypto" @@ -26,6 +27,10 @@ func (u *baseUtilityUnitOfWork) handleMessage(msg typesUtil.Message) (err coreTy return u.handleUnpauseMessage(x) case *typesUtil.MessageChangeParameter: return u.handleMessageChangeParameter(x) + case *ibcTypes.UpdateIbcStore: + return u.handleUpdateIbcStore(x) + case *ibcTypes.PruneIbcStore: + return u.handlePruneIbcStore(x) default: return coreTypes.ErrUnknownMessage(x) } @@ -220,6 +225,16 @@ func (u *baseUtilityUnitOfWork) handleMessageChangeParameter(message *typesUtil. return u.updateParam(message.ParameterKey, v) } +// TODO_IN_THIS_COMMIT: implement +func (u *baseUtilityUnitOfWork) handleUpdateIbcStore(message *ibcTypes.UpdateIbcStore) coreTypes.Error { + return nil +} + +// TODO_IN_THIS_COMMIT: implement +func (u *baseUtilityUnitOfWork) handlePruneIbcStore(message *ibcTypes.PruneIbcStore) coreTypes.Error { + return nil +} + func (u *baseUtilityUnitOfWork) checkBelowMaxChains(actorType coreTypes.ActorType, chains []string) coreTypes.Error { // validators don't have chains field if actorType == coreTypes.ActorType_ACTOR_TYPE_VAL { diff --git a/utility/unit_of_work/tx_message_signers.go b/utility/unit_of_work/tx_message_signers.go index 1da3b9acc..8e84bc133 100644 --- a/utility/unit_of_work/tx_message_signers.go +++ b/utility/unit_of_work/tx_message_signers.go @@ -1,6 +1,7 @@ package unit_of_work import ( + ibcTypes "github.com/pokt-network/pocket/ibc/types" coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/crypto" typesUtil "github.com/pokt-network/pocket/utility/types" @@ -24,6 +25,10 @@ func (u *baseUtilityUnitOfWork) getSignerCandidates(msg typesUtil.Message) ([][] return u.getMessageUnpauseSignerCandidates(x) case *typesUtil.MessageChangeParameter: return u.getMessageChangeParameterSignerCandidates(x) + case *ibcTypes.UpdateIbcStore: + return u.getUpdateIbcStoreSingerCandidates(x) + case *ibcTypes.PruneIbcStore: + return u.getPruneIbcStoreSingerCandidates(x) default: return nil, coreTypes.ErrUnknownMessage(x) } @@ -72,3 +77,11 @@ func (u *baseUtilityUnitOfWork) getMessageUnpauseSignerCandidates(msg *typesUtil func (u *baseUtilityUnitOfWork) getMessageSendSignerCandidates(msg *typesUtil.MessageSend) ([][]byte, coreTypes.Error) { return [][]byte{msg.FromAddress}, nil } + +func (u *baseUtilityUnitOfWork) getUpdateIbcStoreSingerCandidates(msg *ibcTypes.UpdateIbcStore) ([][]byte, coreTypes.Error) { + return [][]byte{msg.Signer}, nil +} + +func (u *baseUtilityUnitOfWork) getPruneIbcStoreSingerCandidates(msg *ibcTypes.PruneIbcStore) ([][]byte, coreTypes.Error) { + return [][]byte{msg.Signer}, nil +} diff --git a/utility/utility_message_handler.go b/utility/utility_message_handler.go index 4cb6ee2a7..46079d1c5 100644 --- a/utility/utility_message_handler.go +++ b/utility/utility_message_handler.go @@ -44,7 +44,6 @@ func (u *utilityModule) HandleUtilityMessage(message *anypb.Any) error { } u.logger.Info().Str("message_type", "TxGossipMessage").Msg("Successfully added a new message to the mempool!") - default: return coreTypes.ErrUnknownMessageType(message.MessageName()) } From c7176e8d7760f2d0c7de18388d404e4d9e4f48fd Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 17:30:50 +0100 Subject: [PATCH 30/74] Add techdebt comment --- utility/unit_of_work/gov.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utility/unit_of_work/gov.go b/utility/unit_of_work/gov.go index e1b893ac1..19461e07b 100644 --- a/utility/unit_of_work/gov.go +++ b/utility/unit_of_work/gov.go @@ -150,7 +150,7 @@ func (u *baseUtilityUnitOfWork) getParamOwner(paramName string) ([]byte, coreTyp func (u *baseUtilityUnitOfWork) getFee(msg typesUtil.Message, actorType coreTypes.ActorType) (amount *big.Int, err coreTypes.Error) { switch x := msg.(type) { - // DISCUSS: What fee should be deducted for IBC store update messages + // TECHDEBT(M6): Decide on IBC store change fees and move into a governance parameter case *typesUtil.MessageSend, *ibcTypes.UpdateIbcStore, *ibcTypes.PruneIbcStore: return getGovParam[*big.Int](u, typesUtil.MessageSendFee) case *typesUtil.MessageStake: From 5c2bbc58e337080a3c91ca7ae88bd3d5ab9ef633 Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 17:31:05 +0100 Subject: [PATCH 31/74] Remove duplicate method --- ibc/types/messages.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/ibc/types/messages.go b/ibc/types/messages.go index 8a38613b6..35bd990a1 100644 --- a/ibc/types/messages.go +++ b/ibc/types/messages.go @@ -63,9 +63,6 @@ func (m *PruneIbcStore) GetMessageName() string { func (m *UpdateIbcStore) GetMessageRecipient() string { return "" } func (m *PruneIbcStore) GetMessageRecipient() string { return "" } -func (m *UpdateIbcStore) GetSigner() []byte { return m.Signer } -func (m *PruneIbcStore) GetSigner() []byte { return m.Signer } - func (m *UpdateIbcStore) GetActorType() coreTypes.ActorType { return coreTypes.ActorType_ACTOR_TYPE_VAL } From c05ab8eeb0f07457a43271527a05aede063c7508 Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 18:39:02 +0100 Subject: [PATCH 32/74] Add IBC store change related postgres DB sql code --- persistence/db.go | 11 +++++++++++ persistence/ibc.go | 14 ++++++++++++++ persistence/sql/sql.go | 30 ++++++++++++++++++++++++++++++ persistence/types/ibc.go | 26 ++++++++++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 persistence/ibc.go create mode 100644 persistence/types/ibc.go diff --git a/persistence/db.go b/persistence/db.go index 43bf14e91..8b420c0ff 100644 --- a/persistence/db.go +++ b/persistence/db.go @@ -134,6 +134,10 @@ func initializeAllTables(ctx context.Context, db *pgxpool.Conn) error { } } + if err := initialiseIbcTables(ctx, db); err != nil { + return err + } + return nil } @@ -184,3 +188,10 @@ func initializeBlockTables(ctx context.Context, db *pgxpool.Conn) error { } return nil } + +func initialiseIbcTables(ctx context.Context, db *pgxpool.Conn) error { + if _, err := db.Exec(ctx, fmt.Sprintf(`%s %s %s %s`, CreateTable, IfNotExists, types.IbcStoreTableName, types.IbcStoreTableSchema)); err != nil { + return err + } + return nil +} diff --git a/persistence/ibc.go b/persistence/ibc.go new file mode 100644 index 000000000..7e7bdb71f --- /dev/null +++ b/persistence/ibc.go @@ -0,0 +1,14 @@ +package persistence + +import ( + pTypes "github.com/pokt-network/pocket/persistence/types" +) + +// SetIBCStoreEntry sets the key value pair in the IBC store postgres table +func (p *PostgresContext) SetIBCStoreEntry(key, value []byte) error { + ctx, tx := p.getCtxAndTx() + if _, err := tx.Exec(ctx, pTypes.InsertIbcStoreEntryQuery(p.Height, key, value)); err != nil { + return err + } + return nil +} diff --git a/persistence/sql/sql.go b/persistence/sql/sql.go index 48bbec2d3..274e20979 100644 --- a/persistence/sql/sql.go +++ b/persistence/sql/sql.go @@ -165,6 +165,36 @@ func GetParams(pgtx pgx.Tx, height uint64) ([]*coreTypes.Param, error) { return paramSlice, nil } +// GetIBCStoreUpdates returns the set of key-value pairs updated at the current height for the IBC store +func GetIBCStoreUpdates(pgtx pgx.Tx, height uint64) (keys [][]byte, values [][]byte, err error) { + fields := "key,value" + query := fmt.Sprintf("SELECT %s FROM %s WHERE height=%d ORDER BY key ASC", fields, ptypes.IbcStoreTableName, height) + rows, err := pgtx.Query(context.TODO(), query) + if err != nil { + return nil, nil, err + } + defer rows.Close() + + var hexKey, hexValue string + for rows.Next() { + if err := rows.Scan(&hexKey, &hexValue); err != nil { + return nil, nil, err + } + key, err := hex.DecodeString(hexKey) + if err != nil { + return nil, nil, err + } + value, err := hex.DecodeString(hexValue) + if err != nil { + return nil, nil, err + } + keys = append(keys, key) + values = append(values, value) + } + + return keys, values, nil +} + func getActor(tx pgx.Tx, actorSchema ptypes.ProtocolActorSchema, address []byte, height int64) (actor *coreTypes.Actor, err error) { ctx := context.TODO() actor, height, err = getActorFromRow(actorSchema.GetActorType(), tx.QueryRow(ctx, actorSchema.GetQuery(hex.EncodeToString(address), height))) diff --git a/persistence/types/ibc.go b/persistence/types/ibc.go new file mode 100644 index 000000000..c94b542d6 --- /dev/null +++ b/persistence/types/ibc.go @@ -0,0 +1,26 @@ +package types + +import ( + "encoding/hex" + "fmt" +) + +const ( + IbcStoreTableName = "ibc_messages" + IbcStoreTableSchema = `( + height BIGINT NOT NULL, + key TEXT NOT NULL, + value TEXT, + PRIMARY KEY (height, key) + )` +) + +func InsertIbcStoreEntryQuery(height int64, key, value []byte) string { + return fmt.Sprintf( + `INSERT INTO %s(height, key, value) VALUES(%d, '%s', '%s')`, + IbcStoreTableName, + height, + hex.EncodeToString(key), + hex.EncodeToString(value), + ) +} From 373209190b5b39fa4652853fd824a20e071a3b21 Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 18:39:21 +0100 Subject: [PATCH 33/74] Add update IBC state tree logic from postgres DB changes --- persistence/trees/trees.go | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/persistence/trees/trees.go b/persistence/trees/trees.go index b2ef8fd76..bb5c2b07f 100644 --- a/persistence/trees/trees.go +++ b/persistence/trees/trees.go @@ -197,7 +197,13 @@ func (t *treeStore) updateMerkleTrees(pgtx pgx.Tx, txi indexer.TxIndexer, height return "", fmt.Errorf("failed to update flags tree - %w", err) } case IbcTreeName: - // TODO: Implement IBC tree + keys, values, err := sql.GetIBCStoreUpdates(pgtx, height) + if err != nil { + return "", fmt.Errorf("failed to get IBC store updates: %w", err) + } + if err := t.updateIbcTree(keys, values); err != nil { + return "", fmt.Errorf("failed to update IBC tree: %w", err) + } // Default default: t.logger.Panic().Msgf("unhandled merkle tree type: %s", treeName) @@ -349,3 +355,23 @@ func (t *treeStore) updateFlagsTree(flags []*coreTypes.Flag) error { return nil } + +func (t *treeStore) updateIbcTree(keys [][]byte, values [][]byte) error { + if len(keys) != len(values) { + return fmt.Errorf("keys and values must be the same length") + } + for i := 0; i < len(keys); i++ { + key := keys[i] + value := values[i] + if value == nil { + if err := t.merkleTrees[IbcTreeName].tree.Delete(key); err != nil { + return err + } + } else { + if err := t.merkleTrees[IbcTreeName].tree.Update(key, value); err != nil { + return err + } + } + } + return nil +} From 5ffa511cf5790b470f425ed25d8cd80b9708e9bc Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 18:40:32 +0100 Subject: [PATCH 34/74] Remove prefix field from ibc messages --- ibc/types/messages.go | 6 ------ ibc/types/proto/messages.proto | 10 +++++----- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/ibc/types/messages.go b/ibc/types/messages.go index 35bd990a1..e9163b2a1 100644 --- a/ibc/types/messages.go +++ b/ibc/types/messages.go @@ -30,9 +30,6 @@ func (m *UpdateIbcStore) ValidateBasic() coreTypes.Error { if m.Key == nil { return coreTypes.ErrNilField("key") } - if m.Prefix == nil { - return coreTypes.ErrNilField("prefix") - } if m.Value == nil { return coreTypes.ErrNilField("value") } @@ -43,9 +40,6 @@ func (m *PruneIbcStore) ValidateBasic() coreTypes.Error { if m.Key == nil { return coreTypes.ErrNilField("key") } - if m.Prefix == nil { - return coreTypes.ErrNilField("prefix") - } return nil } diff --git a/ibc/types/proto/messages.proto b/ibc/types/proto/messages.proto index e2a62b0f8..39b6c839c 100644 --- a/ibc/types/proto/messages.proto +++ b/ibc/types/proto/messages.proto @@ -5,18 +5,18 @@ package types; option go_package = "github.com/pokt-network/pocket/ibc/types"; // update_ibc_store defines a message representing the addition of a key/value pair to the IBC store +// the key field should be the full key - prefixed with the stores CommitmentPrefix message update_ibc_store { bytes signer = 1; - bytes prefix = 2; - bytes key = 3; - bytes value = 4; + bytes key = 2; + bytes value = 3; } // prune_ibc_store defines a message representing the removal of a key from the IBC store +// the key field should be the full key - prefixed with the stores CommitmentPrefix message prune_ibc_store { bytes signer = 1; - bytes prefix = 2; - bytes key = 3; + bytes key = 2; } // ibc_message defines the different types of IBC message that can be sent across the network From eee1a18707452610397d70f0fee48c404a77c55e Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 18:40:52 +0100 Subject: [PATCH 35/74] Add IBC postgres db update error --- shared/core/types/error.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/shared/core/types/error.go b/shared/core/types/error.go index fbd9323c3..c317ce50a 100644 --- a/shared/core/types/error.go +++ b/shared/core/types/error.go @@ -38,7 +38,7 @@ func NewError(code Code, msg string) Error { } } -// NextCode: 145 +// NextCode: 146 type Code float64 // CONSIDERATION: Should these be a proto enum or a golang iota? //nolint:gosec // G101 - Not hard-coded credentials @@ -184,6 +184,7 @@ const ( CodeCreatingProofError Code = 142 CodeUnknownIBCMessageTypeError Code = 143 CodeNilFieldError Code = 144 + CodeUpdatingIBCStoreDBError Code = 145 ) const ( @@ -326,6 +327,7 @@ const ( CreatingProofError = "an error occurred creating the CommitmentProof" UnknownIBCMessageTypeError = "the ibc message type is not recognized" NilFieldError = "field cannot be nil" + UpdatingIBCStoreDBError = "an error occurred updating the ibc store postgres database" ) func ErrUnknownParam(paramName string) Error { @@ -888,3 +890,7 @@ func ErrUnknownIBCMessageType(messageType string) Error { func ErrNilField(field string) Error { return NewError(CodeNilFieldError, fmt.Sprintf("%s: %s", NilFieldError, field)) } + +func ErrUpdatingIBCStoreDB(err error) Error { + return NewError(CodeUpdatingIBCStoreDBError, fmt.Sprintf("%s: %s", UpdatingIBCStoreDBError, err.Error())) +} From cdec8cae1614e86a2c0e60bffa002be82390bda5 Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Tue, 20 Jun 2023 18:41:24 +0100 Subject: [PATCH 36/74] Add SetIBCStoreEntry method to PostgresContext --- shared/modules/persistence_module.go | 3 +++ utility/unit_of_work/tx_message_handler.go | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/shared/modules/persistence_module.go b/shared/modules/persistence_module.go index 3160ef391..d6c0d8a6b 100644 --- a/shared/modules/persistence_module.go +++ b/shared/modules/persistence_module.go @@ -133,6 +133,9 @@ type PersistenceWriteContext interface { // Flag Operations InitFlags() error SetFlag(paramName string, value any, enabled bool) error + + // IBC Operations + SetIBCStoreEntry(key, value []byte) error } type PersistenceReadContext interface { diff --git a/utility/unit_of_work/tx_message_handler.go b/utility/unit_of_work/tx_message_handler.go index 42de09a07..31e6fe46d 100644 --- a/utility/unit_of_work/tx_message_handler.go +++ b/utility/unit_of_work/tx_message_handler.go @@ -225,13 +225,17 @@ func (u *baseUtilityUnitOfWork) handleMessageChangeParameter(message *typesUtil. return u.updateParam(message.ParameterKey, v) } -// TODO_IN_THIS_COMMIT: implement func (u *baseUtilityUnitOfWork) handleUpdateIbcStore(message *ibcTypes.UpdateIbcStore) coreTypes.Error { + if err := u.persistenceRWContext.SetIBCStoreEntry(message.Key, message.Value); err != nil { + return coreTypes.ErrUpdatingIBCStoreDB(err) + } return nil } -// TODO_IN_THIS_COMMIT: implement func (u *baseUtilityUnitOfWork) handlePruneIbcStore(message *ibcTypes.PruneIbcStore) coreTypes.Error { + if err := u.persistenceRWContext.SetIBCStoreEntry(message.Key, nil); err != nil { + return coreTypes.ErrUpdatingIBCStoreDB(err) + } return nil } From ac619b48434955ae5d86ad0f7a7467ab07a6ff8f Mon Sep 17 00:00:00 2001 From: Harry Law <53987565+h5law@users.noreply.github.com> Date: Wed, 21 Jun 2023 11:05:38 +0100 Subject: [PATCH 37/74] Address linter errors --- ibc/store/provable_store.go | 9 +++++---- persistence/sql/sql.go | 2 +- persistence/trees/trees.go | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/ibc/store/provable_store.go b/ibc/store/provable_store.go index 421ebb32d..789685c21 100644 --- a/ibc/store/provable_store.go +++ b/ibc/store/provable_store.go @@ -5,7 +5,6 @@ import ( ics23 "github.com/cosmos/ics23/go" "github.com/pokt-network/pocket/ibc/host" - "github.com/pokt-network/pocket/persistence/kvstore" coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/modules" "github.com/pokt-network/smt" @@ -14,10 +13,12 @@ import ( var _ modules.ProvableStore = &provableStore{} // provableStore implements the ProvableStore interface and wraps an SMT +// it operates in memory and thus cannot make any changes to the underlying +// database. All changes must be propagated through the `IbcMessage` type +// and added to the mempool for inclusion in the next block type provableStore struct { - prefix coreTypes.CommitmentPrefix - tree *smt.SMT - nodeStore kvstore.KVStore + prefix coreTypes.CommitmentPrefix + tree *smt.SMT } // GetCommitmentPrefix returns the commitment prefix of the provable store diff --git a/persistence/sql/sql.go b/persistence/sql/sql.go index 274e20979..85b692c9e 100644 --- a/persistence/sql/sql.go +++ b/persistence/sql/sql.go @@ -166,7 +166,7 @@ func GetParams(pgtx pgx.Tx, height uint64) ([]*coreTypes.Param, error) { } // GetIBCStoreUpdates returns the set of key-value pairs updated at the current height for the IBC store -func GetIBCStoreUpdates(pgtx pgx.Tx, height uint64) (keys [][]byte, values [][]byte, err error) { +func GetIBCStoreUpdates(pgtx pgx.Tx, height uint64) (keys, values [][]byte, err error) { fields := "key,value" query := fmt.Sprintf("SELECT %s FROM %s WHERE height=%d ORDER BY key ASC", fields, ptypes.IbcStoreTableName, height) rows, err := pgtx.Query(context.TODO(), query) diff --git a/persistence/trees/trees.go b/persistence/trees/trees.go index bb5c2b07f..b06591969 100644 --- a/persistence/trees/trees.go +++ b/persistence/trees/trees.go @@ -356,7 +356,7 @@ func (t *treeStore) updateFlagsTree(flags []*coreTypes.Flag) error { return nil } -func (t *treeStore) updateIbcTree(keys [][]byte, values [][]byte) error { +func (t *treeStore) updateIbcTree(keys, values [][]byte) error { if len(keys) != len(values) { return fmt.Errorf("keys and values must be the same length") } From ff447f7bd9bed655f88bae7b0b22ef0afacab8fa Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 22 Jun 2023 14:12:23 +0100 Subject: [PATCH 38/74] Add HandleMessage unit tests --- ibc/handle_message_test.go | 278 +++++++++++++++++++++++++++++++++++++ ibc/main_test.go | 144 +++++++++++++++++++ ibc/module.go | 4 - 3 files changed, 422 insertions(+), 4 deletions(-) create mode 100644 ibc/handle_message_test.go create mode 100644 ibc/main_test.go diff --git a/ibc/handle_message_test.go b/ibc/handle_message_test.go new file mode 100644 index 000000000..2ad4a7956 --- /dev/null +++ b/ibc/handle_message_test.go @@ -0,0 +1,278 @@ +package ibc + +import ( + "fmt" + "strconv" + "strings" + "testing" + + ibcTypes "github.com/pokt-network/pocket/ibc/types" + "github.com/pokt-network/pocket/persistence/indexer" + "github.com/pokt-network/pocket/shared/codec" + "github.com/pokt-network/pocket/shared/core/types" + coreTypes "github.com/pokt-network/pocket/shared/core/types" + "github.com/pokt-network/pocket/shared/crypto" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" +) + +func TestHandleMessage_ErrorAlreadyInMempool(t *testing.T) { + // Prepare test data + _, tx := prepareUpdateMessage(t, []byte("key"), []byte("value")) + txProtoBytes, err := proto.Marshal(tx) + require.NoError(t, err) + + // Prepare the environment + _, utilityMod, _, _ := prepareEnvironment(t, 1, 0, 0, 0) + + // Manually add the tx to the mempool + err = utilityMod.GetMempool().AddTx(txProtoBytes) + require.NoError(t, err) + + // Error on having a duplciate transaction + err = utilityMod.HandleTransaction(txProtoBytes) + require.Error(t, err) + require.EqualError(t, err, coreTypes.ErrDuplicateTransaction().Error()) +} + +func TestHandleMessage_ErrorAlreadyCommitted(t *testing.T) { + // Prepare the environment + _, utilityMod, persistenceMod, _ := prepareEnvironment(t, 0, 0, 0, 0) + idxTx := prepareIndexedMessage(t, persistenceMod.GetTxIndexer()) + + // Error on having an indexed transaction + err := utilityMod.HandleTransaction(idxTx.Tx) + require.Error(t, err) + require.EqualError(t, err, coreTypes.ErrTransactionAlreadyCommitted().Error()) +} + +func TestHandleMessage_BasicValidation_Message(t *testing.T) { + updateMsg, _ := prepareUpdateMessage(t, []byte("key"), []byte("value")) + require.NoError(t, updateMsg.ValidateBasic()) + pruneMsg, _ := preparePruneMessage(t, []byte("key")) + require.NoError(t, pruneMsg.ValidateBasic()) + + testCases := []struct { + name string + msg *ibcTypes.IbcMessage + expected error + }{ + { + name: "Invalid Update Message: Empty Key", + msg: CreateUpdateStoreMessage(nil, []byte("value")), + expected: coreTypes.ErrNilField("key"), + }, + { + name: "Invalid Update Message: Empty Value", + msg: CreateUpdateStoreMessage([]byte("key"), nil), + expected: coreTypes.ErrNilField("value"), + }, + { + name: "Invalid Prune Message: Empty Key", + msg: CreatePruneStoreMessage(nil), + expected: coreTypes.ErrNilField("key"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + require.EqualError(t, err, tc.expected.Error()) + }) + } +} + +func TestHandleMessage_BasicValidation_Transaction(t *testing.T) { + privKey, err := crypto.GeneratePrivateKey() + require.NoError(t, err) + + pubKey := privKey.PublicKey() + + message, validTx := prepareUpdateMessage(t, []byte("key"), []byte("value")) + anyMessage, err := codec.GetCodec().ToAny(message) + require.NoError(t, err) + err = validTx.Sign(privKey) + require.NoError(t, err) + + testCases := []struct { + name string + txProto *coreTypes.Transaction + expectedErr error + }{ + { + name: "Invalid transaction: Missing Nonce", + txProto: &types.Transaction{}, + expectedErr: types.ErrEmptyNonce(), + }, + { + name: "Invalid transaction: Missing Signature Structure", + txProto: &types.Transaction{ + Nonce: strconv.Itoa(int(crypto.GetNonce())), + }, + expectedErr: types.ErrEmptySignatureStructure(), + }, + { + name: "Invalid transaction: Missing Signature", + txProto: &types.Transaction{ + Nonce: strconv.Itoa(int(crypto.GetNonce())), + Signature: &types.Signature{ + PublicKey: nil, + Signature: nil, + }, + }, + expectedErr: types.ErrEmptySignature(), + }, + { + name: "Invalid transaction: Missing Public Key", + txProto: &types.Transaction{ + Nonce: strconv.Itoa(int(crypto.GetNonce())), + Signature: &types.Signature{ + PublicKey: nil, + Signature: []byte("bytes in place for signature but not actually valid"), + }, + }, + expectedErr: types.ErrEmptyPublicKey(), + }, + { + name: "Invalid transaction: Invalid Public Key", + txProto: &types.Transaction{ + Nonce: strconv.Itoa(int(crypto.GetNonce())), + Signature: &types.Signature{ + PublicKey: []byte("invalid pub key"), + Signature: []byte("bytes in place for signature but not actually valid"), + }, + }, + expectedErr: types.ErrNewPublicKeyFromBytes(fmt.Errorf("the public key length is not valid, expected length 32, actual length: 15")), + }, + { + name: "Invalid transaction: Invalid Message", + txProto: &types.Transaction{ + Nonce: strconv.Itoa(int(crypto.GetNonce())), + Signature: &types.Signature{ + PublicKey: pubKey.Bytes(), + Signature: []byte("bytes in place for signature but not actually valid"), + }, + Msg: nil, + }, + expectedErr: types.ErrDecodeMessage(fmt.Errorf("proto: invalid empty type URL")), + }, + { + name: "Invalid transaction: Invalid Signature", + txProto: &types.Transaction{ + Nonce: strconv.Itoa(int(crypto.GetNonce())), + Signature: &types.Signature{ + PublicKey: pubKey.Bytes(), + Signature: []byte("invalid signature"), + }, + Msg: anyMessage, + }, + expectedErr: types.ErrSignatureVerificationFailed(), + }, + { + name: "Valid well-formatted transaction with valid signature", + txProto: validTx, + expectedErr: nil, + }, + } + + // Prepare the environment + _, utilityMod, _, _ := prepareEnvironment(t, 1, 0, 0, 0) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + txProtoBytes, err := proto.Marshal(tc.txProto) + require.NoError(t, err) + + err = utilityMod.HandleTransaction(txProtoBytes) + if tc.expectedErr != nil { + expected := tc.expectedErr.Error() + if strings.Contains(err.Error(), "\u00a0") { + expected = strings.Replace(expected, "proto: ", "proto:\u00a0", 1) + } + require.EqualError(t, err, expected) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestHandleMessage_GetIndexedMessage(t *testing.T) { + // Prepare the environment + _, utilityMod, persistenceMod, _ := prepareEnvironment(t, 1, 0, 0, 0) + idxTx := prepareIndexedMessage(t, persistenceMod.GetTxIndexer()) + + tests := []struct { + name string + txProtoBytes []byte + txExists bool + expectErr error + }{ + {"returns indexed transaction when it exists", idxTx.Tx, true, nil}, + {"returns error when transaction doesn't exist", []byte("Does not exist"), false, types.ErrTransactionNotCommitted()}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + idTx, err := utilityMod.GetIndexedTransaction(test.txProtoBytes) + if test.expectErr != nil { + require.EqualError(t, err, test.expectErr.Error()) + require.Nil(t, idTx) + } else { + require.NoError(t, err) + require.NotNil(t, idTx) + } + }) + } +} + +func TestHandleMessage_AddToMempool(t *testing.T) { + // prepare the environment + _, _, _, ibcMod := prepareEnvironment(t, 1, 0, 0, 0) + require.Len(t, ibcMod.GetBus().GetUtilityModule().GetMempool().GetAll(), 0) + msg, _ := prepareUpdateMessage(t, []byte("key"), []byte("value")) + anyMsg, err := codec.GetCodec().ToAny(msg) + require.NoError(t, err) + require.NoError(t, ibcMod.HandleMessage(anyMsg)) + require.Len(t, ibcMod.GetBus().GetUtilityModule().GetMempool().GetAll(), 1) +} + +func prepareUpdateMessage(t *testing.T, key, value []byte) (*ibcTypes.IbcMessage, *coreTypes.Transaction) { + t.Helper() + msg := CreateUpdateStoreMessage(key, value) + tx, err := ConvertIBCMessageToTx(msg) + require.NoError(t, err) + return msg, tx +} + +func preparePruneMessage(t *testing.T, key []byte) (*ibcTypes.IbcMessage, *coreTypes.Transaction) { + t.Helper() + msg := CreatePruneStoreMessage(key) + tx, err := ConvertIBCMessageToTx(msg) + require.NoError(t, err) + return msg, tx +} + +func prepareIndexedMessage(t *testing.T, txIndexer indexer.TxIndexer) *coreTypes.IndexedTransaction { + t.Helper() + _, tx := preparePruneMessage(t, []byte{}) + txProtoBytes, err := proto.Marshal(tx) + require.NoError(t, err) + + // Test data - Prepare IndexedTransaction + idxTx := &coreTypes.IndexedTransaction{ + Tx: txProtoBytes, + Height: 0, + Index: 0, + ResultCode: 0, + Error: "h5law", + SignerAddr: "h5law", + RecipientAddr: "h5law", + MessageType: "h5law", + } + + // Index a test transaction + err = txIndexer.Index(idxTx) + require.NoError(t, err) + + return idxTx +} diff --git a/ibc/main_test.go b/ibc/main_test.go new file mode 100644 index 000000000..dfb2bf1a5 --- /dev/null +++ b/ibc/main_test.go @@ -0,0 +1,144 @@ +package ibc + +import ( + "log" + "os" + "testing" + + "github.com/pokt-network/pocket/persistence" + "github.com/pokt-network/pocket/runtime" + "github.com/pokt-network/pocket/runtime/configs" + "github.com/pokt-network/pocket/runtime/test_artifacts" + "github.com/pokt-network/pocket/runtime/test_artifacts/keygen" + "github.com/pokt-network/pocket/shared/messaging" + "github.com/pokt-network/pocket/shared/modules" + "github.com/pokt-network/pocket/utility" + "github.com/stretchr/testify/require" +) + +var dbURL string + +// NB: `TestMain` serves all tests in the immediate `ibc` package and not its children +func TestMain(m *testing.M) { + pool, resource, url := test_artifacts.SetupPostgresDocker() + dbURL = url + + exitCode := m.Run() + test_artifacts.CleanupPostgresDocker(m, pool, resource) + os.Exit(exitCode) +} + +func newTestUtilityModule(bus modules.Bus) modules.UtilityModule { + utilityMod, err := utility.Create(bus) + if err != nil { + log.Fatalf("Error creating utility module: %s", err) + } + return utilityMod.(modules.UtilityModule) +} + +func newTestPersistenceModule(bus modules.Bus) modules.PersistenceModule { + persistenceMod, err := persistence.Create(bus) + if err != nil { + log.Fatalf("Error creating persistence module: %s", err) + } + return persistenceMod.(modules.PersistenceModule) +} + +func newTestIbcModule(bus modules.Bus) modules.IBCModule { + ibcMod, err := Create(bus) + if err != nil { + log.Fatalf("Error creating ibc module: %s", err) + } + return ibcMod.(modules.IBCModule) +} + +// Prepares a runtime environment for testing along with a genesis state, a persistence module and a utility module +func prepareEnvironment( + t *testing.T, + numValidators, // nolint:unparam // we are not currently modifying parameter but want to keep it modifiable in the future + numServicers, + numApplications, + numFisherman int, + genesisOpts ...test_artifacts.GenesisOption, +) (*runtime.Manager, modules.UtilityModule, modules.PersistenceModule, modules.IBCModule) { + teardownDeterministicKeygen := keygen.GetInstance().SetSeed(42) + + runtimeCfg := newTestRuntimeConfig(numValidators, numServicers, numApplications, numFisherman, genesisOpts...) + bus, err := runtime.CreateBus(runtimeCfg) + require.NoError(t, err) + + testPersistenceMod := newTestPersistenceModule(bus) + err = testPersistenceMod.Start() + require.NoError(t, err) + + testUtilityMod := newTestUtilityModule(bus) + err = testUtilityMod.Start() + require.NoError(t, err) + + testIbcMod := newTestIbcModule(bus) + err = testIbcMod.Start() + require.NoError(t, err) + + // Reset database to genesis before every test + err = testPersistenceMod.HandleDebugMessage(&messaging.DebugMessage{ + Action: messaging.DebugMessageAction_DEBUG_PERSISTENCE_RESET_TO_GENESIS, + Message: nil, + }) + require.NoError(t, err) + + t.Cleanup(func() { + teardownDeterministicKeygen() + err := testPersistenceMod.Stop() + require.NoError(t, err) + err = testUtilityMod.Stop() + require.NoError(t, err) + err = testIbcMod.Stop() + require.NoError(t, err) + }) + + return runtimeCfg, testUtilityMod, testPersistenceMod, testIbcMod +} + +func newTestRuntimeConfig( + numValidators, + numServicers, + numApplications, + numFisherman int, + genesisOpts ...test_artifacts.GenesisOption, +) *runtime.Manager { + cfg, err := configs.CreateTempConfig(&configs.Config{ + Utility: &configs.UtilityConfig{ + MaxMempoolTransactionBytes: 1000000, + MaxMempoolTransactions: 1000, + }, + Persistence: &configs.PersistenceConfig{ + PostgresUrl: dbURL, + NodeSchema: "test_schema", + BlockStorePath: ":memory:", + TxIndexerPath: ":memory:", + TreesStoreDir: ":memory:", + MaxConnsCount: 50, + MinConnsCount: 1, + MaxConnLifetime: "5m", + MaxConnIdleTime: "1m", + HealthCheckPeriod: "30s", + }, + Validator: &configs.ValidatorConfig{Enabled: true}, + IBC: &configs.IBCConfig{ + Enabled: true, + PrivateKey: "0ca1a40ddecdab4f5b04fa0bfed1d235beaa2b8082e7554425607516f0862075dfe357de55649e6d2ce889acf15eb77e94ab3c5756fe46d3c7538d37f27f115e", + }, + }) + if err != nil { + log.Fatalf("Error creating config: %s", err) + } + genesisState, _ := test_artifacts.NewGenesisState( + numValidators, + numServicers, + numApplications, + numFisherman, + genesisOpts..., + ) + runtimeCfg := runtime.NewManager(cfg, genesisState) + return runtimeCfg +} diff --git a/ibc/module.go b/ibc/module.go index 6390479a5..9a10cd163 100644 --- a/ibc/module.go +++ b/ibc/module.go @@ -11,7 +11,6 @@ import ( "github.com/pokt-network/pocket/shared/codec" coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/crypto" - "github.com/pokt-network/pocket/shared/messaging" "github.com/pokt-network/pocket/shared/modules" "github.com/pokt-network/pocket/shared/modules/base_modules" "google.golang.org/protobuf/types/known/anypb" @@ -92,9 +91,6 @@ func (m *ibcModule) HandleMessage(message *anypb.Any) error { defer m.m.Unlock() // Check the message is actually a valid IBC message - if message.MessageName() != messaging.IbcMessageContentType { - return coreTypes.ErrUnknownIBCMessageType(string(message.MessageName())) - } msg, err := codec.GetCodec().FromAny(message) if err != nil { return err From 6e4a510f0fc08b4f2382e533dfd11e374c98a23e Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 22 Jun 2023 14:18:29 +0100 Subject: [PATCH 39/74] Simplify tests as covered in utility --- ibc/handle_message_test.go | 115 +++---------------------------------- 1 file changed, 8 insertions(+), 107 deletions(-) diff --git a/ibc/handle_message_test.go b/ibc/handle_message_test.go index 2ad4a7956..4a89367d5 100644 --- a/ibc/handle_message_test.go +++ b/ibc/handle_message_test.go @@ -1,9 +1,6 @@ package ibc import ( - "fmt" - "strconv" - "strings" "testing" ibcTypes "github.com/pokt-network/pocket/ibc/types" @@ -83,118 +80,22 @@ func TestHandleMessage_BasicValidation_Message(t *testing.T) { } func TestHandleMessage_BasicValidation_Transaction(t *testing.T) { + // Prepare the environment + _, utilityMod, _, _ := prepareEnvironment(t, 1, 0, 0, 0) + privKey, err := crypto.GeneratePrivateKey() require.NoError(t, err) - pubKey := privKey.PublicKey() - - message, validTx := prepareUpdateMessage(t, []byte("key"), []byte("value")) - anyMessage, err := codec.GetCodec().ToAny(message) + _, validTx := prepareUpdateMessage(t, []byte("key"), []byte("value")) require.NoError(t, err) err = validTx.Sign(privKey) require.NoError(t, err) - testCases := []struct { - name string - txProto *coreTypes.Transaction - expectedErr error - }{ - { - name: "Invalid transaction: Missing Nonce", - txProto: &types.Transaction{}, - expectedErr: types.ErrEmptyNonce(), - }, - { - name: "Invalid transaction: Missing Signature Structure", - txProto: &types.Transaction{ - Nonce: strconv.Itoa(int(crypto.GetNonce())), - }, - expectedErr: types.ErrEmptySignatureStructure(), - }, - { - name: "Invalid transaction: Missing Signature", - txProto: &types.Transaction{ - Nonce: strconv.Itoa(int(crypto.GetNonce())), - Signature: &types.Signature{ - PublicKey: nil, - Signature: nil, - }, - }, - expectedErr: types.ErrEmptySignature(), - }, - { - name: "Invalid transaction: Missing Public Key", - txProto: &types.Transaction{ - Nonce: strconv.Itoa(int(crypto.GetNonce())), - Signature: &types.Signature{ - PublicKey: nil, - Signature: []byte("bytes in place for signature but not actually valid"), - }, - }, - expectedErr: types.ErrEmptyPublicKey(), - }, - { - name: "Invalid transaction: Invalid Public Key", - txProto: &types.Transaction{ - Nonce: strconv.Itoa(int(crypto.GetNonce())), - Signature: &types.Signature{ - PublicKey: []byte("invalid pub key"), - Signature: []byte("bytes in place for signature but not actually valid"), - }, - }, - expectedErr: types.ErrNewPublicKeyFromBytes(fmt.Errorf("the public key length is not valid, expected length 32, actual length: 15")), - }, - { - name: "Invalid transaction: Invalid Message", - txProto: &types.Transaction{ - Nonce: strconv.Itoa(int(crypto.GetNonce())), - Signature: &types.Signature{ - PublicKey: pubKey.Bytes(), - Signature: []byte("bytes in place for signature but not actually valid"), - }, - Msg: nil, - }, - expectedErr: types.ErrDecodeMessage(fmt.Errorf("proto: invalid empty type URL")), - }, - { - name: "Invalid transaction: Invalid Signature", - txProto: &types.Transaction{ - Nonce: strconv.Itoa(int(crypto.GetNonce())), - Signature: &types.Signature{ - PublicKey: pubKey.Bytes(), - Signature: []byte("invalid signature"), - }, - Msg: anyMessage, - }, - expectedErr: types.ErrSignatureVerificationFailed(), - }, - { - name: "Valid well-formatted transaction with valid signature", - txProto: validTx, - expectedErr: nil, - }, - } - - // Prepare the environment - _, utilityMod, _, _ := prepareEnvironment(t, 1, 0, 0, 0) - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - txProtoBytes, err := proto.Marshal(tc.txProto) - require.NoError(t, err) + txProtoBytes, err := proto.Marshal(validTx) + require.NoError(t, err) - err = utilityMod.HandleTransaction(txProtoBytes) - if tc.expectedErr != nil { - expected := tc.expectedErr.Error() - if strings.Contains(err.Error(), "\u00a0") { - expected = strings.Replace(expected, "proto: ", "proto:\u00a0", 1) - } - require.EqualError(t, err, expected) - } else { - require.NoError(t, err) - } - }) - } + err = utilityMod.HandleTransaction(txProtoBytes) + require.NoError(t, err) } func TestHandleMessage_GetIndexedMessage(t *testing.T) { From 895dfd0ee7b7f5cb743ab5fd3fb0b4200aed84bd Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 22 Jun 2023 14:24:35 +0100 Subject: [PATCH 40/74] Update validation testcases --- ibc/handle_message_test.go | 40 ++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/ibc/handle_message_test.go b/ibc/handle_message_test.go index 4a89367d5..44076f7d3 100644 --- a/ibc/handle_message_test.go +++ b/ibc/handle_message_test.go @@ -10,13 +10,12 @@ import ( coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/crypto" "github.com/stretchr/testify/require" - "google.golang.org/protobuf/proto" ) func TestHandleMessage_ErrorAlreadyInMempool(t *testing.T) { // Prepare test data _, tx := prepareUpdateMessage(t, []byte("key"), []byte("value")) - txProtoBytes, err := proto.Marshal(tx) + txProtoBytes, err := codec.GetCodec().Marshal(tx) require.NoError(t, err) // Prepare the environment @@ -91,11 +90,44 @@ func TestHandleMessage_BasicValidation_Transaction(t *testing.T) { err = validTx.Sign(privKey) require.NoError(t, err) - txProtoBytes, err := proto.Marshal(validTx) + txProtoBytes, err := codec.GetCodec().Marshal(validTx) require.NoError(t, err) err = utilityMod.HandleTransaction(txProtoBytes) require.NoError(t, err) + + testCases := []struct { + name string + msg *ibcTypes.IbcMessage + expected error + }{ + { + name: "Invalid Update Message: Empty Key", + msg: CreateUpdateStoreMessage(nil, []byte("value")), + expected: coreTypes.ErrEmptySignatureStructure(), + }, + { + name: "Invalid Update Message: Empty Value", + msg: CreateUpdateStoreMessage([]byte("key"), nil), + expected: coreTypes.ErrEmptySignatureStructure(), + }, + { + name: "Invalid Prune Message: Empty Key", + msg: CreatePruneStoreMessage(nil), + expected: coreTypes.ErrEmptySignatureStructure(), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tx, err := ConvertIBCMessageToTx(tc.msg) + require.NoError(t, err) + txProtoBytes, err := codec.GetCodec().Marshal(tx) + require.NoError(t, err) + err = utilityMod.HandleTransaction(txProtoBytes) + require.EqualError(t, err, tc.expected.Error()) + }) + } } func TestHandleMessage_GetIndexedMessage(t *testing.T) { @@ -156,7 +188,7 @@ func preparePruneMessage(t *testing.T, key []byte) (*ibcTypes.IbcMessage, *coreT func prepareIndexedMessage(t *testing.T, txIndexer indexer.TxIndexer) *coreTypes.IndexedTransaction { t.Helper() _, tx := preparePruneMessage(t, []byte{}) - txProtoBytes, err := proto.Marshal(tx) + txProtoBytes, err := codec.GetCodec().Marshal(tx) require.NoError(t, err) // Test data - Prepare IndexedTransaction From ac2a97449fb0148e188e4e73faf7e9b967162db9 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 22 Jun 2023 14:26:04 +0100 Subject: [PATCH 41/74] Fix importing twice --- ibc/handle_message_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ibc/handle_message_test.go b/ibc/handle_message_test.go index 44076f7d3..a6a2b22f4 100644 --- a/ibc/handle_message_test.go +++ b/ibc/handle_message_test.go @@ -6,7 +6,6 @@ import ( ibcTypes "github.com/pokt-network/pocket/ibc/types" "github.com/pokt-network/pocket/persistence/indexer" "github.com/pokt-network/pocket/shared/codec" - "github.com/pokt-network/pocket/shared/core/types" coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/crypto" "github.com/stretchr/testify/require" @@ -142,7 +141,7 @@ func TestHandleMessage_GetIndexedMessage(t *testing.T) { expectErr error }{ {"returns indexed transaction when it exists", idxTx.Tx, true, nil}, - {"returns error when transaction doesn't exist", []byte("Does not exist"), false, types.ErrTransactionNotCommitted()}, + {"returns error when transaction doesn't exist", []byte("Does not exist"), false, coreTypes.ErrTransactionNotCommitted()}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { From dbf7f2bd8209f00fec8a43fe5dfee3a3ff7179ce Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 22 Jun 2023 14:35:23 +0100 Subject: [PATCH 42/74] Add nolint comments --- ibc/main_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ibc/main_test.go b/ibc/main_test.go index dfb2bf1a5..a2f9abe7c 100644 --- a/ibc/main_test.go +++ b/ibc/main_test.go @@ -53,6 +53,8 @@ func newTestIbcModule(bus modules.Bus) modules.IBCModule { } // Prepares a runtime environment for testing along with a genesis state, a persistence module and a utility module +// +//nolint:unparam // Test suite is not fully built out yet func prepareEnvironment( t *testing.T, numValidators, // nolint:unparam // we are not currently modifying parameter but want to keep it modifiable in the future @@ -99,6 +101,7 @@ func prepareEnvironment( return runtimeCfg, testUtilityMod, testPersistenceMod, testIbcMod } +//nolint:unparam // Test suite is not fully built out yet func newTestRuntimeConfig( numValidators, numServicers, From 0c05028ee5a3c7b2c9349724a2ef3f00b0abe67f Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 22 Jun 2023 20:27:51 +0100 Subject: [PATCH 43/74] Check msg equality in test mempool test --- ibc/handle_message_test.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/ibc/handle_message_test.go b/ibc/handle_message_test.go index a6a2b22f4..11aca854d 100644 --- a/ibc/handle_message_test.go +++ b/ibc/handle_message_test.go @@ -165,7 +165,23 @@ func TestHandleMessage_AddToMempool(t *testing.T) { anyMsg, err := codec.GetCodec().ToAny(msg) require.NoError(t, err) require.NoError(t, ibcMod.HandleMessage(anyMsg)) - require.Len(t, ibcMod.GetBus().GetUtilityModule().GetMempool().GetAll(), 1) + mempoolTxs := ibcMod.GetBus().GetUtilityModule().GetMempool().GetAll() + + require.Len(t, mempoolTxs, 1) + mTx := mempoolTxs[0] + require.NotNil(t, mTx) + txProto := &coreTypes.Transaction{} + err = codec.GetCodec().Unmarshal(mTx, txProto) + require.NoError(t, err) + ibcMsg := &ibcTypes.UpdateIbcStore{} + require.NoError(t, txProto.GetMsg().UnmarshalTo(ibcMsg)) + + // Compare messages are equal + origBz, err := codec.GetCodec().Marshal(msg.GetUpdate()) + require.NoError(t, err) + ibcMsgBz, err := codec.GetCodec().Marshal(ibcMsg) + require.NoError(t, err) + require.Equal(t, origBz, ibcMsgBz) } func prepareUpdateMessage(t *testing.T, key, value []byte) (*ibcTypes.IbcMessage, *coreTypes.Transaction) { From 2cf0ff29a2668b1bf3a50cf48caccbf46a626c89 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 22 Jun 2023 21:50:32 +0100 Subject: [PATCH 44/74] Add new issue comments --- ibc/host.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ibc/host.go b/ibc/host.go index 35b7d64b1..c04e9b68a 100644 --- a/ibc/host.go +++ b/ibc/host.go @@ -1,3 +1,8 @@ +// TECHDEBT(#854): The host should be a submodule with access to the bus, this allows for the +// host to access the persistence module and thus the tree store in order to create local +// copies of the IBC state tree with the GetProvableStore() function. Bus access also will +// allow for the host to send any local changes to these stores as IbcMessage types through +// the P2P module's Broadcast() function to allow them to be included in the next block. package ibc import ( @@ -18,7 +23,10 @@ func (h *host) GetTimestamp() uint64 { return uint64(time.Now().Unix()) } -// GetProvableStore returns +// GetProvableStore returns a copy of the IBC state tree where all operations happen +// locally (in memory) and are not persisted to the database. All changes are instead +// broadcasted to the network for inclusion in the next block. +// TODO(#854): Implement this func (h *host) GetProvableStore(prefix coreTypes.CommitmentPrefix) (modules.ProvableStore, error) { return nil, nil } From 7cbbe6e7d41d6ec5f84992b2f4176983e5264c10 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Fri, 23 Jun 2023 15:08:02 +0100 Subject: [PATCH 45/74] Add mockgen flag for ProvableStore --- shared/modules/ibc_module.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/modules/ibc_module.go b/shared/modules/ibc_module.go index 52427a2e1..1ebc5b601 100644 --- a/shared/modules/ibc_module.go +++ b/shared/modules/ibc_module.go @@ -6,7 +6,7 @@ import ( "google.golang.org/protobuf/types/known/anypb" ) -//go:generate mockgen -destination=./mocks/ibc_module_mock.go github.com/pokt-network/pocket/shared/modules IBCModule,IBCHost,IBCHandler +//go:generate mockgen -destination=./mocks/ibc_module_mock.go github.com/pokt-network/pocket/shared/modules IBCModule,IBCHost,IBCHandler,ProvableStore const IBCModuleName = "ibc" From a1edf4bf00f85e18cb13cbeb1aaf4177423be2c2 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Fri, 23 Jun 2023 15:22:02 +0100 Subject: [PATCH 46/74] Address comments --- ibc/docs/ics24.md | 82 +++++++++++++++++++++++++++++++++++++++++++++++ ibc/host.go | 10 +++--- 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/ibc/docs/ics24.md b/ibc/docs/ics24.md index 8834c4b88..cc3226a5a 100644 --- a/ibc/docs/ics24.md +++ b/ibc/docs/ics24.md @@ -4,6 +4,12 @@ - [Implementation](#implementation) - [Paths and Identifiers](#paths-and-identifiers) - [Timestamps](#timestamps) +- [IBC State](#ibc-state) + - [IBC State Tree](#ibc-state-tree) + - [IBC Messages](#ibc-messages) + - [IBC Message Handling](#ibc-message-handling) + - [Mempool](#mempool) + - [State Transition](#state-transition) ## Overview @@ -11,12 +17,35 @@ As token transfers as defined in [ICS-20][ics20] work on a lock and mint pattern, any tokens sent from **chain A** to **chain B** will have a denomination unique to the connection/channel/port combination that the packet was sent over. This means that if a host where to shutdown a connection or channel without warning any tokens yet to be returned to the host chain would be lost. For this reason, only validator nodes are able to become hosts, as they provide the most reliability out of the different node types. +## Host Configuration + +Only validators can be configured to be IBC hosts. If the IBC module, during its creation, detects the node is a validator (and the IBC `enabled` field in the config is `true`) it will automatically create a host. + +```json +"ibc": { + "enabled": bool, + "private_key": string +} +``` + +The `PrivateKey` field of the configuration is used to sign IBC store related messages and state transitions for inclusion in the block. + ## Implementation **Note**: The ICS-24 implementation is still a work in progress and is not yet fully implemented. ICS-24 has numerous sub components that must be implemented in order for the host to be fully functional. These range from type definitions for identifiers, paths and stores as well as the methods to interact with them. Alongside these ICS-24 also defines the Event Logging system which is used to store the packet data and timeouts for the relayers to read, as only the `CommitmentProof` objects are committed to the chain state. In addition to these numerous other features are part of ICS-24 that are closely linked to other ICS components such as consensus state introspection and client state validation. +### Persistence + +The IBC stores must be included in the networks consensus state as one of the many state trees. This is to ensure the IBC light clients verifying Pocket network's state can verify the inclusion or exclusion of IBC related information from the block headers. + +As the IBC host will make changes to the IBC store locally, in response to functions being called by relayers, they require these changes to be propagated throughout the network (i.e. the mempool) and included in all other node's IBC stores so that during block production these changes are reflected in the state transition. This is done by utilizing the existing transaction workflow, adding the IBC store change messages to the mempool and then handling them as a new message type in block production/application logic. + +TODO_IN_THIS_COMMIT: Mermaid Diagram Here + +See: [IBC State](#ibc-state) below for more details on the IBC state transition process. + ### Paths and Identifiers Paths are defined as bytestrings that are used to access the elements in the different stores. They are built with the function `ApplyPrefix()` which takes a store key as a prefix and a path string and will return the key to access an element in the specific store. The logic for paths can be found in [host/keys.go](../host/keys.go) and [host/prefix.go](../host/prefix.go) @@ -27,6 +56,59 @@ Identifiers are bytestrings constrained to specific characters and lengths depen The `GetTimestamp()` function returns the current unix timestamp of the host machine and is used to calculate timeout periods for packets +## IBC State + +As mentioned [above](#persistence) the IBC store **must** be included in the consensus state of the network. As such the IBC store as defined in [ICS-24][ics24] has been implemented as a single IBC state tree. + +### IBC State Tree + +The IBC state tree is a non-value hashing `SMT` backed by a persistent `KVStore`, this is due to its need for data retrieval as well as proof generation/verification. The root hash of the IBC state tree is included in the `rootTree` which computes the networks state hash for any given block. This allows verifiers to not only verify the inclusion/exclusion of any element in the IBC state tree itself but also that the IBC state tree was used to compute the network's state hash, by utilising the `CommitmentProof` object defined in [ICS-23][ics23]. + +### IBC Messages + +As the hosts make changes to this IBC store locally (creating light clients, opening connections, and sending packets for example); these changes must be propagated throughout the network to ensure the IBC state tree is consistent across all nodes. This is achieved by the new `IbcMessage` type defined in [ibc/types/proto/messages.proto](../types/proto/messages.proto). This type acts as an enum representing two possible state transition events: + +- `UpdateIbcStore`: Updating the store with a key-value pair; adding a new or updating an existing element +- `PruneIbcStore`: Pruning the store via its key; removal of an existing element + +_Note: In both types described above the `key` field **must** already be prefixed with the `CommitmentPrefix` and should be a valid path in the store._ + +When changes are made locally they are not committed to the IBC store itself but are instead used to create an `IbcMessage` which is broadcasted to the network. This is akin to a simple send transaction that has been propagated throughout the mempool but has not been committed to the on-chain state. + +### IBC Message Handling + +Upon a node receiving an `IbcMessage` from the event bus it will use the `HandleMessage()` method of the `IBCModule` to add this message to the transactions mempool via the following steps: + +1. Wrap the `IbcMessage` within a `Transaction` +2. Sign the `Transaction` using the `IBCModule`'s private key +3. Broadcast the `Transaction` throughout the mempool + +TODO_IN_THIS_COMMIT: Mermaid Diagram Here + +See: [ibc/module.go](../module.go) for the specific implementation details. + +### Mempool + +With the `IbcMessage` now propagated through the network's mempool, when it is reaped (by the block proposer) the message's validity will be handled by first determining the type of the `IbcMessage`: + +- `UpdateIbcStore`: The `key` and `value` fields of the `IbcMessage` are inserted into the `ibc_messages` Postgres table along with the current height +- `PruneIbcStore`: The `key` with a `nil` value is passed into the `ibc_messages` Postgres table along with the current height + +_Note: Prior to insertion the `key` and `value` fields of the messages are hexadecimally encoded into strings._ + +### State Transition + +When the new state hash is computed, the different state trees read the updates from their respective Postgres tables and update the trees accordingly. For each entry in the `ibc_message` table depending on the entries `value` field the tree will perform one of two operations: + +- `value == nil` + - This is a `PruneIbcStore` message and thus the tree will delete the entry with the given `key` + - `ibcTree.Delete(key)` +- `value != nil` + - This is an `UpdateIbcStore` message and thus the tree will update the entry with the given `key` to have the given `value` + - `ibcTree.Update(key, value)` + +The new root hash of the IBC store tree is then included in the `rootTree` and the new state hash is computed, ensuring the IBC store's state is consistent across the network. + [ics24]: https://github.com/cosmos/ibc/blob/main/spec/core/ics-024-host-requirements/README.md [ics20]: https://github.com/cosmos/ibc/blob/main/spec/app/ics-020-fungible-token-transfer/README.md [smt]: https://github.com/pokt-network/smt diff --git a/ibc/host.go b/ibc/host.go index c04e9b68a..442017b80 100644 --- a/ibc/host.go +++ b/ibc/host.go @@ -2,7 +2,7 @@ // host to access the persistence module and thus the tree store in order to create local // copies of the IBC state tree with the GetProvableStore() function. Bus access also will // allow for the host to send any local changes to these stores as IbcMessage types through -// the P2P module's Broadcast() function to allow them to be included in the next block. +// the P2P module's Broadcast() function to allow them to propagate through the network's mempool. package ibc import ( @@ -23,9 +23,11 @@ func (h *host) GetTimestamp() uint64 { return uint64(time.Now().Unix()) } -// GetProvableStore returns a copy of the IBC state tree where all operations happen -// locally (in memory) and are not persisted to the database. All changes are instead -// broadcasted to the network for inclusion in the next block. +// GetProvableStore returns a copy of the IBC state tree where all operations observed by +// this specific ibc light client were applied to its ephemeral (in memory) state and have not +// yet been included in the next block. The aggregation of all light client-provable stores +// propagated throughout the network are happen validated by the block proposer when reaping +// the mempool, and lead to a valid on-chain state transition when consensus is reached. // TODO(#854): Implement this func (h *host) GetProvableStore(prefix coreTypes.CommitmentPrefix) (modules.ProvableStore, error) { return nil, nil From 416682b4bbf56814ab2f17a0824a5f72f3951072 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Fri, 23 Jun 2023 16:38:02 +0100 Subject: [PATCH 47/74] Add diagrams --- ibc/docs/ics24.md | 67 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/ibc/docs/ics24.md b/ibc/docs/ics24.md index cc3226a5a..148f68469 100644 --- a/ibc/docs/ics24.md +++ b/ibc/docs/ics24.md @@ -40,9 +40,45 @@ ICS-24 has numerous sub components that must be implemented in order for the hos The IBC stores must be included in the networks consensus state as one of the many state trees. This is to ensure the IBC light clients verifying Pocket network's state can verify the inclusion or exclusion of IBC related information from the block headers. -As the IBC host will make changes to the IBC store locally, in response to functions being called by relayers, they require these changes to be propagated throughout the network (i.e. the mempool) and included in all other node's IBC stores so that during block production these changes are reflected in the state transition. This is done by utilizing the existing transaction workflow, adding the IBC store change messages to the mempool and then handling them as a new message type in block production/application logic. +The following is a simplified sequence diagram of an IBC fungible token transfer. This requires **Chain A** to commit to its state the packet data related to the transfer, so that **Chain B** can verify the inclusion of this packet data with the light client of **Chain A** it runs. + +```mermaid +sequenceDiagram + actor UA as User A + box Blue Chain A + participant A1 as Validator A + participant A2 as IBC Host A + participant A3 as Light Client B + end + box Transparent Relayer + actor R1 as Relayer + end + box Red Chain B + participant B1 as Validator B + participant B2 as IBC Host B + participant B3 as Light Client A + end + actor UB as User B + R1->>R1: Watch(Chain A) + R1->>R1: Watch(Chain B) + UA->>A1: Send 10$POKT to User B + A1->>A1: Lock(10$POKT) + A1->>A2: Create(FungibleTokenPacketData) + A2->>A1: Commit(FungibleTokenPacketData) + A1->>A1: NewBlock() + R1->>R1: CheckNewBlockForIBCPackets() + R1->>A2: QueryAndProve(FungibleTokenPacketData) + A2->>R1: FungibleTokenPacketData + A2->>R1: Proof(FungibleTokenPacketData) + R1->>B2: Validate(FungibleTokenPacketData, Proof(FungibleTokenPacketData)) + B2->>B3: Verify(Proof(FungibleTokenPacketData)) + B3->>B2: FoundInState(FungibleTokenPacketData) + B2->>B1: Send 10$POKT to User B + B1->>B1: Mint(10$POKT) + B1->>UB: Receive 10$POKT from User A +``` -TODO_IN_THIS_COMMIT: Mermaid Diagram Here +As the IBC host will make changes to the IBC store locally, in response to functions being called by relayers, they require these changes to be propagated throughout the network (i.e. the mempool) and included in all other node's IBC stores so that during block production these changes are reflected in the state transition. This is done by utilizing the existing transaction workflow, adding the IBC store change messages to the mempool and then handling them as a new message type in block production/application logic. See: [IBC State](#ibc-state) below for more details on the IBC state transition process. @@ -83,7 +119,32 @@ Upon a node receiving an `IbcMessage` from the event bus it will use the `Handle 2. Sign the `Transaction` using the `IBCModule`'s private key 3. Broadcast the `Transaction` throughout the mempool -TODO_IN_THIS_COMMIT: Mermaid Diagram Here +```mermaid +graph LR + subgraph Bus + A[Events] + end + subgraph I[IBC Host] + I1["HandleMessage(Message)"] + end + subgraph Handler + H1["ConvertIbcMessageToTransaction(IbcMessage)"] + subgraph Transaction + T1["coreTypes.Transaction{Msg: IbcMessage}"] + end + H2["SignTransaction(Transaction)"] + end + subgraph Mempool + M1["ValidateTransaction(Transaction)"] + M2["AddToMempool(Transaction)"] + end + Bus--Message-->I + I--IbcMessage-->Handler + H1--IbcMessage-->Transaction + Transaction--Transaction-->H2 + Handler--Transaction-->Mempool + M1--Transaction-->M2 +``` See: [ibc/module.go](../module.go) for the specific implementation details. From 953a146386fe03e700730d6da86cc7ee54b347ef Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:28:32 +0100 Subject: [PATCH 48/74] Update state hashes --- persistence/test/state_test.go | 6 +++--- utility/session_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/persistence/test/state_test.go b/persistence/test/state_test.go index d7a7555cd..37aeacf01 100644 --- a/persistence/test/state_test.go +++ b/persistence/test/state_test.go @@ -42,9 +42,9 @@ func TestStateHash_DeterministicStateWhenUpdatingAppStake(t *testing.T) { // logic changes, these hashes will need to be updated based on the test output. // TODO: Add an explicit updateSnapshots flag to the test to make this more clear. stateHashes := []string{ - "fb1c1b2da242eb6148884e1f11c184c673963b6fcb59feea4ea51c9840e4b56c", - "9ae40f9fd0864c01760c19d6688d1c7bba5ad746fc3f7123cf071b142c2a302a", - "5c13743e9d29e83cbff1301e3750c7583e365b2c3fd81b643ed8db985f46268f", + "00788e9a5133c31c869eb58d383f2fc30aac19b585947a8c82fe5808504ff84f", + "693450dee542f1455fdbfdeab22eb91e1888af50285330d45607b58c22f985f7", + "ad13b1646d02978f893919f413c7497b6f4d332011c40b6453499316d6705680", } stakeAmount := initialStakeAmount diff --git a/utility/session_test.go b/utility/session_test.go index ff544e508..c05e5bbdf 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -25,7 +25,7 @@ func TestSession_GetSession_SingleFishermanSingleServicerBaseCase(t *testing.T) numFishermen := 1 numServicers := 1 // needs to be manually updated if business logic changes - expectedSessionId := "7a915e89e4805095150e6826dff161ab2612e766b9e6893e6fe747e20a3abfa8" + expectedSessionId := "3de7deb1c64b32cf313838f8124b2b123ae78cff02bf5eca1564d9f78a764608" runtimeCfg, utilityMod, _ := prepareEnvironment(t, 5, numServicers, 1, numFishermen) From c39ea1d090aa06143a4c4d739130d66768358486 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:38:43 +0100 Subject: [PATCH 49/74] fixup: runtime key addition --- runtime/manager_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/runtime/manager_test.go b/runtime/manager_test.go index 8c3ce360f..35fef8094 100644 --- a/runtime/manager_test.go +++ b/runtime/manager_test.go @@ -1823,7 +1823,10 @@ func TestNewManagerFromReaders(t *testing.T) { }, Validator: &configs.ValidatorConfig{Enabled: true}, Fisherman: defaultCfg.Fisherman, - IBC: &configs.IBCConfig{Enabled: true}, + IBC: &configs.IBCConfig{ + Enabled: true, + PrivateKey: "0ca1a40ddecdab4f5b04fa0bfed1d235beaa2b8082e7554425607516f0862075dfe357de55649e6d2ce889acf15eb77e94ab3c5756fe46d3c7538d37f27f115e", + }, }, genesisState: expectedGenesis, clock: clock.New(), From 89550be82c8bb7ec96d5ae8ff613078aca097d7b Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Mon, 26 Jun 2023 12:00:16 +0100 Subject: [PATCH 50/74] fixup: remove prefixes from IbcMessages --- ibc/messages.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/ibc/messages.go b/ibc/messages.go index 7a97aae4d..c50912926 100644 --- a/ibc/messages.go +++ b/ibc/messages.go @@ -10,24 +10,22 @@ import ( "google.golang.org/protobuf/types/known/anypb" ) -func CreateUpdateStoreMessage(prefix coreTypes.CommitmentPrefix, key, value []byte) *types.IbcMessage { +func CreateUpdateStoreMessage(key, value []byte) *types.IbcMessage { return &types.IbcMessage{ Event: &types.IbcMessage_Update{ Update: &types.UpdateIbcStore{ - Prefix: prefix, - Key: key, - Value: value, + Key: key, + Value: value, }, }, } } -func CreatePruneStoreMessage(prefix coreTypes.CommitmentPrefix, key []byte) *types.IbcMessage { +func CreatePruneStoreMessage(key []byte) *types.IbcMessage { return &types.IbcMessage{ Event: &types.IbcMessage_Prune{ Prune: &types.PruneIbcStore{ - Prefix: prefix, - Key: key, + Key: key, }, }, } From 47668029a02fd6aa79d086944314c54222e9938b Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Mon, 26 Jun 2023 14:26:29 +0100 Subject: [PATCH 51/74] Clear IBC table state between tests --- persistence/debug.go | 1 + persistence/test/benchmark_state_test.go | 2 +- persistence/types/ibc.go | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/persistence/debug.go b/persistence/debug.go index 81b075365..90baf0da8 100644 --- a/persistence/debug.go +++ b/persistence/debug.go @@ -14,6 +14,7 @@ var nonActorClearFunctions = []func() string{ types.ClearAllGovParamsQuery, types.ClearAllGovFlagsQuery, types.ClearAllBlocksQuery, + types.ClearAllIbcQuery, } func (m *persistenceModule) HandleDebugMessage(debugMessage *messaging.DebugMessage) error { diff --git a/persistence/test/benchmark_state_test.go b/persistence/test/benchmark_state_test.go index 9084faf8a..2e40e2fbb 100644 --- a/persistence/test/benchmark_state_test.go +++ b/persistence/test/benchmark_state_test.go @@ -118,7 +118,7 @@ MethodLoop: case reflect.Slice: switch arg.Elem().Kind() { case reflect.Uint8: - v = reflect.ValueOf([]uint8{0}) + v = reflect.ValueOf([]uint8{uint8(rand.Intn(2 ^ 8 - 1))}) case reflect.String: v = reflect.ValueOf([]string{"abc"}) default: diff --git a/persistence/types/ibc.go b/persistence/types/ibc.go index c94b542d6..4d68688bd 100644 --- a/persistence/types/ibc.go +++ b/persistence/types/ibc.go @@ -24,3 +24,7 @@ func InsertIbcStoreEntryQuery(height int64, key, value []byte) string { hex.EncodeToString(value), ) } + +func ClearAllIbcQuery() string { + return fmt.Sprintf(`DELETE FROM %s`, IbcStoreTableName) +} From ed94086daa4c608409561b2bc64b520a561c9853 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Mon, 26 Jun 2023 14:54:10 +0100 Subject: [PATCH 52/74] Improve unit test cases --- ibc/handle_message_test.go | 145 +++++++++++++++++++++++++++++++------ 1 file changed, 122 insertions(+), 23 deletions(-) diff --git a/ibc/handle_message_test.go b/ibc/handle_message_test.go index 11aca854d..cc2f6a563 100644 --- a/ibc/handle_message_test.go +++ b/ibc/handle_message_test.go @@ -1,6 +1,8 @@ package ibc import ( + "errors" + "fmt" "testing" ibcTypes "github.com/pokt-network/pocket/ibc/types" @@ -42,16 +44,21 @@ func TestHandleMessage_ErrorAlreadyCommitted(t *testing.T) { } func TestHandleMessage_BasicValidation_Message(t *testing.T) { - updateMsg, _ := prepareUpdateMessage(t, []byte("key"), []byte("value")) - require.NoError(t, updateMsg.ValidateBasic()) - pruneMsg, _ := preparePruneMessage(t, []byte("key")) - require.NoError(t, pruneMsg.ValidateBasic()) - testCases := []struct { name string msg *ibcTypes.IbcMessage expected error }{ + { + name: "Valid Update Message", + msg: CreateUpdateStoreMessage([]byte("key"), []byte("value")), + expected: nil, + }, + { + name: "Valid Prune Message", + msg: CreatePruneStoreMessage([]byte("key")), + expected: nil, + }, { name: "Invalid Update Message: Empty Key", msg: CreateUpdateStoreMessage(nil, []byte("value")), @@ -72,7 +79,11 @@ func TestHandleMessage_BasicValidation_Message(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { err := tc.msg.ValidateBasic() - require.EqualError(t, err, tc.expected.Error()) + if tc.expected != nil { + require.EqualError(t, err, tc.expected.Error()) + } else { + require.NoError(t, err) + } }) } } @@ -83,48 +94,136 @@ func TestHandleMessage_BasicValidation_Transaction(t *testing.T) { privKey, err := crypto.GeneratePrivateKey() require.NoError(t, err) + falseKey, err := crypto.GeneratePrivateKey() + require.NoError(t, err) - _, validTx := prepareUpdateMessage(t, []byte("key"), []byte("value")) + validUpdateMsg, validUpdateTx := prepareUpdateMessage(t, []byte("key"), []byte("value")) require.NoError(t, err) - err = validTx.Sign(privKey) + err = validUpdateTx.Sign(privKey) require.NoError(t, err) - - txProtoBytes, err := codec.GetCodec().Marshal(validTx) + updateAny, err := codec.GetCodec().ToAny(validUpdateMsg.GetUpdate()) + require.NoError(t, err) + bz, err := validUpdateTx.SignableBytes() + require.NoError(t, err) + falseUpdateSig, err := falseKey.Sign(bz) require.NoError(t, err) - err = utilityMod.HandleTransaction(txProtoBytes) + validPruneMsg, validPruneTx := preparePruneMessage(t, []byte("key")) + require.NoError(t, err) + err = validPruneTx.Sign(privKey) + require.NoError(t, err) + pruneAny, err := codec.GetCodec().ToAny(validPruneMsg.GetPrune()) + require.NoError(t, err) + bz, err = validPruneTx.SignableBytes() + require.NoError(t, err) + falsePruneSig, err := falseKey.Sign(bz) require.NoError(t, err) testCases := []struct { name string - msg *ibcTypes.IbcMessage + tx *coreTypes.Transaction expected error }{ { - name: "Invalid Update Message: Empty Key", - msg: CreateUpdateStoreMessage(nil, []byte("value")), - expected: coreTypes.ErrEmptySignatureStructure(), + name: "Valid Update Transaction", + tx: validUpdateTx, + expected: nil, }, { - name: "Invalid Update Message: Empty Value", - msg: CreateUpdateStoreMessage([]byte("key"), nil), + name: "Valid Prune Transaction", + tx: validPruneTx, + expected: nil, + }, + { + name: "Invalid Update Transaction: Empty Nonce", + tx: &coreTypes.Transaction{ + Msg: updateAny, + }, + expected: coreTypes.ErrEmptyNonce(), + }, + { + name: "Invalid Prune Transaction: Empty Nonce", + tx: &coreTypes.Transaction{ + Msg: pruneAny, + }, + expected: coreTypes.ErrEmptyNonce(), + }, + { + name: "Invalid Update Transaction: Empty Signature", + tx: &coreTypes.Transaction{ + Msg: updateAny, + Nonce: fmt.Sprintf("%d", crypto.GetNonce()), + }, expected: coreTypes.ErrEmptySignatureStructure(), }, { - name: "Invalid Prune Message: Empty Key", - msg: CreatePruneStoreMessage(nil), + name: "Invalid Prune Transaction: Empty Signature", + tx: &coreTypes.Transaction{ + Msg: pruneAny, + Nonce: fmt.Sprintf("%d", crypto.GetNonce()), + }, expected: coreTypes.ErrEmptySignatureStructure(), }, + { + name: "Invalid Update Transaction: Bad Key", + tx: &coreTypes.Transaction{ + Msg: updateAny, + Nonce: fmt.Sprintf("%d", crypto.GetNonce()), + Signature: &coreTypes.Signature{ + PublicKey: []byte("bad key"), + Signature: falsePruneSig, + }, + }, + expected: coreTypes.ErrNewPublicKeyFromBytes(errors.New("the public key length is not valid, expected length 32, actual length: 7")), + }, + { + name: "Invalid Prune Transaction: Bad Signature", + tx: &coreTypes.Transaction{ + Msg: pruneAny, + Nonce: fmt.Sprintf("%d", crypto.GetNonce()), + Signature: &coreTypes.Signature{ + PublicKey: []byte("bad key"), + Signature: falsePruneSig, + }, + }, + expected: coreTypes.ErrNewPublicKeyFromBytes(errors.New("the public key length is not valid, expected length 32, actual length: 7")), + }, + { + name: "Invalid Update Transaction: Bad Signature", + tx: &coreTypes.Transaction{ + Msg: updateAny, + Nonce: fmt.Sprintf("%d", crypto.GetNonce()), + Signature: &coreTypes.Signature{ + PublicKey: privKey.PublicKey().Bytes(), + Signature: falseUpdateSig, + }, + }, + expected: coreTypes.ErrSignatureVerificationFailed(), + }, + { + name: "Invalid Prune Transaction: Bad Key", + tx: &coreTypes.Transaction{ + Msg: pruneAny, + Nonce: fmt.Sprintf("%d", crypto.GetNonce()), + Signature: &coreTypes.Signature{ + PublicKey: privKey.PublicKey().Bytes(), + Signature: falsePruneSig, + }, + }, + expected: coreTypes.ErrSignatureVerificationFailed(), + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - tx, err := ConvertIBCMessageToTx(tc.msg) - require.NoError(t, err) - txProtoBytes, err := codec.GetCodec().Marshal(tx) + txProtoBytes, err := codec.GetCodec().Marshal(tc.tx) require.NoError(t, err) err = utilityMod.HandleTransaction(txProtoBytes) - require.EqualError(t, err, tc.expected.Error()) + if tc.expected != nil { + require.EqualError(t, err, tc.expected.Error()) + } else { + require.NoError(t, err) + } }) } } From 32964cb3bcc0228f3e8740bc5f0ac85b02409fc4 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Mon, 26 Jun 2023 16:41:30 +0100 Subject: [PATCH 53/74] Add no valuehashing to state trees --- persistence/trees/module.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/persistence/trees/module.go b/persistence/trees/module.go index 9c0d5eff0..cb21c9913 100644 --- a/persistence/trees/module.go +++ b/persistence/trees/module.go @@ -70,7 +70,7 @@ func (t *treeStore) setupTrees() error { } t.merkleTrees[stateTreeNames[i]] = &stateTree{ name: stateTreeNames[i], - tree: smt.NewSparseMerkleTree(nodeStore, smtTreeHasher), + tree: smt.NewSparseMerkleTree(nodeStore, smtTreeHasher, smtValueHasher), nodeStore: nodeStore, } } @@ -90,7 +90,7 @@ func (t *treeStore) setupInMemory() error { nodeStore := kvstore.NewMemKVStore() // For testing, `smt.NewSimpleMap()` can be used as well t.merkleTrees[stateTreeNames[i]] = &stateTree{ name: stateTreeNames[i], - tree: smt.NewSparseMerkleTree(nodeStore, smtTreeHasher), + tree: smt.NewSparseMerkleTree(nodeStore, smtTreeHasher, smtValueHasher), nodeStore: nodeStore, } } From 1d84d08809e1729809c31d20f77e1b801bc153d1 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Mon, 26 Jun 2023 16:41:59 +0100 Subject: [PATCH 54/74] Add ibc.feature text file to track upcoming tests to be added --- ibc/ibc.feature | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 ibc/ibc.feature diff --git a/ibc/ibc.feature b/ibc/ibc.feature new file mode 100644 index 000000000..e8c297bb2 --- /dev/null +++ b/ibc/ibc.feature @@ -0,0 +1,12 @@ +Tests to add: + - Add integration tests to cover the following: + - Local IBC store change is propagated through the network + - Check via P2P magic + - Local IBC store change is included in PostgresDB + - Check via SQL query + - Add E2E tests to cover the following: + - Local IBC store change gets included in the next block + - Check via inclusion proof + - Local IBC store change that is not included in next block stays in mempool + - Check by querying mempool + - Check via exclusion proof From f7e7c0ce16e394c7fb7c6b09e1879e5e513a112a Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Mon, 26 Jun 2023 16:53:48 +0100 Subject: [PATCH 55/74] Update docs --- ibc/docs/ics24.md | 23 +++++++---------------- persistence/docs/PROTOCOL_STATE_HASH.md | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/ibc/docs/ics24.md b/ibc/docs/ics24.md index 148f68469..82291862b 100644 --- a/ibc/docs/ics24.md +++ b/ibc/docs/ics24.md @@ -1,7 +1,9 @@ # ICS-24 Host Requirements - [Overview](#overview) +- [Host Configuration](#host-configuration) - [Implementation](#implementation) + - [Persistence](#persistence) - [Paths and Identifiers](#paths-and-identifiers) - [Timestamps](#timestamps) - [IBC State](#ibc-state) @@ -45,7 +47,7 @@ The following is a simplified sequence diagram of an IBC fungible token transfer ```mermaid sequenceDiagram actor UA as User A - box Blue Chain A + box Transparent Chain A participant A1 as Validator A participant A2 as IBC Host A participant A3 as Light Client B @@ -53,7 +55,7 @@ sequenceDiagram box Transparent Relayer actor R1 as Relayer end - box Red Chain B + box Transparent Chain B participant B1 as Validator B participant B2 as IBC Host B participant B3 as Light Client A @@ -152,23 +154,12 @@ See: [ibc/module.go](../module.go) for the specific implementation details. With the `IbcMessage` now propagated through the network's mempool, when it is reaped (by the block proposer) the message's validity will be handled by first determining the type of the `IbcMessage`: -- `UpdateIbcStore`: The `key` and `value` fields of the `IbcMessage` are inserted into the `ibc_messages` Postgres table along with the current height -- `PruneIbcStore`: The `key` with a `nil` value is passed into the `ibc_messages` Postgres table along with the current height - -_Note: Prior to insertion the `key` and `value` fields of the messages are hexadecimally encoded into strings._ +- `UpdateIbcStore`: The `key` and `value` fields are tracked by persistence and used to update the `ibc` store state tree +- `PruneIbcStore`: The `key` field is tracked by persistence and marked for removal in the `ibc` store state tree ### State Transition -When the new state hash is computed, the different state trees read the updates from their respective Postgres tables and update the trees accordingly. For each entry in the `ibc_message` table depending on the entries `value` field the tree will perform one of two operations: - -- `value == nil` - - This is a `PruneIbcStore` message and thus the tree will delete the entry with the given `key` - - `ibcTree.Delete(key)` -- `value != nil` - - This is an `UpdateIbcStore` message and thus the tree will update the entry with the given `key` to have the given `value` - - `ibcTree.Update(key, value)` - -The new root hash of the IBC store tree is then included in the `rootTree` and the new state hash is computed, ensuring the IBC store's state is consistent across the network. +See: [PROTOCOL_STATE_HASH.md](../../persistence/docs/PROTOCOL_STATE_HASH.md#ibc-state-tree) for more information on how the persistence module uses the data it has tracked from the `IbcMessage` objects, in order to update the actual state trees and in turn the root hash. [ics24]: https://github.com/cosmos/ibc/blob/main/spec/core/ics-024-host-requirements/README.md [ics20]: https://github.com/cosmos/ibc/blob/main/spec/app/ics-020-fungible-token-transfer/README.md diff --git a/persistence/docs/PROTOCOL_STATE_HASH.md b/persistence/docs/PROTOCOL_STATE_HASH.md index 75ffd4886..1d2e28c8e 100644 --- a/persistence/docs/PROTOCOL_STATE_HASH.md +++ b/persistence/docs/PROTOCOL_STATE_HASH.md @@ -10,6 +10,7 @@ Alternative implementation of the persistence module are free to choose their ow - [Block Proto](#block-proto) - [Trees](#trees) - [Compute State Hash](#compute-state-hash) + - [IBC State Tree](#ibc-state-tree) - [Store Block (Commit)](#store-block-commit) - [Failed Commitments](#failed-commitments) @@ -96,6 +97,23 @@ sequenceDiagram deactivate P ``` +### IBC State Tree + +When the new state hash is computed, the different state trees read the updates from their respective Postgres tables and update the trees accordingly. + +`IbcMessage` objects are inserted into the `ibc_message` table in two ways., depending on the IBC messages' type: 1. `UpdateIbcStore`: the `key` and `value` fields are inserted with the height into the table 2. `PruneIbcStore`: the `key` with a `nil` value is inserted into the table + +For each entry in the `ibc_message` table depending on the entries `value` field the tree will perform one of two operations: + +- `value == nil` + - This is a `PruneIbcStore` message and thus the tree will delete the entry with the given `key` + - `ibcTree.Delete(key)` +- `value != nil` + - This is an `UpdateIbcStore` message and thus the tree will update the entry with the given `key` to have the given `value` + - `ibcTree.Update(key, value)` + +_Note: Prior to insertion the `key` and `value` fields of the messages are hexadecimally encoded into strings._ + ## Store Block (Commit) When the `Commit(proposer, quorumCert)` function is invoked, the current context is committed to disk. The `PersistenceContext` does the following: From be48d99da1db268bdf2283cdf4470b003c1084a3 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:48:46 +0100 Subject: [PATCH 56/74] Fix proto naming --- ibc/handle_message_test.go | 8 ++--- ibc/messages.go | 22 +++++++------- ibc/module.go | 4 +-- ibc/types/messages.go | 34 +++++++++++----------- ibc/types/proto/messages.proto | 16 +++++----- utility/unit_of_work/gov.go | 2 +- utility/unit_of_work/tx_message_handler.go | 12 ++++---- utility/unit_of_work/tx_message_signers.go | 12 ++++---- 8 files changed, 55 insertions(+), 55 deletions(-) diff --git a/ibc/handle_message_test.go b/ibc/handle_message_test.go index cc2f6a563..b547d2a87 100644 --- a/ibc/handle_message_test.go +++ b/ibc/handle_message_test.go @@ -46,7 +46,7 @@ func TestHandleMessage_ErrorAlreadyCommitted(t *testing.T) { func TestHandleMessage_BasicValidation_Message(t *testing.T) { testCases := []struct { name string - msg *ibcTypes.IbcMessage + msg *ibcTypes.IBCMessage expected error }{ { @@ -272,7 +272,7 @@ func TestHandleMessage_AddToMempool(t *testing.T) { txProto := &coreTypes.Transaction{} err = codec.GetCodec().Unmarshal(mTx, txProto) require.NoError(t, err) - ibcMsg := &ibcTypes.UpdateIbcStore{} + ibcMsg := &ibcTypes.UpdateIBCStore{} require.NoError(t, txProto.GetMsg().UnmarshalTo(ibcMsg)) // Compare messages are equal @@ -283,7 +283,7 @@ func TestHandleMessage_AddToMempool(t *testing.T) { require.Equal(t, origBz, ibcMsgBz) } -func prepareUpdateMessage(t *testing.T, key, value []byte) (*ibcTypes.IbcMessage, *coreTypes.Transaction) { +func prepareUpdateMessage(t *testing.T, key, value []byte) (*ibcTypes.IBCMessage, *coreTypes.Transaction) { t.Helper() msg := CreateUpdateStoreMessage(key, value) tx, err := ConvertIBCMessageToTx(msg) @@ -291,7 +291,7 @@ func prepareUpdateMessage(t *testing.T, key, value []byte) (*ibcTypes.IbcMessage return msg, tx } -func preparePruneMessage(t *testing.T, key []byte) (*ibcTypes.IbcMessage, *coreTypes.Transaction) { +func preparePruneMessage(t *testing.T, key []byte) (*ibcTypes.IBCMessage, *coreTypes.Transaction) { t.Helper() msg := CreatePruneStoreMessage(key) tx, err := ConvertIBCMessageToTx(msg) diff --git a/ibc/messages.go b/ibc/messages.go index c50912926..24bbe0a67 100644 --- a/ibc/messages.go +++ b/ibc/messages.go @@ -10,10 +10,10 @@ import ( "google.golang.org/protobuf/types/known/anypb" ) -func CreateUpdateStoreMessage(key, value []byte) *types.IbcMessage { - return &types.IbcMessage{ - Event: &types.IbcMessage_Update{ - Update: &types.UpdateIbcStore{ +func CreateUpdateStoreMessage(key, value []byte) *types.IBCMessage { + return &types.IBCMessage{ + Event: &types.IBCMessage_Update{ + Update: &types.UpdateIBCStore{ Key: key, Value: value, }, @@ -21,23 +21,23 @@ func CreateUpdateStoreMessage(key, value []byte) *types.IbcMessage { } } -func CreatePruneStoreMessage(key []byte) *types.IbcMessage { - return &types.IbcMessage{ - Event: &types.IbcMessage_Prune{ - Prune: &types.PruneIbcStore{ +func CreatePruneStoreMessage(key []byte) *types.IBCMessage { + return &types.IBCMessage{ + Event: &types.IBCMessage_Prune{ + Prune: &types.PruneIBCStore{ Key: key, }, }, } } -func ConvertIBCMessageToTx(ibcMessage *types.IbcMessage) (*coreTypes.Transaction, error) { +func ConvertIBCMessageToTx(ibcMessage *types.IBCMessage) (*coreTypes.Transaction, error) { var anyMsg *anypb.Any var err error switch event := ibcMessage.Event.(type) { - case *types.IbcMessage_Update: + case *types.IBCMessage_Update: anyMsg, err = codec.GetCodec().ToAny(event.Update) - case *types.IbcMessage_Prune: + case *types.IBCMessage_Prune: anyMsg, err = codec.GetCodec().ToAny(event.Prune) default: return nil, coreTypes.ErrUnknownIBCMessageType(fmt.Sprintf("%T", event)) diff --git a/ibc/module.go b/ibc/module.go index 9a10cd163..cf7e4c896 100644 --- a/ibc/module.go +++ b/ibc/module.go @@ -95,7 +95,7 @@ func (m *ibcModule) HandleMessage(message *anypb.Any) error { if err != nil { return err } - ibcMessage, ok := msg.(*ibcTypes.IbcMessage) + ibcMessage, ok := msg.(*ibcTypes.IBCMessage) if !ok { return fmt.Errorf("failed to cast message to IBCMessage") } @@ -139,7 +139,7 @@ func (m *ibcModule) HandleMessage(message *anypb.Any) error { if err := m.GetBus().GetUtilityModule().HandleTransaction(txBz); err != nil { return err } - m.logger.Info().Str("message_type", "IbcMessage").Msg("Successfully added a new message to the mempool!") + m.logger.Info().Str("message_type", "IBCMessage").Msg("Successfully added a new message to the mempool!") return nil } diff --git a/ibc/types/messages.go b/ibc/types/messages.go index e9163b2a1..917da4554 100644 --- a/ibc/types/messages.go +++ b/ibc/types/messages.go @@ -11,22 +11,22 @@ import ( // Implement the Message interface var ( - _ utilityTypes.Message = &UpdateIbcStore{} - _ utilityTypes.Message = &PruneIbcStore{} + _ utilityTypes.Message = &UpdateIBCStore{} + _ utilityTypes.Message = &PruneIBCStore{} ) -func (m *IbcMessage) ValidateBasic() coreTypes.Error { +func (m *IBCMessage) ValidateBasic() coreTypes.Error { switch msg := m.Event.(type) { - case *IbcMessage_Update: + case *IBCMessage_Update: return msg.Update.ValidateBasic() - case *IbcMessage_Prune: + case *IBCMessage_Prune: return msg.Prune.ValidateBasic() default: return coreTypes.ErrUnknownIBCMessageType(fmt.Sprintf("%T", msg)) } } -func (m *UpdateIbcStore) ValidateBasic() coreTypes.Error { +func (m *UpdateIBCStore) ValidateBasic() coreTypes.Error { if m.Key == nil { return coreTypes.ErrNilField("key") } @@ -36,36 +36,36 @@ func (m *UpdateIbcStore) ValidateBasic() coreTypes.Error { return nil } -func (m *PruneIbcStore) ValidateBasic() coreTypes.Error { +func (m *PruneIBCStore) ValidateBasic() coreTypes.Error { if m.Key == nil { return coreTypes.ErrNilField("key") } return nil } -func (m *UpdateIbcStore) SetSigner(signer []byte) { m.Signer = signer } -func (m *PruneIbcStore) SetSigner(signer []byte) { m.Signer = signer } +func (m *UpdateIBCStore) SetSigner(signer []byte) { m.Signer = signer } +func (m *PruneIBCStore) SetSigner(signer []byte) { m.Signer = signer } -func (m *UpdateIbcStore) GetMessageName() string { +func (m *UpdateIBCStore) GetMessageName() string { return string(m.ProtoReflect().Descriptor().Name()) } -func (m *PruneIbcStore) GetMessageName() string { +func (m *PruneIBCStore) GetMessageName() string { return string(m.ProtoReflect().Descriptor().Name()) } -func (m *UpdateIbcStore) GetMessageRecipient() string { return "" } -func (m *PruneIbcStore) GetMessageRecipient() string { return "" } +func (m *UpdateIBCStore) GetMessageRecipient() string { return "" } +func (m *PruneIBCStore) GetMessageRecipient() string { return "" } -func (m *UpdateIbcStore) GetActorType() coreTypes.ActorType { +func (m *UpdateIBCStore) GetActorType() coreTypes.ActorType { return coreTypes.ActorType_ACTOR_TYPE_VAL } -func (m *PruneIbcStore) GetActorType() coreTypes.ActorType { +func (m *PruneIBCStore) GetActorType() coreTypes.ActorType { return coreTypes.ActorType_ACTOR_TYPE_VAL } -func (m *UpdateIbcStore) GetCanonicalBytes() []byte { +func (m *UpdateIBCStore) GetCanonicalBytes() []byte { bz, err := codec.GetCodec().Marshal(m) if err != nil { log.Fatalf("must marshal %v", err) @@ -73,7 +73,7 @@ func (m *UpdateIbcStore) GetCanonicalBytes() []byte { return bz // DISCUSS(#142): should we also sort the JSON like in V0? } -func (m *PruneIbcStore) GetCanonicalBytes() []byte { +func (m *PruneIBCStore) GetCanonicalBytes() []byte { bz, err := codec.GetCodec().Marshal(m) if err != nil { log.Fatalf("must marshal %v", err) diff --git a/ibc/types/proto/messages.proto b/ibc/types/proto/messages.proto index 39b6c839c..6cc5e9efc 100644 --- a/ibc/types/proto/messages.proto +++ b/ibc/types/proto/messages.proto @@ -4,25 +4,25 @@ package types; option go_package = "github.com/pokt-network/pocket/ibc/types"; -// update_ibc_store defines a message representing the addition of a key/value pair to the IBC store +// UpdateIBCStore defines a message representing the addition of a key/value pair to the IBC store // the key field should be the full key - prefixed with the stores CommitmentPrefix -message update_ibc_store { +message UpdateIBCStore { bytes signer = 1; bytes key = 2; bytes value = 3; } -// prune_ibc_store defines a message representing the removal of a key from the IBC store +// PruneIBCStore defines a message representing the removal of a key from the IBC store // the key field should be the full key - prefixed with the stores CommitmentPrefix -message prune_ibc_store { +message PruneIBCStore { bytes signer = 1; bytes key = 2; } -// ibc_message defines the different types of IBC message that can be sent across the network -message ibc_message { +// IBCMessage defines the different types of IBC message that can be sent across the network +message IBCMessage { oneof event { - update_ibc_store update = 1; - prune_ibc_store prune = 2; + UpdateIBCStore update = 1; + PruneIBCStore prune = 2; } } diff --git a/utility/unit_of_work/gov.go b/utility/unit_of_work/gov.go index 19461e07b..0c71c9418 100644 --- a/utility/unit_of_work/gov.go +++ b/utility/unit_of_work/gov.go @@ -151,7 +151,7 @@ func (u *baseUtilityUnitOfWork) getParamOwner(paramName string) ([]byte, coreTyp func (u *baseUtilityUnitOfWork) getFee(msg typesUtil.Message, actorType coreTypes.ActorType) (amount *big.Int, err coreTypes.Error) { switch x := msg.(type) { // TECHDEBT(M6): Decide on IBC store change fees and move into a governance parameter - case *typesUtil.MessageSend, *ibcTypes.UpdateIbcStore, *ibcTypes.PruneIbcStore: + case *typesUtil.MessageSend, *ibcTypes.UpdateIBCStore, *ibcTypes.PruneIBCStore: return getGovParam[*big.Int](u, typesUtil.MessageSendFee) case *typesUtil.MessageStake: switch actorType { diff --git a/utility/unit_of_work/tx_message_handler.go b/utility/unit_of_work/tx_message_handler.go index 31e6fe46d..654f6d25f 100644 --- a/utility/unit_of_work/tx_message_handler.go +++ b/utility/unit_of_work/tx_message_handler.go @@ -27,10 +27,10 @@ func (u *baseUtilityUnitOfWork) handleMessage(msg typesUtil.Message) (err coreTy return u.handleUnpauseMessage(x) case *typesUtil.MessageChangeParameter: return u.handleMessageChangeParameter(x) - case *ibcTypes.UpdateIbcStore: - return u.handleUpdateIbcStore(x) - case *ibcTypes.PruneIbcStore: - return u.handlePruneIbcStore(x) + case *ibcTypes.UpdateIBCStore: + return u.handleUpdateIBCStore(x) + case *ibcTypes.PruneIBCStore: + return u.handlePruneIBCStore(x) default: return coreTypes.ErrUnknownMessage(x) } @@ -225,14 +225,14 @@ func (u *baseUtilityUnitOfWork) handleMessageChangeParameter(message *typesUtil. return u.updateParam(message.ParameterKey, v) } -func (u *baseUtilityUnitOfWork) handleUpdateIbcStore(message *ibcTypes.UpdateIbcStore) coreTypes.Error { +func (u *baseUtilityUnitOfWork) handleUpdateIBCStore(message *ibcTypes.UpdateIBCStore) coreTypes.Error { if err := u.persistenceRWContext.SetIBCStoreEntry(message.Key, message.Value); err != nil { return coreTypes.ErrUpdatingIBCStoreDB(err) } return nil } -func (u *baseUtilityUnitOfWork) handlePruneIbcStore(message *ibcTypes.PruneIbcStore) coreTypes.Error { +func (u *baseUtilityUnitOfWork) handlePruneIBCStore(message *ibcTypes.PruneIBCStore) coreTypes.Error { if err := u.persistenceRWContext.SetIBCStoreEntry(message.Key, nil); err != nil { return coreTypes.ErrUpdatingIBCStoreDB(err) } diff --git a/utility/unit_of_work/tx_message_signers.go b/utility/unit_of_work/tx_message_signers.go index 8e84bc133..602ef602a 100644 --- a/utility/unit_of_work/tx_message_signers.go +++ b/utility/unit_of_work/tx_message_signers.go @@ -25,10 +25,10 @@ func (u *baseUtilityUnitOfWork) getSignerCandidates(msg typesUtil.Message) ([][] return u.getMessageUnpauseSignerCandidates(x) case *typesUtil.MessageChangeParameter: return u.getMessageChangeParameterSignerCandidates(x) - case *ibcTypes.UpdateIbcStore: - return u.getUpdateIbcStoreSingerCandidates(x) - case *ibcTypes.PruneIbcStore: - return u.getPruneIbcStoreSingerCandidates(x) + case *ibcTypes.UpdateIBCStore: + return u.getUpdateIBCStoreSingerCandidates(x) + case *ibcTypes.PruneIBCStore: + return u.getPruneIBCStoreSingerCandidates(x) default: return nil, coreTypes.ErrUnknownMessage(x) } @@ -78,10 +78,10 @@ func (u *baseUtilityUnitOfWork) getMessageSendSignerCandidates(msg *typesUtil.Me return [][]byte{msg.FromAddress}, nil } -func (u *baseUtilityUnitOfWork) getUpdateIbcStoreSingerCandidates(msg *ibcTypes.UpdateIbcStore) ([][]byte, coreTypes.Error) { +func (u *baseUtilityUnitOfWork) getUpdateIBCStoreSingerCandidates(msg *ibcTypes.UpdateIBCStore) ([][]byte, coreTypes.Error) { return [][]byte{msg.Signer}, nil } -func (u *baseUtilityUnitOfWork) getPruneIbcStoreSingerCandidates(msg *ibcTypes.PruneIbcStore) ([][]byte, coreTypes.Error) { +func (u *baseUtilityUnitOfWork) getPruneIBCStoreSingerCandidates(msg *ibcTypes.PruneIBCStore) ([][]byte, coreTypes.Error) { return [][]byte{msg.Signer}, nil } From e54be28d43a3c105c1bc56073a469b41cca7cc0a Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:52:24 +0100 Subject: [PATCH 57/74] Add signer comments --- ibc/types/proto/messages.proto | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ibc/types/proto/messages.proto b/ibc/types/proto/messages.proto index 6cc5e9efc..8c9ee95b1 100644 --- a/ibc/types/proto/messages.proto +++ b/ibc/types/proto/messages.proto @@ -5,17 +5,17 @@ package types; option go_package = "github.com/pokt-network/pocket/ibc/types"; // UpdateIBCStore defines a message representing the addition of a key/value pair to the IBC store -// the key field should be the full key - prefixed with the stores CommitmentPrefix +// the key field should be the full key - prefixed with the store's CommitmentPrefix message UpdateIBCStore { - bytes signer = 1; + bytes signer = 1; // signer should be the address of the node that sent the message bytes key = 2; bytes value = 3; } // PruneIBCStore defines a message representing the removal of a key from the IBC store -// the key field should be the full key - prefixed with the stores CommitmentPrefix +// the key field should be the full key - prefixed with the store's CommitmentPrefix message PruneIBCStore { - bytes signer = 1; + bytes signer = 1; // signer should be the address of the node that sent the message bytes key = 2; } From 9ccc19702f3a59e8c2e15fdc6d17c0735e2afaf4 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:52:35 +0100 Subject: [PATCH 58/74] Prefix errors with IBC --- shared/core/types/error.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/shared/core/types/error.go b/shared/core/types/error.go index c317ce50a..b8c7595ed 100644 --- a/shared/core/types/error.go +++ b/shared/core/types/error.go @@ -320,14 +320,14 @@ const ( NegativeAmountError = "the amount is negative" UnknownActorTypeError = "the actor type is not recognized" UnknownMessageTypeError = "the message being by the utility message is not recognized" - HostAlreadyExistsError = "an ibc host already exists" - HostDoesNotExistError = "an ibc host does not exist" + IBCHostAlreadyExistsError = "an ibc host already exists" + IBCHostDoesNotExistError = "an ibc host does not exist" IBCInvalidIDError = "invalid ibc identifier" IBCInvalidPathError = "invalid ibc path" - CreatingProofError = "an error occurred creating the CommitmentProof" - UnknownIBCMessageTypeError = "the ibc message type is not recognized" + IBCCreatingProofError = "an error occurred creating the CommitmentProof" + IBCUnknownIBCMessageTypeError = "the ibc message type is not recognized" NilFieldError = "field cannot be nil" - UpdatingIBCStoreDBError = "an error occurred updating the ibc store postgres database" + IBCUpdatingIBCStoreDBError = "an error occurred updating the ibc store postgres database" ) func ErrUnknownParam(paramName string) Error { @@ -864,11 +864,11 @@ func ErrUnknownMessageType(messageType any) Error { } func ErrHostAlreadyExists() Error { - return NewError(CodeHostAlreadyExists, HostAlreadyExistsError) + return NewError(CodeHostAlreadyExists, IBCHostAlreadyExistsError) } func ErrHostDoesNotExist() Error { - return NewError(CodeHostDoesNotExist, HostDoesNotExistError) + return NewError(CodeHostDoesNotExist, IBCHostDoesNotExistError) } func ErrIBCInvalidID(identifier, msg string) Error { @@ -880,11 +880,11 @@ func ErrIBCInvalidPath(path string) Error { } func ErrCreatingProof(err error) Error { - return NewError(CodeCreatingProofError, fmt.Sprintf("%s: %s", CreatingProofError, err.Error())) + return NewError(CodeCreatingProofError, fmt.Sprintf("%s: %s", IBCCreatingProofError, err.Error())) } func ErrUnknownIBCMessageType(messageType string) Error { - return NewError(CodeUnknownIBCMessageTypeError, fmt.Sprintf("%s: %s", UnknownIBCMessageTypeError, messageType)) + return NewError(CodeUnknownIBCMessageTypeError, fmt.Sprintf("%s: %s", IBCUnknownIBCMessageTypeError, messageType)) } func ErrNilField(field string) Error { @@ -892,5 +892,5 @@ func ErrNilField(field string) Error { } func ErrUpdatingIBCStoreDB(err error) Error { - return NewError(CodeUpdatingIBCStoreDBError, fmt.Sprintf("%s: %s", UpdatingIBCStoreDBError, err.Error())) + return NewError(CodeUpdatingIBCStoreDBError, fmt.Sprintf("%s: %s", IBCUpdatingIBCStoreDBError, err.Error())) } From fbadb8d82614164b98279632ab4a07d5b4c8c73e Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:52:43 +0100 Subject: [PATCH 59/74] Update docs --- ibc/docs/ics24.md | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/ibc/docs/ics24.md b/ibc/docs/ics24.md index 82291862b..e32fdc65d 100644 --- a/ibc/docs/ics24.md +++ b/ibc/docs/ics24.md @@ -63,19 +63,19 @@ sequenceDiagram actor UB as User B R1->>R1: Watch(Chain A) R1->>R1: Watch(Chain B) - UA->>A1: Send 10$POKT to User B + UA->>+A1: Send 10$POKT to User B A1->>A1: Lock(10$POKT) - A1->>A2: Create(FungibleTokenPacketData) - A2->>A1: Commit(FungibleTokenPacketData) - A1->>A1: NewBlock() + A1->>+A2: Create(FungibleTokenPacketData) + A2->>-A1: Commit(FungibleTokenPacketData) + A1->>-A1: NewBlock() R1->>R1: CheckNewBlockForIBCPackets() - R1->>A2: QueryAndProve(FungibleTokenPacketData) + R1->>+A2: QueryAndProve(FungibleTokenPacketData) A2->>R1: FungibleTokenPacketData - A2->>R1: Proof(FungibleTokenPacketData) - R1->>B2: Validate(FungibleTokenPacketData, Proof(FungibleTokenPacketData)) - B2->>B3: Verify(Proof(FungibleTokenPacketData)) - B3->>B2: FoundInState(FungibleTokenPacketData) - B2->>B1: Send 10$POKT to User B + A2->>-R1: Proof(FungibleTokenPacketData) + R1->>+B2: Validate(FungibleTokenPacketData, Proof(FungibleTokenPacketData)) + B2->>+B3: Verify(Proof(FungibleTokenPacketData)) + B3->>-B2: FoundInState(FungibleTokenPacketData) + B2->>-B1: Send 10$POKT to User B B1->>B1: Mint(10$POKT) B1->>UB: Receive 10$POKT from User A ``` @@ -96,7 +96,7 @@ The `GetTimestamp()` function returns the current unix timestamp of the host mac ## IBC State -As mentioned [above](#persistence) the IBC store **must** be included in the consensus state of the network. As such the IBC store as defined in [ICS-24][ics24] has been implemented as a single IBC state tree. +As mentioned [above](#persistence) the IBC store **MUST** be included in the consensus state of the network. As such the IBC store as defined in [ICS-24][ics24] has been implemented as a single IBC state tree. ### IBC State Tree @@ -104,7 +104,9 @@ The IBC state tree is a non-value hashing `SMT` backed by a persistent `KVStore` ### IBC Messages -As the hosts make changes to this IBC store locally (creating light clients, opening connections, and sending packets for example); these changes must be propagated throughout the network to ensure the IBC state tree is consistent across all nodes. This is achieved by the new `IbcMessage` type defined in [ibc/types/proto/messages.proto](../types/proto/messages.proto). This type acts as an enum representing two possible state transition events: +Hosts maintain uncommitted changes in a local ephemeral IBC store while messages propagate through the mempool. + +These messages enable a variety of IBC related state changes such as creating light clients, opening connections, sending packets, etc... This is enabled by propagating `IbcMessage` types defined in [ibc/types/proto/messages.proto](../types/proto/messages.proto). This type acts as an enum representing two possible state transition events: - `UpdateIbcStore`: Updating the store with a key-value pair; adding a new or updating an existing element - `PruneIbcStore`: Pruning the store via its key; removal of an existing element From 65f647fe2fdb365d60bbfe9732a9e7be76adb242 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:59:41 +0100 Subject: [PATCH 60/74] Address comments --- consensus/e2e_tests/utils_test.go | 2 +- ibc/docs/ics24.md | 28 ++++++++++++------------- ibc/host.go | 2 +- ibc/{ibc.feature => ibc.feature.wip} | 9 +++++++- ibc/main_test.go | 10 ++++----- ibc/store/provable_store.go | 2 +- internal/testutil/ibc/mock.go | 8 +++---- persistence/db.go | 6 +++--- persistence/debug.go | 2 +- persistence/docs/PROTOCOL_STATE_HASH.md | 6 +++--- persistence/ibc.go | 2 +- persistence/sql/sql.go | 2 +- persistence/trees/trees.go | 14 ++++++------- persistence/types/ibc.go | 12 +++++------ shared/messaging/events.go | 2 +- shared/node.go | 2 +- 16 files changed, 58 insertions(+), 51 deletions(-) rename ibc/{ibc.feature => ibc.feature.wip} (57%) diff --git a/consensus/e2e_tests/utils_test.go b/consensus/e2e_tests/utils_test.go index 94294af26..21243c199 100644 --- a/consensus/e2e_tests/utils_test.go +++ b/consensus/e2e_tests/utils_test.go @@ -122,7 +122,7 @@ func CreateTestConsensusPocketNode( telemetryMock := baseTelemetryMock(t, eventsChannel) loggerMock := baseLoggerMock(t, eventsChannel) rpcMock := baseRpcMock(t, eventsChannel) - ibcMock := ibcUtils.IbcMockWithHost(t, eventsChannel) + ibcMock := ibcUtils.IBCMockWithHost(t, eventsChannel) for _, module := range []modules.Module{ p2pMock, diff --git a/ibc/docs/ics24.md b/ibc/docs/ics24.md index e32fdc65d..49edfddf3 100644 --- a/ibc/docs/ics24.md +++ b/ibc/docs/ics24.md @@ -106,20 +106,20 @@ The IBC state tree is a non-value hashing `SMT` backed by a persistent `KVStore` Hosts maintain uncommitted changes in a local ephemeral IBC store while messages propagate through the mempool. -These messages enable a variety of IBC related state changes such as creating light clients, opening connections, sending packets, etc... This is enabled by propagating `IbcMessage` types defined in [ibc/types/proto/messages.proto](../types/proto/messages.proto). This type acts as an enum representing two possible state transition events: +These messages enable a variety of IBC related state changes such as creating light clients, opening connections, sending packets, etc... This is enabled by propagating `IBCMessage` types defined in [ibc/types/proto/messages.proto](../types/proto/messages.proto). This type acts as an enum representing two possible state transition events: -- `UpdateIbcStore`: Updating the store with a key-value pair; adding a new or updating an existing element -- `PruneIbcStore`: Pruning the store via its key; removal of an existing element +- `UpdateIBCStore`: Updating the store with a key-value pair; adding a new or updating an existing element +- `PruneIBCStore`: Pruning the store via its key; removal of an existing element _Note: In both types described above the `key` field **must** already be prefixed with the `CommitmentPrefix` and should be a valid path in the store._ -When changes are made locally they are not committed to the IBC store itself but are instead used to create an `IbcMessage` which is broadcasted to the network. This is akin to a simple send transaction that has been propagated throughout the mempool but has not been committed to the on-chain state. +When changes are made locally they are not committed to the IBC store itself but are instead used to create an `IBCMessage` which is broadcasted to the network. This is akin to a simple send transaction that has been propagated throughout the mempool but has not been committed to the on-chain state. ### IBC Message Handling -Upon a node receiving an `IbcMessage` from the event bus it will use the `HandleMessage()` method of the `IBCModule` to add this message to the transactions mempool via the following steps: +Upon a node receiving an `IBCMessage` from the event bus it will use the `HandleMessage()` method of the `IBCModule` to add this message to the transactions mempool via the following steps: -1. Wrap the `IbcMessage` within a `Transaction` +1. Wrap the `IBCMessage` within a `Transaction` 2. Sign the `Transaction` using the `IBCModule`'s private key 3. Broadcast the `Transaction` throughout the mempool @@ -132,9 +132,9 @@ graph LR I1["HandleMessage(Message)"] end subgraph Handler - H1["ConvertIbcMessageToTransaction(IbcMessage)"] + H1["ConvertIBCMessageToTransaction(IBCMessage)"] subgraph Transaction - T1["coreTypes.Transaction{Msg: IbcMessage}"] + T1["coreTypes.Transaction{Msg: IBCMessage}"] end H2["SignTransaction(Transaction)"] end @@ -143,8 +143,8 @@ graph LR M2["AddToMempool(Transaction)"] end Bus--Message-->I - I--IbcMessage-->Handler - H1--IbcMessage-->Transaction + I--IBCMessage-->Handler + H1--IBCMessage-->Transaction Transaction--Transaction-->H2 Handler--Transaction-->Mempool M1--Transaction-->M2 @@ -154,14 +154,14 @@ See: [ibc/module.go](../module.go) for the specific implementation details. ### Mempool -With the `IbcMessage` now propagated through the network's mempool, when it is reaped (by the block proposer) the message's validity will be handled by first determining the type of the `IbcMessage`: +With the `IBCMessage` now propagated through the network's mempool, when it is reaped (by the block proposer) the message's validity will be handled by first determining the type of the `IBCMessage`: -- `UpdateIbcStore`: The `key` and `value` fields are tracked by persistence and used to update the `ibc` store state tree -- `PruneIbcStore`: The `key` field is tracked by persistence and marked for removal in the `ibc` store state tree +- `UpdateIBCStore`: The `key` and `value` fields are tracked by persistence and used to update the `ibc` store state tree +- `PruneIBCStore`: The `key` field is tracked by persistence and marked for removal in the `ibc` store state tree ### State Transition -See: [PROTOCOL_STATE_HASH.md](../../persistence/docs/PROTOCOL_STATE_HASH.md#ibc-state-tree) for more information on how the persistence module uses the data it has tracked from the `IbcMessage` objects, in order to update the actual state trees and in turn the root hash. +See: [PROTOCOL_STATE_HASH.md](../../persistence/docs/PROTOCOL_STATE_HASH.md#ibc-state-tree) for more information on how the persistence module uses the data it has tracked from the `IBCMessage` objects, in order to update the actual state trees and in turn the root hash. [ics24]: https://github.com/cosmos/ibc/blob/main/spec/core/ics-024-host-requirements/README.md [ics20]: https://github.com/cosmos/ibc/blob/main/spec/app/ics-020-fungible-token-transfer/README.md diff --git a/ibc/host.go b/ibc/host.go index 442017b80..db2f791d3 100644 --- a/ibc/host.go +++ b/ibc/host.go @@ -1,7 +1,7 @@ // TECHDEBT(#854): The host should be a submodule with access to the bus, this allows for the // host to access the persistence module and thus the tree store in order to create local // copies of the IBC state tree with the GetProvableStore() function. Bus access also will -// allow for the host to send any local changes to these stores as IbcMessage types through +// allow for the host to send any local changes to these stores as IBCMessage types through // the P2P module's Broadcast() function to allow them to propagate through the network's mempool. package ibc diff --git a/ibc/ibc.feature b/ibc/ibc.feature.wip similarity index 57% rename from ibc/ibc.feature rename to ibc/ibc.feature.wip index e8c297bb2..cc5278341 100644 --- a/ibc/ibc.feature +++ b/ibc/ibc.feature.wip @@ -3,10 +3,17 @@ Tests to add: - Local IBC store change is propagated through the network - Check via P2P magic - Local IBC store change is included in PostgresDB - - Check via SQL query + - Check via SQL query/magic + - Duplicate IBC messages w/in the same block + - Check via SQL query/magic + - Shouldn't allow duplicates + - Invalid IBC messages + - Should error before going to the network - Add E2E tests to cover the following: - Local IBC store change gets included in the next block - Check via inclusion proof - Local IBC store change that is not included in next block stays in mempool - Check by querying mempool - Check via exclusion proof + - Duplicate IBC messages in subsequent blocks + - Changes that don't change the state should be noop and not included diff --git a/ibc/main_test.go b/ibc/main_test.go index a2f9abe7c..d96df0669 100644 --- a/ibc/main_test.go +++ b/ibc/main_test.go @@ -44,7 +44,7 @@ func newTestPersistenceModule(bus modules.Bus) modules.PersistenceModule { return persistenceMod.(modules.PersistenceModule) } -func newTestIbcModule(bus modules.Bus) modules.IBCModule { +func newTestIBCModule(bus modules.Bus) modules.IBCModule { ibcMod, err := Create(bus) if err != nil { log.Fatalf("Error creating ibc module: %s", err) @@ -77,8 +77,8 @@ func prepareEnvironment( err = testUtilityMod.Start() require.NoError(t, err) - testIbcMod := newTestIbcModule(bus) - err = testIbcMod.Start() + testIBCMod := newTestIBCModule(bus) + err = testIBCMod.Start() require.NoError(t, err) // Reset database to genesis before every test @@ -94,11 +94,11 @@ func prepareEnvironment( require.NoError(t, err) err = testUtilityMod.Stop() require.NoError(t, err) - err = testIbcMod.Stop() + err = testIBCMod.Stop() require.NoError(t, err) }) - return runtimeCfg, testUtilityMod, testPersistenceMod, testIbcMod + return runtimeCfg, testUtilityMod, testPersistenceMod, testIBCMod } //nolint:unparam // Test suite is not fully built out yet diff --git a/ibc/store/provable_store.go b/ibc/store/provable_store.go index 789685c21..98e85e067 100644 --- a/ibc/store/provable_store.go +++ b/ibc/store/provable_store.go @@ -14,7 +14,7 @@ var _ modules.ProvableStore = &provableStore{} // provableStore implements the ProvableStore interface and wraps an SMT // it operates in memory and thus cannot make any changes to the underlying -// database. All changes must be propagated through the `IbcMessage` type +// database. All changes must be propagated through the `IBCMessage` type // and added to the mempool for inclusion in the next block type provableStore struct { prefix coreTypes.CommitmentPrefix diff --git a/internal/testutil/ibc/mock.go b/internal/testutil/ibc/mock.go index b4a931d77..e44651125 100644 --- a/internal/testutil/ibc/mock.go +++ b/internal/testutil/ibc/mock.go @@ -9,8 +9,8 @@ import ( "github.com/regen-network/gocuke" ) -// BaseIbcMock returns a mock IBC module without a Host -func BaseIbcMock(t gocuke.TestingT, busMock *mockModules.MockBus) *mockModules.MockIBCModule { +// BaseIBCMock returns a mock IBC module without a Host +func BaseIBCMock(t gocuke.TestingT, busMock *mockModules.MockBus) *mockModules.MockIBCModule { ctrl := gomock.NewController(t) ibcMock := mockModules.NewMockIBCModule(ctrl) @@ -22,8 +22,8 @@ func BaseIbcMock(t gocuke.TestingT, busMock *mockModules.MockBus) *mockModules.M return ibcMock } -// IbcMockWithHost returns a mock IBC module with a Host -func IbcMockWithHost(t gocuke.TestingT, _ modules.EventsChannel) *mockModules.MockIBCModule { +// IBCMockWithHost returns a mock IBC module with a Host +func IBCMockWithHost(t gocuke.TestingT, _ modules.EventsChannel) *mockModules.MockIBCModule { ctrl := gomock.NewController(t) ibcMock := mockModules.NewMockIBCModule(ctrl) diff --git a/persistence/db.go b/persistence/db.go index 8b420c0ff..27a0fd2c1 100644 --- a/persistence/db.go +++ b/persistence/db.go @@ -134,7 +134,7 @@ func initializeAllTables(ctx context.Context, db *pgxpool.Conn) error { } } - if err := initialiseIbcTables(ctx, db); err != nil { + if err := initialiseIBCTables(ctx, db); err != nil { return err } @@ -189,8 +189,8 @@ func initializeBlockTables(ctx context.Context, db *pgxpool.Conn) error { return nil } -func initialiseIbcTables(ctx context.Context, db *pgxpool.Conn) error { - if _, err := db.Exec(ctx, fmt.Sprintf(`%s %s %s %s`, CreateTable, IfNotExists, types.IbcStoreTableName, types.IbcStoreTableSchema)); err != nil { +func initialiseIBCTables(ctx context.Context, db *pgxpool.Conn) error { + if _, err := db.Exec(ctx, fmt.Sprintf(`%s %s %s %s`, CreateTable, IfNotExists, types.IBCStoreTableName, types.IBCStoreTableSchema)); err != nil { return err } return nil diff --git a/persistence/debug.go b/persistence/debug.go index 90baf0da8..38e69e221 100644 --- a/persistence/debug.go +++ b/persistence/debug.go @@ -14,7 +14,7 @@ var nonActorClearFunctions = []func() string{ types.ClearAllGovParamsQuery, types.ClearAllGovFlagsQuery, types.ClearAllBlocksQuery, - types.ClearAllIbcQuery, + types.ClearAllIBCQuery, } func (m *persistenceModule) HandleDebugMessage(debugMessage *messaging.DebugMessage) error { diff --git a/persistence/docs/PROTOCOL_STATE_HASH.md b/persistence/docs/PROTOCOL_STATE_HASH.md index 1d2e28c8e..d98040ccc 100644 --- a/persistence/docs/PROTOCOL_STATE_HASH.md +++ b/persistence/docs/PROTOCOL_STATE_HASH.md @@ -101,15 +101,15 @@ sequenceDiagram When the new state hash is computed, the different state trees read the updates from their respective Postgres tables and update the trees accordingly. -`IbcMessage` objects are inserted into the `ibc_message` table in two ways., depending on the IBC messages' type: 1. `UpdateIbcStore`: the `key` and `value` fields are inserted with the height into the table 2. `PruneIbcStore`: the `key` with a `nil` value is inserted into the table +`IBCMessage` objects are inserted into the `ibc_entries` table in two ways., depending on the IBC messages' type: 1. `UpdateIBCStore`: the `key` and `value` fields are inserted with the height into the table 2. `PruneIBCStore`: the `key` with a `nil` value is inserted into the table For each entry in the `ibc_message` table depending on the entries `value` field the tree will perform one of two operations: - `value == nil` - - This is a `PruneIbcStore` message and thus the tree will delete the entry with the given `key` + - This is a `PruneIBCStore` message and thus the tree will delete the entry with the given `key` - `ibcTree.Delete(key)` - `value != nil` - - This is an `UpdateIbcStore` message and thus the tree will update the entry with the given `key` to have the given `value` + - This is an `UpdateIBCStore` message and thus the tree will update the entry with the given `key` to have the given `value` - `ibcTree.Update(key, value)` _Note: Prior to insertion the `key` and `value` fields of the messages are hexadecimally encoded into strings._ diff --git a/persistence/ibc.go b/persistence/ibc.go index 7e7bdb71f..45ccf6740 100644 --- a/persistence/ibc.go +++ b/persistence/ibc.go @@ -7,7 +7,7 @@ import ( // SetIBCStoreEntry sets the key value pair in the IBC store postgres table func (p *PostgresContext) SetIBCStoreEntry(key, value []byte) error { ctx, tx := p.getCtxAndTx() - if _, err := tx.Exec(ctx, pTypes.InsertIbcStoreEntryQuery(p.Height, key, value)); err != nil { + if _, err := tx.Exec(ctx, pTypes.InsertIBCStoreEntryQuery(p.Height, key, value)); err != nil { return err } return nil diff --git a/persistence/sql/sql.go b/persistence/sql/sql.go index 85b692c9e..80a1c8320 100644 --- a/persistence/sql/sql.go +++ b/persistence/sql/sql.go @@ -168,7 +168,7 @@ func GetParams(pgtx pgx.Tx, height uint64) ([]*coreTypes.Param, error) { // GetIBCStoreUpdates returns the set of key-value pairs updated at the current height for the IBC store func GetIBCStoreUpdates(pgtx pgx.Tx, height uint64) (keys, values [][]byte, err error) { fields := "key,value" - query := fmt.Sprintf("SELECT %s FROM %s WHERE height=%d ORDER BY key ASC", fields, ptypes.IbcStoreTableName, height) + query := fmt.Sprintf("SELECT %s FROM %s WHERE height=%d ORDER BY key ASC", fields, ptypes.IBCStoreTableName, height) rows, err := pgtx.Query(context.TODO(), query) if err != nil { return nil, nil, err diff --git a/persistence/trees/trees.go b/persistence/trees/trees.go index b06591969..e20b3f969 100644 --- a/persistence/trees/trees.go +++ b/persistence/trees/trees.go @@ -43,7 +43,7 @@ const ( TransactionsTreeName = "transactions" ParamsTreeName = "params" FlagsTreeName = "flags" - IbcTreeName = "ibc" + IBCTreeName = "ibc" ) var actorTypeToMerkleTreeName = map[coreTypes.ActorType]string{ @@ -66,7 +66,7 @@ var stateTreeNames = []string{ // Account Trees AccountTreeName, PoolTreeName, // Data Trees - TransactionsTreeName, ParamsTreeName, FlagsTreeName, IbcTreeName, + TransactionsTreeName, ParamsTreeName, FlagsTreeName, IBCTreeName, } // stateTree is a wrapper around the SMT that contains an identifying @@ -196,12 +196,12 @@ func (t *treeStore) updateMerkleTrees(pgtx pgx.Tx, txi indexer.TxIndexer, height if err := t.updateFlagsTree(flags); err != nil { return "", fmt.Errorf("failed to update flags tree - %w", err) } - case IbcTreeName: + case IBCTreeName: keys, values, err := sql.GetIBCStoreUpdates(pgtx, height) if err != nil { return "", fmt.Errorf("failed to get IBC store updates: %w", err) } - if err := t.updateIbcTree(keys, values); err != nil { + if err := t.updateIBCTree(keys, values); err != nil { return "", fmt.Errorf("failed to update IBC tree: %w", err) } // Default @@ -356,7 +356,7 @@ func (t *treeStore) updateFlagsTree(flags []*coreTypes.Flag) error { return nil } -func (t *treeStore) updateIbcTree(keys, values [][]byte) error { +func (t *treeStore) updateIBCTree(keys, values [][]byte) error { if len(keys) != len(values) { return fmt.Errorf("keys and values must be the same length") } @@ -364,11 +364,11 @@ func (t *treeStore) updateIbcTree(keys, values [][]byte) error { key := keys[i] value := values[i] if value == nil { - if err := t.merkleTrees[IbcTreeName].tree.Delete(key); err != nil { + if err := t.merkleTrees[IBCTreeName].tree.Delete(key); err != nil { return err } } else { - if err := t.merkleTrees[IbcTreeName].tree.Update(key, value); err != nil { + if err := t.merkleTrees[IBCTreeName].tree.Update(key, value); err != nil { return err } } diff --git a/persistence/types/ibc.go b/persistence/types/ibc.go index 4d68688bd..cc5d05ed2 100644 --- a/persistence/types/ibc.go +++ b/persistence/types/ibc.go @@ -6,8 +6,8 @@ import ( ) const ( - IbcStoreTableName = "ibc_messages" - IbcStoreTableSchema = `( + IBCStoreTableName = "ibc_entries" + IBCStoreTableSchema = `( height BIGINT NOT NULL, key TEXT NOT NULL, value TEXT, @@ -15,16 +15,16 @@ const ( )` ) -func InsertIbcStoreEntryQuery(height int64, key, value []byte) string { +func InsertIBCStoreEntryQuery(height int64, key, value []byte) string { return fmt.Sprintf( `INSERT INTO %s(height, key, value) VALUES(%d, '%s', '%s')`, - IbcStoreTableName, + IBCStoreTableName, height, hex.EncodeToString(key), hex.EncodeToString(value), ) } -func ClearAllIbcQuery() string { - return fmt.Sprintf(`DELETE FROM %s`, IbcStoreTableName) +func ClearAllIBCQuery() string { + return fmt.Sprintf(`DELETE FROM %s`, IBCStoreTableName) } diff --git a/shared/messaging/events.go b/shared/messaging/events.go index 68a5eddec..2d6f80a77 100644 --- a/shared/messaging/events.go +++ b/shared/messaging/events.go @@ -14,7 +14,7 @@ const ( TxGossipMessageContentType = "utility.TxGossipMessage" // IBC - IbcMessageContentType = "ibc.IbcMessage" + IBCMessageContentType = "ibc.IBCMessage" ) // Helper logger for state sync tranition events diff --git a/shared/node.go b/shared/node.go index c644cc793..f046caeda 100644 --- a/shared/node.go +++ b/shared/node.go @@ -189,7 +189,7 @@ func (node *Node) handleEvent(message *messaging.PocketEnvelope) error { err_p2p := node.GetBus().GetP2PModule().HandleEvent(message.Content) // TODO: Remove this lib once we move to Go 1.2 return multierr.Combine(err_consensus, err_p2p) - case messaging.IbcMessageContentType: + case messaging.IBCMessageContentType: return node.GetBus().GetIBCModule().HandleMessage(message.Content) default: logger.Global.Warn().Msgf("Unsupported message content type: %s", contentType) From 64140942faa739760f1d767768598bbdaddb0a4d Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 14:41:32 +0100 Subject: [PATCH 61/74] Update IBC errors --- ibc/messages.go | 2 +- ibc/module.go | 2 +- ibc/store/proofs_ics23.go | 4 +-- ibc/types/messages.go | 2 +- shared/core/types/error.go | 30 +++++++++++++--------- utility/unit_of_work/tx_message_handler.go | 4 +-- 6 files changed, 25 insertions(+), 19 deletions(-) diff --git a/ibc/messages.go b/ibc/messages.go index 24bbe0a67..ae810c31f 100644 --- a/ibc/messages.go +++ b/ibc/messages.go @@ -40,7 +40,7 @@ func ConvertIBCMessageToTx(ibcMessage *types.IBCMessage) (*coreTypes.Transaction case *types.IBCMessage_Prune: anyMsg, err = codec.GetCodec().ToAny(event.Prune) default: - return nil, coreTypes.ErrUnknownIBCMessageType(fmt.Sprintf("%T", event)) + return nil, coreTypes.ErrIBCUnknownMessageType(fmt.Sprintf("%T", event)) } if err != nil { return nil, err diff --git a/ibc/module.go b/ibc/module.go index cf7e4c896..d74766661 100644 --- a/ibc/module.go +++ b/ibc/module.go @@ -146,7 +146,7 @@ func (m *ibcModule) HandleMessage(message *anypb.Any) error { // newHost returns a new IBC host instance if one is not already created func (m *ibcModule) newHost() error { if m.host != nil { - return coreTypes.ErrHostAlreadyExists() + return coreTypes.ErrIBCHostAlreadyExists() } host := &host{ logger: m.logger, diff --git a/ibc/store/proofs_ics23.go b/ibc/store/proofs_ics23.go index 4f0d43fba..53e0b203d 100644 --- a/ibc/store/proofs_ics23.go +++ b/ibc/store/proofs_ics23.go @@ -64,7 +64,7 @@ func VerifyNonMembership(root ics23.CommitmentRoot, proof *ics23.CommitmentProof func createMembershipProof(tree *smt.SMT, key, value []byte) (*ics23.CommitmentProof, error) { proof, err := tree.Prove(key) if err != nil { - return nil, coreTypes.ErrCreatingProof(err) + return nil, coreTypes.ErrIBCCreatingProof(err) } return convertSMPToExistenceProof(proof, key, value), nil } @@ -73,7 +73,7 @@ func createMembershipProof(tree *smt.SMT, key, value []byte) (*ics23.CommitmentP func createNonMembershipProof(tree *smt.SMT, key []byte) (*ics23.CommitmentProof, error) { proof, err := tree.Prove(key) if err != nil { - return nil, coreTypes.ErrCreatingProof(err) + return nil, coreTypes.ErrIBCCreatingProof(err) } return convertSMPToExclusionProof(proof, key), nil diff --git a/ibc/types/messages.go b/ibc/types/messages.go index 917da4554..ac7eef3f4 100644 --- a/ibc/types/messages.go +++ b/ibc/types/messages.go @@ -22,7 +22,7 @@ func (m *IBCMessage) ValidateBasic() coreTypes.Error { case *IBCMessage_Prune: return msg.Prune.ValidateBasic() default: - return coreTypes.ErrUnknownIBCMessageType(fmt.Sprintf("%T", msg)) + return coreTypes.ErrIBCUnknownMessageType(fmt.Sprintf("%T", msg)) } } diff --git a/shared/core/types/error.go b/shared/core/types/error.go index b8c7595ed..31d1d9c07 100644 --- a/shared/core/types/error.go +++ b/shared/core/types/error.go @@ -38,7 +38,7 @@ func NewError(code Code, msg string) Error { } } -// NextCode: 146 +// NextCode: 147 type Code float64 // CONSIDERATION: Should these be a proto enum or a golang iota? //nolint:gosec // G101 - Not hard-coded credentials @@ -182,9 +182,10 @@ const ( CodeIBCInvalidID Code = 140 CodeIBCInvalidPath Code = 141 CodeCreatingProofError Code = 142 - CodeUnknownIBCMessageTypeError Code = 143 + CodeIBCUnknownMessageTypeError Code = 143 CodeNilFieldError Code = 144 - CodeUpdatingIBCStoreDBError Code = 145 + CodeIBCUpdatingStoreError Code = 145 + CodeIBCStoreAlreadyExistsError Code = 146 ) const ( @@ -325,9 +326,10 @@ const ( IBCInvalidIDError = "invalid ibc identifier" IBCInvalidPathError = "invalid ibc path" IBCCreatingProofError = "an error occurred creating the CommitmentProof" - IBCUnknownIBCMessageTypeError = "the ibc message type is not recognized" + IBCUnknownMessageTypeError = "the ibc message type is not recognized" NilFieldError = "field cannot be nil" - IBCUpdatingIBCStoreDBError = "an error occurred updating the ibc store postgres database" + IBCUpdatingStoreError = "an error occurred updating the ibc store postgres database" + IBCStoreAlreadyExistsError = "ibc store already exists in the store manager" ) func ErrUnknownParam(paramName string) Error { @@ -863,11 +865,11 @@ func ErrUnknownMessageType(messageType any) Error { return NewError(CodeUnknownMessageType, fmt.Sprintf("%s: %v", UnknownMessageTypeError, messageType)) } -func ErrHostAlreadyExists() Error { +func ErrIBCHostAlreadyExists() Error { return NewError(CodeHostAlreadyExists, IBCHostAlreadyExistsError) } -func ErrHostDoesNotExist() Error { +func ErrIBCHostDoesNotExist() Error { return NewError(CodeHostDoesNotExist, IBCHostDoesNotExistError) } @@ -879,18 +881,22 @@ func ErrIBCInvalidPath(path string) Error { return NewError(CodeIBCInvalidPath, fmt.Sprintf("%s: %s", IBCInvalidPathError, path)) } -func ErrCreatingProof(err error) Error { +func ErrIBCCreatingProof(err error) Error { return NewError(CodeCreatingProofError, fmt.Sprintf("%s: %s", IBCCreatingProofError, err.Error())) } -func ErrUnknownIBCMessageType(messageType string) Error { - return NewError(CodeUnknownIBCMessageTypeError, fmt.Sprintf("%s: %s", IBCUnknownIBCMessageTypeError, messageType)) +func ErrIBCUnknownMessageType(messageType string) Error { + return NewError(CodeIBCUnknownMessageTypeError, fmt.Sprintf("%s: %s", IBCUnknownMessageTypeError, messageType)) } func ErrNilField(field string) Error { return NewError(CodeNilFieldError, fmt.Sprintf("%s: %s", NilFieldError, field)) } -func ErrUpdatingIBCStoreDB(err error) Error { - return NewError(CodeUpdatingIBCStoreDBError, fmt.Sprintf("%s: %s", IBCUpdatingIBCStoreDBError, err.Error())) +func ErrIBCUpdatingStore(err error) Error { + return NewError(CodeIBCUpdatingStoreError, fmt.Sprintf("%s: %s", IBCUpdatingStoreError, err.Error())) +} + +func ErrIBCStoreAlreadyExists(name string) Error { + return NewError(CodeIBCStoreAlreadyExistsError, fmt.Sprintf("%s: %s", IBCStoreAlreadyExistsError, name)) } diff --git a/utility/unit_of_work/tx_message_handler.go b/utility/unit_of_work/tx_message_handler.go index 654f6d25f..6c696e14c 100644 --- a/utility/unit_of_work/tx_message_handler.go +++ b/utility/unit_of_work/tx_message_handler.go @@ -227,14 +227,14 @@ func (u *baseUtilityUnitOfWork) handleMessageChangeParameter(message *typesUtil. func (u *baseUtilityUnitOfWork) handleUpdateIBCStore(message *ibcTypes.UpdateIBCStore) coreTypes.Error { if err := u.persistenceRWContext.SetIBCStoreEntry(message.Key, message.Value); err != nil { - return coreTypes.ErrUpdatingIBCStoreDB(err) + return coreTypes.ErrIBCUpdatingStore(err) } return nil } func (u *baseUtilityUnitOfWork) handlePruneIBCStore(message *ibcTypes.PruneIBCStore) coreTypes.Error { if err := u.persistenceRWContext.SetIBCStoreEntry(message.Key, nil); err != nil { - return coreTypes.ErrUpdatingIBCStoreDB(err) + return coreTypes.ErrIBCUpdatingStore(err) } return nil } From 681a328403652be8be1a3167aae359a6353a0170 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 14:48:49 +0100 Subject: [PATCH 62/74] Remove no value hashing from state trees --- persistence/test/state_test.go | 6 +++--- persistence/trees/module.go | 4 ++-- persistence/trees/trees.go | 13 ++++--------- utility/session_test.go | 2 +- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/persistence/test/state_test.go b/persistence/test/state_test.go index 37aeacf01..d2b67fcf6 100644 --- a/persistence/test/state_test.go +++ b/persistence/test/state_test.go @@ -42,9 +42,9 @@ func TestStateHash_DeterministicStateWhenUpdatingAppStake(t *testing.T) { // logic changes, these hashes will need to be updated based on the test output. // TODO: Add an explicit updateSnapshots flag to the test to make this more clear. stateHashes := []string{ - "00788e9a5133c31c869eb58d383f2fc30aac19b585947a8c82fe5808504ff84f", - "693450dee542f1455fdbfdeab22eb91e1888af50285330d45607b58c22f985f7", - "ad13b1646d02978f893919f413c7497b6f4d332011c40b6453499316d6705680", + "1e433a8905c7b1cf42222f8d01ba222038653f8ff35ae97cce1fd6a32d18b51e", + "4542dea3eedb99ad46b3c6e0ea901a9ee365b590b2f2ac7f12678ac369a2fe90", + "1524529fa827852e397adf1938eb058848209c4916cdacef929d050efc472c1d", } stakeAmount := initialStakeAmount diff --git a/persistence/trees/module.go b/persistence/trees/module.go index cb21c9913..9c0d5eff0 100644 --- a/persistence/trees/module.go +++ b/persistence/trees/module.go @@ -70,7 +70,7 @@ func (t *treeStore) setupTrees() error { } t.merkleTrees[stateTreeNames[i]] = &stateTree{ name: stateTreeNames[i], - tree: smt.NewSparseMerkleTree(nodeStore, smtTreeHasher, smtValueHasher), + tree: smt.NewSparseMerkleTree(nodeStore, smtTreeHasher), nodeStore: nodeStore, } } @@ -90,7 +90,7 @@ func (t *treeStore) setupInMemory() error { nodeStore := kvstore.NewMemKVStore() // For testing, `smt.NewSimpleMap()` can be used as well t.merkleTrees[stateTreeNames[i]] = &stateTree{ name: stateTreeNames[i], - tree: smt.NewSparseMerkleTree(nodeStore, smtTreeHasher, smtValueHasher), + tree: smt.NewSparseMerkleTree(nodeStore, smtTreeHasher), nodeStore: nodeStore, } } diff --git a/persistence/trees/trees.go b/persistence/trees/trees.go index e20b3f969..f12b0871c 100644 --- a/persistence/trees/trees.go +++ b/persistence/trees/trees.go @@ -23,14 +23,9 @@ import ( "github.com/pokt-network/smt" ) -var ( - // smtTreeHasher sets the hasher used by the tree SMT trees - // as a package level variable for visibility and internal use. - smtTreeHasher hash.Hash = sha256.New() - // smtValueHasher sets the hasher used by the tree SMT trees - // to be nil, which means that values are not prehashed - smtValueHasher smt.Option = smt.WithValueHasher(nil) -) +// smtTreeHasher sets the hasher used by the tree SMT trees +// as a package level variable for visibility and internal use. +var smtTreeHasher hash.Hash = sha256.New() const ( RootTreeName = "root" @@ -127,7 +122,7 @@ func (t *treeStore) DebugClearAll() error { if err := nodeStore.ClearAll(); err != nil { return fmt.Errorf("failed to clear %s node store: %w", treeName, err) } - stateTree.tree = smt.NewSparseMerkleTree(nodeStore, smtTreeHasher, smtValueHasher) + stateTree.tree = smt.NewSparseMerkleTree(nodeStore, smtTreeHasher) } return nil } diff --git a/utility/session_test.go b/utility/session_test.go index c05e5bbdf..319af206c 100644 --- a/utility/session_test.go +++ b/utility/session_test.go @@ -25,7 +25,7 @@ func TestSession_GetSession_SingleFishermanSingleServicerBaseCase(t *testing.T) numFishermen := 1 numServicers := 1 // needs to be manually updated if business logic changes - expectedSessionId := "3de7deb1c64b32cf313838f8124b2b123ae78cff02bf5eca1564d9f78a764608" + expectedSessionId := "b1e9791358aae070ac7f86fdb74e5a9d26fff025fb737a2114ccf9ad95b624bd" runtimeCfg, utilityMod, _ := prepareEnvironment(t, 5, numServicers, 1, numFishermen) From 128cee5dfa90cec18f5c4ae601ce6e49ebb00e97 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 15:00:53 +0100 Subject: [PATCH 63/74] Reword IBC stores --- ibc/store/provable_store.go | 202 +++++++++++++++++++++++---- ibc/store/store_manager.go | 111 +++++++++++++++ persistence/ibc.go | 13 +- persistence/types/ibc.go | 10 ++ shared/core/types/error.go | 8 +- shared/modules/persistence_module.go | 3 + 6 files changed, 315 insertions(+), 32 deletions(-) create mode 100644 ibc/store/store_manager.go diff --git a/ibc/store/provable_store.go b/ibc/store/provable_store.go index 98e85e067..36d7a24bd 100644 --- a/ibc/store/provable_store.go +++ b/ibc/store/provable_store.go @@ -2,65 +2,207 @@ package store import ( "bytes" + "fmt" + "strconv" + "strings" ics23 "github.com/cosmos/ics23/go" "github.com/pokt-network/pocket/ibc/host" + "github.com/pokt-network/pocket/persistence/kvstore" coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/modules" - "github.com/pokt-network/smt" ) -var _ modules.ProvableStore = &provableStore{} +// CachedEntry represents a local change made to the IBC store prior to it being +// committed to the state tree. These should be written to disk in the to prevent a +// loss of data and pruned when included in the state tree +// written to disk as follows: +// "{height}/{prefixedKey}" => value +type cachedEntry struct { + name string + height uint64 + prefixedKey []byte + value []byte +} -// provableStore implements the ProvableStore interface and wraps an SMT -// it operates in memory and thus cannot make any changes to the underlying -// database. All changes must be propagated through the `IBCMessage` type -// and added to the mempool for inclusion in the next block -type provableStore struct { - prefix coreTypes.CommitmentPrefix - tree *smt.SMT +// prepare returns the key and value to be written to disk +func (c *cachedEntry) prepare() ([]byte, []byte) { + return []byte(fmt.Sprintf("%s/%d/%s", c.name, c.height, string(c.prefixedKey))), c.value } -// GetCommitmentPrefix returns the commitment prefix of the provable store -func (p *provableStore) GetCommitmentPrefix() coreTypes.CommitmentPrefix { - return p.prefix +// provableStore is a struct that interfaces with the PostgresDB instance +// obtained from Persistence. It is used to Get/Set/Delete the keys in the +// IBC state tree, in doing so it will trigger the creation of +type provableStore struct { + bus modules.Bus // used to interact with persistence (passed from IBCHost) + name string // store name in storeManager + prefix coreTypes.CommitmentPrefix // []byte(name) + cache []*cachedEntry // in-memory cache of local changes to be written to disk } -// Get returns the value in the tree of the key prefixed with the CommitmentPrefix -func (p *provableStore) Get(key []byte) ([]byte, error) { - prefixed := applyPrefix(p.prefix, key) - return p.tree.Get(prefixed) +// newProvableStore returns a new instance of provableStore with the bus and prefix provided +func newProvableStore(bus modules.Bus, prefix coreTypes.CommitmentPrefix) *provableStore { + return &provableStore{ + bus: bus, + name: string(prefix), + prefix: prefix, + cache: make([]*cachedEntry, 0), + } } -// Set sets the value in the tree of the key prefixed with the CommitmentPrefix -func (p *provableStore) Set(key, value []byte) error { +// Get queries the persistence layer for the latest value stored in the IBC state tree +// keys are automatically prefixed with the CommitmentPrefix if not present +func (p *provableStore) Get(key []byte) ([]byte, error) { prefixed := applyPrefix(p.prefix, key) - return p.tree.Update(prefixed, value) + currHeight := int64(p.bus.GetConsensusModule().CurrentHeight()) + rCtx, err := p.bus.GetPersistenceModule().NewReadContext(currHeight) + if err != nil { + return nil, err + } + defer rCtx.Release() + return rCtx.GetIBCStoreEntry(prefixed, currHeight) // returns latest value stored } -// Delete deletes the value in the tree of the key prefixed with the CommitmentPrefix -func (p *provableStore) Delete(key []byte) error { +// Get queries the persistence layer for the latest value stored in the IBC state tree +// it then generates a proof by importing the IBC state tree from the TreeStore +// keys are automatically prefixed with the CommitmentPrefix if not present +func (p *provableStore) GetAndProve(key []byte, membership bool) ([]byte, *ics23.CommitmentProof, error) { prefixed := applyPrefix(p.prefix, key) - return p.tree.Delete(prefixed) + currHeight := int64(p.bus.GetConsensusModule().CurrentHeight()) + rCtx, err := p.bus.GetPersistenceModule().NewReadContext(currHeight) + if err != nil { + return nil, nil, err + } + value, err := rCtx.GetIBCStoreEntry(prefixed, currHeight) // returns latest value stored + if err != nil { + return nil, nil, err + } + defer rCtx.Release() + proof := new(ics23.CommitmentProof) + if membership { + proof, err = p.CreateMembershipProof(key, value) + } else { + proof, err = p.CreateNonMembershipProof(key) + } + if err != nil { + return nil, nil, err + } + return value, proof, nil } // CreateMembershipProof creates a membership proof for the key-value pair with the key -// prefixed with the CommitmentPrefix +// prefixed with the CommitmentPrefix, by importing the state tree from the TreeStore func (p *provableStore) CreateMembershipProof(key, value []byte) (*ics23.CommitmentProof, error) { + // import IBC state tree + // TODO(#854): Implement tree retrieval + /** prefixed := applyPrefix(p.prefix, key) - return createMembershipProof(p.tree, prefixed, value) + root, nodeStore := p.bus.GetTreeStore() + lazy := smt.ImportSparseMerkleTree(nodeStore, root, sha256.New()) + return createMembershipProof(lazy, prefixed, value) + **/ + return nil, nil } -// CreateNonMembershipProof creates a non-membership proof for the key prefixed with the CommitmentPrefix +// CreateNonMembershipProof creates a non-membership proof for the key prefixed with the +// CommitmentPrefix, by importing the state tree from the TreeStore func (p *provableStore) CreateNonMembershipProof(key []byte) (*ics23.CommitmentProof, error) { + // import IBC state tree + // TODO(#854): Implement tree retrieval + /** prefixed := applyPrefix(p.prefix, key) - return createNonMembershipProof(p.tree, prefixed) + root, nodeStore := p.bus.GetTreeStore() + lazy := smt.ImportSparseMerkleTree(nodeStore, root, sha256.New()) + return createNonMembershipProof(lazy, prefixed) + **/ + return nil, nil } -// Root returns the root of the entire tree -// NOTE: Root does not work on a per-prefix basis but returns the root of the entire tree -func (p *provableStore) Root() []byte { - return p.tree.Root() +// Set updates the persistence layer with the new key-value pair at the latest height and +// emits an UpdateIBCStore event to the bus for propogation throughout the network, to be +// included in each node's mempool and thus the next block +func (p *provableStore) Set(key, value []byte) error { + prefixed := applyPrefix(p.prefix, key) + currHeight := int64(p.bus.GetConsensusModule().CurrentHeight()) + rwCtx, err := p.bus.GetPersistenceModule().NewRWContext(currHeight) + if err != nil { + return err + } + defer rwCtx.Release() + if err := rwCtx.SetIBCStoreEntry(prefixed, value); err != nil { + return err + } + // TODO(#854): Implement emit functions + // return emitUpdateStoreEvent(p.prefix, key, value) + return nil +} + +// Delete updates the persistence layer with the key and nil value pair at the latest height +// and emits an PruneIBCStore event to the bus for propogation throughout the network, to be +// included in each node's mempool and thus the next block +func (p *provableStore) Delete(key []byte) error { + prefixed := applyPrefix(p.prefix, key) + currHeight := int64(p.bus.GetConsensusModule().CurrentHeight()) + rwCtx, err := p.bus.GetPersistenceModule().NewRWContext(currHeight) + if err != nil { + return err + } + defer rwCtx.Release() + if err := rwCtx.SetIBCStoreEntry(prefixed, nil); err != nil { + return err + } + // TODO(#854): Implement emit functions + // return emitPruneStoreEvent(p.prefix, key) + return nil +} + +// CacheEntries writes all local changes to disk and clears the in-memory cache +func (p *provableStore) CacheEntries(store kvstore.KVStore) error { + for _, entry := range p.cache { + key, value := entry.prepare() + if err := store.Set(key, value); err != nil { + return err + } + } + p.cache = make([]*cachedEntry, 0) + return nil +} + +// PruneCache removes all entries from the cache at the given height +func (p *provableStore) PruneCache(store kvstore.KVStore, height uint64) error { + keys, _, err := store.GetAll([]byte(fmt.Sprintf("%s/%d", p.name, height)), false) + if err != nil { + return err + } + for _, key := range keys { + if err := store.Delete(key); err != nil { + return err + } + } + return nil +} + +// RestoreCache loads all entries from disk into the cache +func (p *provableStore) RestoreCache(store kvstore.KVStore) error { + keys, values, err := store.GetAll(p.prefix, false) + if err != nil { + return err + } + for i := 0; i < len(keys); i++ { + parts := strings.SplitN(string(keys[i]), "/", 2) // name, heightStr, prefixedKeyStr + height, err := strconv.ParseUint(parts[1], 10, 64) + if err != nil { + return err + } + value := values[i] + p.cache = append(p.cache, &cachedEntry{ + name: parts[0], + height: height, + prefixedKey: []byte(parts[2]), + value: value, + }) + } + return nil } // applyPrefix will apply the CommitmentPrefix to the key provided if not already applied diff --git a/ibc/store/store_manager.go b/ibc/store/store_manager.go new file mode 100644 index 000000000..6e80f61b5 --- /dev/null +++ b/ibc/store/store_manager.go @@ -0,0 +1,111 @@ +package store + +import ( + "fmt" + "sync" + + "github.com/pokt-network/pocket/persistence/kvstore" + coreTypes "github.com/pokt-network/pocket/shared/core/types" + "github.com/pokt-network/pocket/shared/modules" +) + +var cacheDirs = func(storesDir string) string { return fmt.Sprintf("%s/caches", storesDir) } + +// storeManager holds an in-memory map of all the provable stores in use +type storeManager struct { + m sync.Mutex + storesDir string + stores map[string]*provableStore +} + +// newStoreManager returns a new storeManager instance +func newStoreManager(storesDir string) *storeManager { + return &storeManager{ + m: sync.Mutex{}, + storesDir: storesDir, + stores: make(map[string]*provableStore, 0), + } +} + +// AddStore creates and adds a provableStore to the storeManager +// if one of the same name does not already exist +func (s *storeManager) AddStore(prefix coreTypes.CommitmentPrefix, bus modules.Bus) error { + s.m.Lock() + defer s.m.Unlock() + if _, ok := s.stores[string(prefix)]; ok { + return coreTypes.ErrIBCStoreAlreadyExists(string(prefix)) + } + store := newProvableStore(bus, prefix) + s.stores[store.name] = store + return nil +} + +// GetStore returns the provableStore with the given name +func (s *storeManager) GetStore(name string) (*provableStore, error) { + s.m.Lock() + defer s.m.Unlock() + store, ok := s.stores[name] + if !ok { + return nil, coreTypes.ErrIBCStoreDoesNotExist(name) + } + return store, nil +} + +// RemoveStore removes the provableStore with the given name +func (s *storeManager) RemoveStore(name string) error { + s.m.Lock() + defer s.m.Unlock() + if _, ok := s.stores[name]; !ok { + return coreTypes.ErrIBCStoreDoesNotExist(name) + } + delete(s.stores, name) + return nil +} + +// CacheAllEntries caches all the entries for all stores in the storeManager +func (s *storeManager) CacheAllEntries() error { + s.m.Lock() + defer s.m.Unlock() + disk, err := kvstore.NewKVStore(cacheDirs(s.storesDir)) + if err != nil { + return err + } + for _, store := range s.stores { + if err := store.CacheEntries(disk); err != nil { + return err + } + } + return disk.Stop() +} + +// PruneCaches prunes the caches for all stores in the storeManager at the given height +func (s *storeManager) PruneCaches(height uint64) error { + s.m.Lock() + defer s.m.Unlock() + disk, err := kvstore.NewKVStore(cacheDirs(s.storesDir)) + if err != nil { + return err + } + for _, store := range s.stores { + if err := store.PruneCache(disk, height); err != nil { + return err + } + } + return disk.Stop() +} + +// RestoreCaches restores the caches from disk for all stores in the storeManager +func (s *storeManager) RestoreCaches() error { + s.m.Lock() + defer s.m.Unlock() + disk, err := kvstore.NewKVStore(cacheDirs(s.storesDir)) + if err != nil { + return err + } + for _, store := range s.stores { + if err := store.RestoreCache(disk); err != nil { + return err + } + } + return disk.Stop() +} diff --git a/persistence/ibc.go b/persistence/ibc.go index 45ccf6740..abfd76dba 100644 --- a/persistence/ibc.go +++ b/persistence/ibc.go @@ -4,7 +4,7 @@ import ( pTypes "github.com/pokt-network/pocket/persistence/types" ) -// SetIBCStoreEntry sets the key value pair in the IBC store postgres table +// SetIBCStoreEntry sets the key value pair in the IBC store postgres table at the current height func (p *PostgresContext) SetIBCStoreEntry(key, value []byte) error { ctx, tx := p.getCtxAndTx() if _, err := tx.Exec(ctx, pTypes.InsertIBCStoreEntryQuery(p.Height, key, value)); err != nil { @@ -12,3 +12,14 @@ func (p *PostgresContext) SetIBCStoreEntry(key, value []byte) error { } return nil } + +// GetIBCStoreEntry returns the stored value for the key at the height provided from the IBC store table +func (p *PostgresContext) GetIBCStoreEntry(key []byte, height int64) ([]byte, error) { + ctx, tx := p.getCtxAndTx() + row := tx.QueryRow(ctx, pTypes.GetIBCStoreEntryQuery(height, key)) + var value []byte + if err := row.Scan(&value); err != nil { + return nil, err + } + return value, nil +} diff --git a/persistence/types/ibc.go b/persistence/types/ibc.go index cc5d05ed2..42bd096a8 100644 --- a/persistence/types/ibc.go +++ b/persistence/types/ibc.go @@ -25,6 +25,16 @@ func InsertIBCStoreEntryQuery(height int64, key, value []byte) string { ) } +// Return the latest value for the key at the height provided or at the last updated height +func GetIBCStoreEntryQuery(height int64, key []byte) string { + return fmt.Sprintf( + `SELECT value FROM %s WHERE height <= %d AND key = '%s' ORDER BY height DESC LIMIT 1`, + IBCStoreTableName, + height, + hex.EncodeToString(key), + ) +} + func ClearAllIBCQuery() string { return fmt.Sprintf(`DELETE FROM %s`, IBCStoreTableName) } diff --git a/shared/core/types/error.go b/shared/core/types/error.go index 31d1d9c07..76d7686f0 100644 --- a/shared/core/types/error.go +++ b/shared/core/types/error.go @@ -38,7 +38,7 @@ func NewError(code Code, msg string) Error { } } -// NextCode: 147 +// NextCode: 148 type Code float64 // CONSIDERATION: Should these be a proto enum or a golang iota? //nolint:gosec // G101 - Not hard-coded credentials @@ -186,6 +186,7 @@ const ( CodeNilFieldError Code = 144 CodeIBCUpdatingStoreError Code = 145 CodeIBCStoreAlreadyExistsError Code = 146 + CodeIBCStoreDoesNotExistError Code = 147 ) const ( @@ -330,6 +331,7 @@ const ( NilFieldError = "field cannot be nil" IBCUpdatingStoreError = "an error occurred updating the ibc store postgres database" IBCStoreAlreadyExistsError = "ibc store already exists in the store manager" + IBCStoreDoesNotExistError = "ibc store does not exist in the store manager" ) func ErrUnknownParam(paramName string) Error { @@ -900,3 +902,7 @@ func ErrIBCUpdatingStore(err error) Error { func ErrIBCStoreAlreadyExists(name string) Error { return NewError(CodeIBCStoreAlreadyExistsError, fmt.Sprintf("%s: %s", IBCStoreAlreadyExistsError, name)) } + +func ErrIBCStoreDoesNotExist(name string) Error { + return NewError(CodeIBCStoreDoesNotExistError, fmt.Sprintf("%s: %s", IBCStoreDoesNotExistError, name)) +} diff --git a/shared/modules/persistence_module.go b/shared/modules/persistence_module.go index d6c0d8a6b..c77288730 100644 --- a/shared/modules/persistence_module.go +++ b/shared/modules/persistence_module.go @@ -226,4 +226,7 @@ type PersistenceReadContext interface { GetIntFlag(paramName string, height int64) (int, bool, error) GetStringFlag(paramName string, height int64) (string, bool, error) GetBytesFlag(paramName string, height int64) ([]byte, bool, error) + + // IBC Queries + GetIBCStoreEntry(key []byte, height int64) ([]byte, error) } From ccea884ca543025a472ee0c64300a23ea0ec0294 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 15:32:04 +0100 Subject: [PATCH 64/74] Update interfaces --- ibc/store/provable_store.go | 45 ++++++++++++++++++++++++++++-------- ibc/store/store_manager.go | 15 +++++++----- shared/modules/ibc_module.go | 25 ++++++++++++++++---- 3 files changed, 66 insertions(+), 19 deletions(-) diff --git a/ibc/store/provable_store.go b/ibc/store/provable_store.go index 36d7a24bd..ac69698d8 100644 --- a/ibc/store/provable_store.go +++ b/ibc/store/provable_store.go @@ -13,21 +13,23 @@ import ( "github.com/pokt-network/pocket/shared/modules" ) +var _ modules.ProvableStore = &provableStore{} + // CachedEntry represents a local change made to the IBC store prior to it being // committed to the state tree. These should be written to disk in the to prevent a // loss of data and pruned when included in the state tree // written to disk as follows: // "{height}/{prefixedKey}" => value type cachedEntry struct { - name string + storeName string height uint64 prefixedKey []byte value []byte } // prepare returns the key and value to be written to disk -func (c *cachedEntry) prepare() ([]byte, []byte) { - return []byte(fmt.Sprintf("%s/%d/%s", c.name, c.height, string(c.prefixedKey))), c.value +func (c *cachedEntry) prepare() (key []byte, value []byte) { + return []byte(fmt.Sprintf("%s/%d/%s", c.storeName, c.height, string(c.prefixedKey))), c.value } // provableStore is a struct that interfaces with the PostgresDB instance @@ -78,7 +80,7 @@ func (p *provableStore) GetAndProve(key []byte, membership bool) ([]byte, *ics23 return nil, nil, err } defer rCtx.Release() - proof := new(ics23.CommitmentProof) + var proof *ics23.CommitmentProof if membership { proof, err = p.CreateMembershipProof(key, value) } else { @@ -97,7 +99,7 @@ func (p *provableStore) CreateMembershipProof(key, value []byte) (*ics23.Commitm // TODO(#854): Implement tree retrieval /** prefixed := applyPrefix(p.prefix, key) - root, nodeStore := p.bus.GetTreeStore() + root, nodeStore := p.bus.GetTreeStore().GetTree(trees.IBCStateTree) lazy := smt.ImportSparseMerkleTree(nodeStore, root, sha256.New()) return createMembershipProof(lazy, prefixed, value) **/ @@ -111,7 +113,7 @@ func (p *provableStore) CreateNonMembershipProof(key []byte) (*ics23.CommitmentP // TODO(#854): Implement tree retrieval /** prefixed := applyPrefix(p.prefix, key) - root, nodeStore := p.bus.GetTreeStore() + root, nodeStore := p.bus.GetTreeStore().GetTree(trees.IBCStateTree) lazy := smt.ImportSparseMerkleTree(nodeStore, root, sha256.New()) return createNonMembershipProof(lazy, prefixed) **/ @@ -119,7 +121,7 @@ func (p *provableStore) CreateNonMembershipProof(key []byte) (*ics23.CommitmentP } // Set updates the persistence layer with the new key-value pair at the latest height and -// emits an UpdateIBCStore event to the bus for propogation throughout the network, to be +// emits an UpdateIBCStore event to the bus for propagation throughout the network, to be // included in each node's mempool and thus the next block func (p *provableStore) Set(key, value []byte) error { prefixed := applyPrefix(p.prefix, key) @@ -132,13 +134,19 @@ func (p *provableStore) Set(key, value []byte) error { if err := rwCtx.SetIBCStoreEntry(prefixed, value); err != nil { return err } + p.cache = append(p.cache, &cachedEntry{ + storeName: p.name, + height: uint64(currHeight), + prefixedKey: prefixed, + value: value, + }) // TODO(#854): Implement emit functions // return emitUpdateStoreEvent(p.prefix, key, value) return nil } // Delete updates the persistence layer with the key and nil value pair at the latest height -// and emits an PruneIBCStore event to the bus for propogation throughout the network, to be +// and emits an PruneIBCStore event to the bus for propagation throughout the network, to be // included in each node's mempool and thus the next block func (p *provableStore) Delete(key []byte) error { prefixed := applyPrefix(p.prefix, key) @@ -151,11 +159,30 @@ func (p *provableStore) Delete(key []byte) error { if err := rwCtx.SetIBCStoreEntry(prefixed, nil); err != nil { return err } + p.cache = append(p.cache, &cachedEntry{ + storeName: p.name, + height: uint64(currHeight), + prefixedKey: prefixed, + value: nil, + }) // TODO(#854): Implement emit functions // return emitPruneStoreEvent(p.prefix, key) return nil } +// GetCommitmentPrefix returns the CommitmentPrefix for the store +func (p *provableStore) GetCommitmentPrefix() coreTypes.CommitmentPrefix { return p.prefix } + +// Root returns the current root of the IBC state tree +func (p *provableStore) Root() ics23.CommitmentRoot { + // TODO(#854): Implement tree retrieval + /** + root, _ := p.bus.GetTreeStore().GetTree(trees.IBCStateTree) + return root + **/ + return nil +} + // CacheEntries writes all local changes to disk and clears the in-memory cache func (p *provableStore) CacheEntries(store kvstore.KVStore) error { for _, entry := range p.cache { @@ -196,7 +223,7 @@ func (p *provableStore) RestoreCache(store kvstore.KVStore) error { } value := values[i] p.cache = append(p.cache, &cachedEntry{ - name: parts[0], + storeName: parts[0], height: height, prefixedKey: []byte(parts[2]), value: value, diff --git a/ibc/store/store_manager.go b/ibc/store/store_manager.go index 6e80f61b5..ba520652e 100644 --- a/ibc/store/store_manager.go +++ b/ibc/store/store_manager.go @@ -9,7 +9,10 @@ import ( "github.com/pokt-network/pocket/shared/modules" ) -var cacheDirs = func(storesDir string) string { return fmt.Sprintf("%s/caches", storesDir) } +var ( + _ modules.IBCStoreManager = &storeManager{} + cacheDirs = func(storesDir string) string { return fmt.Sprintf("%s/caches", storesDir) } +) // storeManager holds an in-memory map of all the provable stores in use type storeManager struct { @@ -29,19 +32,19 @@ func newStoreManager(storesDir string) *storeManager { // AddStore creates and adds a provableStore to the storeManager // if one of the same name does not already exist -func (s *storeManager) AddStore(prefix coreTypes.CommitmentPrefix, bus modules.Bus) error { +func (s *storeManager) AddStore(name string, bus modules.Bus) error { s.m.Lock() defer s.m.Unlock() - if _, ok := s.stores[string(prefix)]; ok { - return coreTypes.ErrIBCStoreAlreadyExists(string(prefix)) + if _, ok := s.stores[name]; ok { + return coreTypes.ErrIBCStoreAlreadyExists(name) } - store := newProvableStore(bus, prefix) + store := newProvableStore(bus, coreTypes.CommitmentPrefix(name)) s.stores[store.name] = store return nil } // GetStore returns the provableStore with the given name -func (s *storeManager) GetStore(name string) (*provableStore, error) { +func (s *storeManager) GetStore(name string) (modules.ProvableStore, error) { s.m.Lock() defer s.m.Unlock() store, ok := s.stores[name] diff --git a/shared/modules/ibc_module.go b/shared/modules/ibc_module.go index 1ebc5b601..b03835467 100644 --- a/shared/modules/ibc_module.go +++ b/shared/modules/ibc_module.go @@ -2,11 +2,12 @@ package modules import ( ics23 "github.com/cosmos/ics23/go" + "github.com/pokt-network/pocket/persistence/kvstore" coreTypes "github.com/pokt-network/pocket/shared/core/types" "google.golang.org/protobuf/types/known/anypb" ) -//go:generate mockgen -destination=./mocks/ibc_module_mock.go github.com/pokt-network/pocket/shared/modules IBCModule,IBCHost,IBCHandler,ProvableStore +//go:generate mockgen -destination=./mocks/ibc_module_mock.go github.com/pokt-network/pocket/shared/modules IBCModule,IBCHost,IBCHandler,IBCStoreManager,ProvableStore const IBCModuleName = "ibc" @@ -206,12 +207,28 @@ type IBCHandler interface { **/ } +// IBCStoreManager manages the different ProvableStore instances created by the IBC host +type IBCStoreManager interface { + AddStore(name string, bus Bus) error + GetStore(name string) (ProvableStore, error) + RemoveStore(name string) error + CacheAllEntries() error + PruneCaches(height uint64) error + RestoreCaches() error +} + +// ProvableStore interacts with Persistence and the IBC state tree in order for the IBC host to +// be able to interact with the IBC store locally and propagate any changes throuhout the network type ProvableStore interface { Get(key []byte) ([]byte, error) + GetAndProve(key []byte, membership bool) ([]byte, *ics23.CommitmentProof, error) + CreateMembershipProof(key, value []byte) (*ics23.CommitmentProof, error) + CreateNonMembershipProof(key []byte) (*ics23.CommitmentProof, error) Set(key, value []byte) error Delete(key []byte) error - Root() []byte GetCommitmentPrefix() coreTypes.CommitmentPrefix - CreateMembershipProof(key, value []byte) (*ics23.CommitmentProof, error) - CreateNonMembershipProof(key []byte) (*ics23.CommitmentProof, error) + Root() ics23.CommitmentRoot + CacheEntries(kvstore.KVStore) error + PruneCache(store kvstore.KVStore, height uint64) error + RestoreCache(kvstore.KVStore) error } From d27211d0d4333b1f70613f896e6ddb7cd7952e36 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 15:42:34 +0100 Subject: [PATCH 65/74] Add storesDir to IBC config and pass to host and storemanger --- build/config/config.validator1.json | 3 ++- build/config/config.validator2.json | 3 ++- build/config/config.validator3.json | 3 ++- build/config/config.validator4.json | 3 ++- ibc/host.go | 4 +++- ibc/module.go | 5 ++++- ibc/store/store_manager.go | 9 +++++---- runtime/configs/config.go | 3 ++- runtime/configs/proto/ibc_config.proto | 1 + runtime/defaults/defaults.go | 3 ++- runtime/manager_test.go | 1 + shared/modules/ibc_module.go | 2 +- 12 files changed, 27 insertions(+), 13 deletions(-) diff --git a/build/config/config.validator1.json b/build/config/config.validator1.json index 746e74881..888d137bb 100644 --- a/build/config/config.validator1.json +++ b/build/config/config.validator1.json @@ -60,6 +60,7 @@ }, "ibc": { "enabled": true, - "private_key": "0ca1a40ddecdab4f5b04fa0bfed1d235beaa2b8082e7554425607516f0862075dfe357de55649e6d2ce889acf15eb77e94ab3c5756fe46d3c7538d37f27f115e" + "private_key": "0ca1a40ddecdab4f5b04fa0bfed1d235beaa2b8082e7554425607516f0862075dfe357de55649e6d2ce889acf15eb77e94ab3c5756fe46d3c7538d37f27f115e", + "stores_dir": "/var/ibc" } } diff --git a/build/config/config.validator2.json b/build/config/config.validator2.json index 052d8eeb7..aaf37c8de 100644 --- a/build/config/config.validator2.json +++ b/build/config/config.validator2.json @@ -53,6 +53,7 @@ }, "ibc": { "enabled": true, - "private_key": "0ca1a40ddecdab4f5b04fa0bfed1d235beaa2b8082e7554425607516f0862075dfe357de55649e6d2ce889acf15eb77e94ab3c5756fe46d3c7538d37f27f115e" + "private_key": "0ca1a40ddecdab4f5b04fa0bfed1d235beaa2b8082e7554425607516f0862075dfe357de55649e6d2ce889acf15eb77e94ab3c5756fe46d3c7538d37f27f115e", + "stores_dir": "/var/ibc" } } diff --git a/build/config/config.validator3.json b/build/config/config.validator3.json index 834a72ff2..86611bf12 100644 --- a/build/config/config.validator3.json +++ b/build/config/config.validator3.json @@ -53,6 +53,7 @@ }, "ibc": { "enabled": true, - "private_key": "0ca1a40ddecdab4f5b04fa0bfed1d235beaa2b8082e7554425607516f0862075dfe357de55649e6d2ce889acf15eb77e94ab3c5756fe46d3c7538d37f27f115e" + "private_key": "0ca1a40ddecdab4f5b04fa0bfed1d235beaa2b8082e7554425607516f0862075dfe357de55649e6d2ce889acf15eb77e94ab3c5756fe46d3c7538d37f27f115e", + "stores_dir": "/var/ibc" } } diff --git a/build/config/config.validator4.json b/build/config/config.validator4.json index df37cb69a..903a8dba0 100644 --- a/build/config/config.validator4.json +++ b/build/config/config.validator4.json @@ -53,6 +53,7 @@ }, "ibc": { "enabled": true, - "private_key": "0ca1a40ddecdab4f5b04fa0bfed1d235beaa2b8082e7554425607516f0862075dfe357de55649e6d2ce889acf15eb77e94ab3c5756fe46d3c7538d37f27f115e" + "private_key": "0ca1a40ddecdab4f5b04fa0bfed1d235beaa2b8082e7554425607516f0862075dfe357de55649e6d2ce889acf15eb77e94ab3c5756fe46d3c7538d37f27f115e", + "stores_dir": "/var/ibc" } } diff --git a/ibc/host.go b/ibc/host.go index db2f791d3..3218a554f 100644 --- a/ibc/host.go +++ b/ibc/host.go @@ -15,7 +15,9 @@ import ( var _ modules.IBCHost = &host{} type host struct { - logger *modules.Logger + logger *modules.Logger + storesDir string + storeManager modules.IBCStoreManager } // GetTimestamp returns the current unix timestamp diff --git a/ibc/module.go b/ibc/module.go index d74766661..0629716f4 100644 --- a/ibc/module.go +++ b/ibc/module.go @@ -5,6 +5,7 @@ import ( "fmt" "sync" + "github.com/pokt-network/pocket/ibc/store" ibcTypes "github.com/pokt-network/pocket/ibc/types" "github.com/pokt-network/pocket/logger" "github.com/pokt-network/pocket/runtime/configs" @@ -149,7 +150,9 @@ func (m *ibcModule) newHost() error { return coreTypes.ErrIBCHostAlreadyExists() } host := &host{ - logger: m.logger, + logger: m.logger, + storesDir: m.cfg.StoresDir, + storeManager: store.NewStoreManager(m.cfg.StoresDir), } m.host = host return nil diff --git a/ibc/store/store_manager.go b/ibc/store/store_manager.go index ba520652e..62edc0caa 100644 --- a/ibc/store/store_manager.go +++ b/ibc/store/store_manager.go @@ -17,12 +17,13 @@ var ( // storeManager holds an in-memory map of all the provable stores in use type storeManager struct { m sync.Mutex + bus modules.Bus storesDir string stores map[string]*provableStore } -// newStoreManager returns a new storeManager instance -func newStoreManager(storesDir string) *storeManager { +// NewStoreManager returns a new storeManager instance +func NewStoreManager(storesDir string) *storeManager { return &storeManager{ m: sync.Mutex{}, storesDir: storesDir, @@ -32,13 +33,13 @@ func newStoreManager(storesDir string) *storeManager { // AddStore creates and adds a provableStore to the storeManager // if one of the same name does not already exist -func (s *storeManager) AddStore(name string, bus modules.Bus) error { +func (s *storeManager) AddStore(name string) error { s.m.Lock() defer s.m.Unlock() if _, ok := s.stores[name]; ok { return coreTypes.ErrIBCStoreAlreadyExists(name) } - store := newProvableStore(bus, coreTypes.CommitmentPrefix(name)) + store := newProvableStore(s.bus, coreTypes.CommitmentPrefix(name)) s.stores[store.name] = store return nil } diff --git a/runtime/configs/config.go b/runtime/configs/config.go index ee2f6f382..41df56173 100644 --- a/runtime/configs/config.go +++ b/runtime/configs/config.go @@ -160,7 +160,8 @@ func NewDefaultConfig(options ...func(*Config)) *Config { Servicer: &ServicerConfig{}, Fisherman: &FishermanConfig{}, IBC: &IBCConfig{ - Enabled: defaults.DefaultIBCEnabled, + Enabled: defaults.DefaultIBCEnabled, + StoresDir: defaults.DefaultIBCStoresDir, }, } diff --git a/runtime/configs/proto/ibc_config.proto b/runtime/configs/proto/ibc_config.proto index b83d4f406..a1e62686a 100644 --- a/runtime/configs/proto/ibc_config.proto +++ b/runtime/configs/proto/ibc_config.proto @@ -13,4 +13,5 @@ message IBCConfig { // to relay IBC packets using an IBC relayer binary bool enabled = 1; string private_key = 2; // hex encoded + string stores_dir = 3; } diff --git a/runtime/defaults/defaults.go b/runtime/defaults/defaults.go index ce02b2a05..660f49c22 100644 --- a/runtime/defaults/defaults.go +++ b/runtime/defaults/defaults.go @@ -73,7 +73,8 @@ var ( DefaultKeybaseVaultMountPath = "" // ibc - DefaultIBCEnabled = false + DefaultIBCEnabled = false + DefaultIBCStoresDir = "/var/ibc" ) var ( diff --git a/runtime/manager_test.go b/runtime/manager_test.go index 35fef8094..026ef1259 100644 --- a/runtime/manager_test.go +++ b/runtime/manager_test.go @@ -1826,6 +1826,7 @@ func TestNewManagerFromReaders(t *testing.T) { IBC: &configs.IBCConfig{ Enabled: true, PrivateKey: "0ca1a40ddecdab4f5b04fa0bfed1d235beaa2b8082e7554425607516f0862075dfe357de55649e6d2ce889acf15eb77e94ab3c5756fe46d3c7538d37f27f115e", + StoresDir: defaults.DefaultIBCStoresDir, }, }, genesisState: expectedGenesis, diff --git a/shared/modules/ibc_module.go b/shared/modules/ibc_module.go index b03835467..638e1f6dd 100644 --- a/shared/modules/ibc_module.go +++ b/shared/modules/ibc_module.go @@ -209,7 +209,7 @@ type IBCHandler interface { // IBCStoreManager manages the different ProvableStore instances created by the IBC host type IBCStoreManager interface { - AddStore(name string, bus Bus) error + AddStore(name string) error GetStore(name string) (ProvableStore, error) RemoveStore(name string) error CacheAllEntries() error From d47619102c5bf97538f510f2e17c68eabab24bb4 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 15:48:43 +0100 Subject: [PATCH 66/74] golint error --- ibc/docs/ics24.md | 2 +- ibc/store/provable_store.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ibc/docs/ics24.md b/ibc/docs/ics24.md index 49edfddf3..a4e358aca 100644 --- a/ibc/docs/ics24.md +++ b/ibc/docs/ics24.md @@ -15,7 +15,7 @@ ## Overview -[ICS-24][ics24] details the requirements of the host chain, in order for it to be compatible with IBC. A host is defined as a node on a chain that runs the IBC software. A host has the ability to create connections with counterparty chains, open channels, and ports as well as commit proofs to the consensus state of its own chain for the relayer to submit to another chain. The host is responsible to managing and creating clients and all other aspects of the IBC module. +[ICS-24][ics24] details the requirements of the host chain, in order for it to be compatible with IBC. A host is defined as a node on a chain that runs the IBC software. A host has the ability to create connections with counterparty chains, open channels, expose ports, and commit proofs to the consensus state of its own chain for the relayer to submit to another chain. The host is responsible to managing and creating clients and all other aspects of the IBC module. As token transfers as defined in [ICS-20][ics20] work on a lock and mint pattern, any tokens sent from **chain A** to **chain B** will have a denomination unique to the connection/channel/port combination that the packet was sent over. This means that if a host where to shutdown a connection or channel without warning any tokens yet to be returned to the host chain would be lost. For this reason, only validator nodes are able to become hosts, as they provide the most reliability out of the different node types. diff --git a/ibc/store/provable_store.go b/ibc/store/provable_store.go index ac69698d8..f0866c85b 100644 --- a/ibc/store/provable_store.go +++ b/ibc/store/provable_store.go @@ -28,7 +28,7 @@ type cachedEntry struct { } // prepare returns the key and value to be written to disk -func (c *cachedEntry) prepare() (key []byte, value []byte) { +func (c *cachedEntry) prepare() (key, value []byte) { return []byte(fmt.Sprintf("%s/%d/%s", c.storeName, c.height, string(c.prefixedKey))), c.value } From 9cb2bddec81c6358c263ba3f10a4cb6a65e650c5 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 20:09:12 +0100 Subject: [PATCH 67/74] Reorganise --- ibc/handle_message_test.go | 18 +++++++++--------- ibc/module.go | 2 +- ibc/{host => path}/identifiers.go | 2 +- ibc/{host => path}/keys.go | 2 +- ibc/{host => path}/keys_ics02.go | 2 +- ibc/{host => path}/keys_ics03.go | 2 +- ibc/{host => path}/keys_ics04.go | 2 +- ibc/{host => path}/keys_ics05.go | 2 +- ibc/{host => path}/path_test.go | 2 +- ibc/{host => path}/prefix.go | 2 +- ibc/store/provable_store.go | 4 ++-- ibc/{messages.go => types/convert.go} | 25 ++++++++++++------------- 12 files changed, 32 insertions(+), 33 deletions(-) rename ibc/{host => path}/identifiers.go (99%) rename ibc/{host => path}/keys.go (99%) rename ibc/{host => path}/keys_ics02.go (99%) rename ibc/{host => path}/keys_ics03.go (99%) rename ibc/{host => path}/keys_ics04.go (99%) rename ibc/{host => path}/keys_ics05.go (98%) rename ibc/{host => path}/path_test.go (99%) rename ibc/{host => path}/prefix.go (98%) rename ibc/{messages.go => types/convert.go} (57%) diff --git a/ibc/handle_message_test.go b/ibc/handle_message_test.go index b547d2a87..7bb81af20 100644 --- a/ibc/handle_message_test.go +++ b/ibc/handle_message_test.go @@ -51,27 +51,27 @@ func TestHandleMessage_BasicValidation_Message(t *testing.T) { }{ { name: "Valid Update Message", - msg: CreateUpdateStoreMessage([]byte("key"), []byte("value")), + msg: ibcTypes.CreateUpdateStoreMessage([]byte("key"), []byte("value")), expected: nil, }, { name: "Valid Prune Message", - msg: CreatePruneStoreMessage([]byte("key")), + msg: ibcTypes.CreatePruneStoreMessage([]byte("key")), expected: nil, }, { name: "Invalid Update Message: Empty Key", - msg: CreateUpdateStoreMessage(nil, []byte("value")), + msg: ibcTypes.CreateUpdateStoreMessage(nil, []byte("value")), expected: coreTypes.ErrNilField("key"), }, { name: "Invalid Update Message: Empty Value", - msg: CreateUpdateStoreMessage([]byte("key"), nil), + msg: ibcTypes.CreateUpdateStoreMessage([]byte("key"), nil), expected: coreTypes.ErrNilField("value"), }, { name: "Invalid Prune Message: Empty Key", - msg: CreatePruneStoreMessage(nil), + msg: ibcTypes.CreatePruneStoreMessage(nil), expected: coreTypes.ErrNilField("key"), }, } @@ -285,16 +285,16 @@ func TestHandleMessage_AddToMempool(t *testing.T) { func prepareUpdateMessage(t *testing.T, key, value []byte) (*ibcTypes.IBCMessage, *coreTypes.Transaction) { t.Helper() - msg := CreateUpdateStoreMessage(key, value) - tx, err := ConvertIBCMessageToTx(msg) + msg := ibcTypes.CreateUpdateStoreMessage(key, value) + tx, err := ibcTypes.ConvertIBCMessageToTx(msg) require.NoError(t, err) return msg, tx } func preparePruneMessage(t *testing.T, key []byte) (*ibcTypes.IBCMessage, *coreTypes.Transaction) { t.Helper() - msg := CreatePruneStoreMessage(key) - tx, err := ConvertIBCMessageToTx(msg) + msg := ibcTypes.CreatePruneStoreMessage(key) + tx, err := ibcTypes.ConvertIBCMessageToTx(msg) require.NoError(t, err) return msg, tx } diff --git a/ibc/module.go b/ibc/module.go index 0629716f4..e7a423636 100644 --- a/ibc/module.go +++ b/ibc/module.go @@ -105,7 +105,7 @@ func (m *ibcModule) HandleMessage(message *anypb.Any) error { } // Convert IBC message to a utility Transaction - tx, err := ConvertIBCMessageToTx(ibcMessage) + tx, err := ibcTypes.ConvertIBCMessageToTx(ibcMessage) if err != nil { return err } diff --git a/ibc/host/identifiers.go b/ibc/path/identifiers.go similarity index 99% rename from ibc/host/identifiers.go rename to ibc/path/identifiers.go index f6b4a046d..283edc8aa 100644 --- a/ibc/host/identifiers.go +++ b/ibc/path/identifiers.go @@ -1,4 +1,4 @@ -package host +package path import ( "fmt" diff --git a/ibc/host/keys.go b/ibc/path/keys.go similarity index 99% rename from ibc/host/keys.go rename to ibc/path/keys.go index c5a62ff34..1cd8a4de8 100644 --- a/ibc/host/keys.go +++ b/ibc/path/keys.go @@ -1,4 +1,4 @@ -package host +package path import ( "fmt" diff --git a/ibc/host/keys_ics02.go b/ibc/path/keys_ics02.go similarity index 99% rename from ibc/host/keys_ics02.go rename to ibc/path/keys_ics02.go index a114f2bf4..5cce681be 100644 --- a/ibc/host/keys_ics02.go +++ b/ibc/path/keys_ics02.go @@ -1,4 +1,4 @@ -package host +package path import "fmt" diff --git a/ibc/host/keys_ics03.go b/ibc/path/keys_ics03.go similarity index 99% rename from ibc/host/keys_ics03.go rename to ibc/path/keys_ics03.go index 505d9e0e2..99e56a48a 100644 --- a/ibc/host/keys_ics03.go +++ b/ibc/path/keys_ics03.go @@ -1,4 +1,4 @@ -package host +package path import "fmt" diff --git a/ibc/host/keys_ics04.go b/ibc/path/keys_ics04.go similarity index 99% rename from ibc/host/keys_ics04.go rename to ibc/path/keys_ics04.go index 1cabd8c7c..004ad3647 100644 --- a/ibc/host/keys_ics04.go +++ b/ibc/path/keys_ics04.go @@ -1,4 +1,4 @@ -package host +package path import ( "fmt" diff --git a/ibc/host/keys_ics05.go b/ibc/path/keys_ics05.go similarity index 98% rename from ibc/host/keys_ics05.go rename to ibc/path/keys_ics05.go index 9ff1f1448..f6c807f9a 100644 --- a/ibc/host/keys_ics05.go +++ b/ibc/path/keys_ics05.go @@ -1,4 +1,4 @@ -package host +package path import "fmt" diff --git a/ibc/host/path_test.go b/ibc/path/path_test.go similarity index 99% rename from ibc/host/path_test.go rename to ibc/path/path_test.go index 1c1c8d367..96d47f039 100644 --- a/ibc/host/path_test.go +++ b/ibc/path/path_test.go @@ -1,4 +1,4 @@ -package host +package path import ( "testing" diff --git a/ibc/host/prefix.go b/ibc/path/prefix.go similarity index 98% rename from ibc/host/prefix.go rename to ibc/path/prefix.go index 86510bbfd..ebeddab07 100644 --- a/ibc/host/prefix.go +++ b/ibc/path/prefix.go @@ -1,4 +1,4 @@ -package host +package path import ( coreTypes "github.com/pokt-network/pocket/shared/core/types" diff --git a/ibc/store/provable_store.go b/ibc/store/provable_store.go index f0866c85b..366013d06 100644 --- a/ibc/store/provable_store.go +++ b/ibc/store/provable_store.go @@ -7,7 +7,7 @@ import ( "strings" ics23 "github.com/cosmos/ics23/go" - "github.com/pokt-network/pocket/ibc/host" + "github.com/pokt-network/pocket/ibc/path" "github.com/pokt-network/pocket/persistence/kvstore" coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/modules" @@ -240,5 +240,5 @@ func applyPrefix(prefix coreTypes.CommitmentPrefix, key []byte) coreTypes.Commit if bytes.Equal(prefix[:len(slashed)], slashed) { return key } - return host.ApplyPrefix(prefix, string(key)) + return path.ApplyPrefix(prefix, string(key)) } diff --git a/ibc/messages.go b/ibc/types/convert.go similarity index 57% rename from ibc/messages.go rename to ibc/types/convert.go index ae810c31f..02f205f65 100644 --- a/ibc/messages.go +++ b/ibc/types/convert.go @@ -1,19 +1,18 @@ -package ibc +package types import ( "fmt" - "github.com/pokt-network/pocket/ibc/types" "github.com/pokt-network/pocket/shared/codec" coreTypes "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/crypto" "google.golang.org/protobuf/types/known/anypb" ) -func CreateUpdateStoreMessage(key, value []byte) *types.IBCMessage { - return &types.IBCMessage{ - Event: &types.IBCMessage_Update{ - Update: &types.UpdateIBCStore{ +func CreateUpdateStoreMessage(key, value []byte) *IBCMessage { + return &IBCMessage{ + Event: &IBCMessage_Update{ + Update: &UpdateIBCStore{ Key: key, Value: value, }, @@ -21,23 +20,23 @@ func CreateUpdateStoreMessage(key, value []byte) *types.IBCMessage { } } -func CreatePruneStoreMessage(key []byte) *types.IBCMessage { - return &types.IBCMessage{ - Event: &types.IBCMessage_Prune{ - Prune: &types.PruneIBCStore{ +func CreatePruneStoreMessage(key []byte) *IBCMessage { + return &IBCMessage{ + Event: &IBCMessage_Prune{ + Prune: &PruneIBCStore{ Key: key, }, }, } } -func ConvertIBCMessageToTx(ibcMessage *types.IBCMessage) (*coreTypes.Transaction, error) { +func ConvertIBCMessageToTx(ibcMessage *IBCMessage) (*coreTypes.Transaction, error) { var anyMsg *anypb.Any var err error switch event := ibcMessage.Event.(type) { - case *types.IBCMessage_Update: + case *IBCMessage_Update: anyMsg, err = codec.GetCodec().ToAny(event.Update) - case *types.IBCMessage_Prune: + case *IBCMessage_Prune: anyMsg, err = codec.GetCodec().ToAny(event.Prune) default: return nil, coreTypes.ErrIBCUnknownMessageType(fmt.Sprintf("%T", event)) From 6b74a5fbee9712e65559dc6c15bb2185860ce633 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 20:40:48 +0100 Subject: [PATCH 68/74] Update docs on data retrieval --- ibc/docs/ics24.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/ibc/docs/ics24.md b/ibc/docs/ics24.md index a4e358aca..0df83a951 100644 --- a/ibc/docs/ics24.md +++ b/ibc/docs/ics24.md @@ -8,6 +8,7 @@ - [Timestamps](#timestamps) - [IBC State](#ibc-state) - [IBC State Tree](#ibc-state-tree) + - [Data Retrieval](#data-retrieval) - [IBC Messages](#ibc-messages) - [IBC Message Handling](#ibc-message-handling) - [Mempool](#mempool) @@ -26,7 +27,8 @@ Only validators can be configured to be IBC hosts. If the IBC module, during its ```json "ibc": { "enabled": bool, - "private_key": string + "private_key": string, + "stores_dir": string } ``` @@ -86,9 +88,9 @@ See: [IBC State](#ibc-state) below for more details on the IBC state transition ### Paths and Identifiers -Paths are defined as bytestrings that are used to access the elements in the different stores. They are built with the function `ApplyPrefix()` which takes a store key as a prefix and a path string and will return the key to access an element in the specific store. The logic for paths can be found in [host/keys.go](../host/keys.go) and [host/prefix.go](../host/prefix.go) +Paths are defined as bytestrings that are used to access the elements in the different stores. They are built with the function `ApplyPrefix()` which takes a store key as a prefix and a path string and will return the key to access an element in the specific store. The logic for paths can be found in [path/keys.go](../path/keys.go) and [path/prefix.go](../path/prefix.go) -Identifiers are bytestrings constrained to specific characters and lengths depending on their usages. They are used to identify: channels, clients, connections and ports. Although the minimum length of the identifiers is much less we use a minimum length of 32 bytes and a maximum length that varies depending on the use case to randomly generate identifiers. This allows for an extremely low chance of collision between identifiers. Identifiers have no significance beyond their use to store different elements in the IBC stores and as such there is no need for non-random identifiers. The logic for identifiers can be found in [host/identifiers.go](../host/identifiers.go). +Identifiers are bytestrings constrained to specific characters and lengths depending on their usages. They are used to identify: channels, clients, connections and ports. Although the minimum length of the identifiers is much less we use a minimum length of 32 bytes and a maximum length that varies depending on the use case to randomly generate identifiers. This allows for an extremely low chance of collision between identifiers. Identifiers have no significance beyond their use to store different elements in the IBC stores and as such there is no need for non-random identifiers. The logic for identifiers can be found in [path/identifiers.go](../path/identifiers.go). ### Timestamps @@ -100,7 +102,15 @@ As mentioned [above](#persistence) the IBC store **MUST** be included in the con ### IBC State Tree -The IBC state tree is a non-value hashing `SMT` backed by a persistent `KVStore`, this is due to its need for data retrieval as well as proof generation/verification. The root hash of the IBC state tree is included in the `rootTree` which computes the networks state hash for any given block. This allows verifiers to not only verify the inclusion/exclusion of any element in the IBC state tree itself but also that the IBC state tree was used to compute the network's state hash, by utilising the `CommitmentProof` object defined in [ICS-23][ics23]. +The IBC state tree is an `SMT` backed by a persistent `KVStore`, this is used for proof generation/verification. Data retrieval uses the `peristence` layer, see the [data retrieval](#data-retrieval) section below for more details. + +The root hash of the IBC state tree is included in the `rootTree` which computes the network's state hash for any given block. This allows verifiers to not only verify the inclusion/exclusion of any element in the IBC state tree itself but also that the IBC state tree was used to compute the network's state hash, by utilising the `CommitmentProof` object defined in [ICS-23][ics23]. + +### Data Retrieval + +In order to query the IBC store the `persistence` layer is leveraged. All local changes to the IBC store are broadcasted as [IBC messages](#ibc-messages) and ultimately stored in each node's `peristence` layer. This allows for the efficient querying of the IBC store instead of having to query the IBC state tree directly. + +When attempting to generate a proof for a specific `key` in the IBC state tree the IBC host will import a local copy of the IBC state tree and use this to generate the proof. Otherwise all queries are handled by the `peristence` layer's underlying database. ### IBC Messages From 9ba683a0349f435b951bc6d4f0baf264b240b747 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 29 Jun 2023 20:55:06 +0100 Subject: [PATCH 69/74] Update docs on provable stores and caching --- ibc/docs/README.md | 4 +++- ibc/docs/ics24.md | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/ibc/docs/README.md b/ibc/docs/README.md index 01a04c4ed..28160a08b 100644 --- a/ibc/docs/README.md +++ b/ibc/docs/README.md @@ -95,7 +95,9 @@ _Note_: Connections, Channels and Ports in IBC are not the same as networking co [ICS24][ics24] defines the IBC stores and these must be a part of the Pocket networks consensus state. As such the `ibcTree` is defined as one of the state trees used to generate the root hash. This tree contains the relevant information the hosts/relayers need to be able to use IBC, in accordance with ICS-24 and the other ICS components. -TODO([#854](https://github.com/pokt-network/pocket/issues/854)): Add a local cache for changes to the state for use in the event of the node crashing. +In order to interact with the IBC store's the host must create a `ProvableStore` instance which can make local changes to the state and propagate these through the network. This store maintains a local cache that can be backed up to disk and restored. In the event of a node failure, or local changes being unable to be propagated, the cache can be restored from the disk backup and the host can attempt to propagate the changes again. + +See: [store/provable_store.go](../store/provable_store.go) and [ics24.md](ics24.md) for more details on the specifics of the IBC store implementation for Pocket. ## Components diff --git a/ibc/docs/ics24.md b/ibc/docs/ics24.md index 0df83a951..256294deb 100644 --- a/ibc/docs/ics24.md +++ b/ibc/docs/ics24.md @@ -13,6 +13,8 @@ - [IBC Message Handling](#ibc-message-handling) - [Mempool](#mempool) - [State Transition](#state-transition) +- [Provable Stores](#provable-stores) + - [Caching](#caching) ## Overview @@ -173,6 +175,22 @@ With the `IBCMessage` now propagated through the network's mempool, when it is r See: [PROTOCOL_STATE_HASH.md](../../persistence/docs/PROTOCOL_STATE_HASH.md#ibc-state-tree) for more information on how the persistence module uses the data it has tracked from the `IBCMessage` objects, in order to update the actual state trees and in turn the root hash. +## Provable Stores + +The `ProvableStore` interface defined in [shared/modules/ibc_module.go](../../shared/modules/ibc_module.go) is implemented by the [`provableStore`](../store/provable_store.go) type and managed by the [`StoreManager`](../store/store_manager.go). + +The provable stores are each assigned a `prefix`. This represents the specific sub-store that they are able to access and interact with in the IBC state tree. When doing any operation `get`/`set`/`delete` the `prefix` is applied to the `key` provided to generate the `CommitmentPath` to the element in the IBC state tree. + +The provable stores do not directly interface with the IBC state tree but instead utliise the `peristence` layer to query the data locally. This allows for the efficient querying of the IBC store instead of having to query the IBC state tree directly. Any changes made by the `ProvableStore` instance are broadcasted to the network for inclusion in the next block, being stored in their mempools. + +### Caching + +Every local change made to the IBC store (`update`/`delete`) is stored in an in-memory cache. These caches are written to disk by the [`StoreManager`](../store/store_manager.go). + +In the event of a node failure, or local changes not being propagated correctly. Any changes stored in the cache can be "replayed" by the node and broadcasted to the network for inclusion in the next block. + +_TODO: Implement this functionality_ + [ics24]: https://github.com/cosmos/ibc/blob/main/spec/core/ics-024-host-requirements/README.md [ics20]: https://github.com/cosmos/ibc/blob/main/spec/app/ics-020-fungible-token-transfer/README.md [smt]: https://github.com/pokt-network/smt From 5bb0adf24c03dec7405fd323b33cc5e512f23a84 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Fri, 30 Jun 2023 09:50:03 +0100 Subject: [PATCH 70/74] Prefix errors --- shared/core/types/error.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shared/core/types/error.go b/shared/core/types/error.go index 76d7686f0..162a9c42d 100644 --- a/shared/core/types/error.go +++ b/shared/core/types/error.go @@ -177,11 +177,11 @@ const ( CodeUnknownActorType Code = 130 CodeUnknownMessageType Code = 131 CodeProposalBlockNotSet Code = 133 - CodeHostAlreadyExists Code = 138 - CodeHostDoesNotExist Code = 139 + CodeIBCHostAlreadyExists Code = 138 + CodeIBCHostDoesNotExist Code = 139 CodeIBCInvalidID Code = 140 CodeIBCInvalidPath Code = 141 - CodeCreatingProofError Code = 142 + CodeIBCCreatingProofError Code = 142 CodeIBCUnknownMessageTypeError Code = 143 CodeNilFieldError Code = 144 CodeIBCUpdatingStoreError Code = 145 @@ -868,11 +868,11 @@ func ErrUnknownMessageType(messageType any) Error { } func ErrIBCHostAlreadyExists() Error { - return NewError(CodeHostAlreadyExists, IBCHostAlreadyExistsError) + return NewError(CodeIBCHostAlreadyExists, IBCHostAlreadyExistsError) } func ErrIBCHostDoesNotExist() Error { - return NewError(CodeHostDoesNotExist, IBCHostDoesNotExistError) + return NewError(CodeIBCHostDoesNotExist, IBCHostDoesNotExistError) } func ErrIBCInvalidID(identifier, msg string) Error { @@ -884,7 +884,7 @@ func ErrIBCInvalidPath(path string) Error { } func ErrIBCCreatingProof(err error) Error { - return NewError(CodeCreatingProofError, fmt.Sprintf("%s: %s", IBCCreatingProofError, err.Error())) + return NewError(CodeIBCCreatingProofError, fmt.Sprintf("%s: %s", IBCCreatingProofError, err.Error())) } func ErrIBCUnknownMessageType(messageType string) Error { From e94f4a97ec8f43598e08c192d9f0b4720748aa75 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Fri, 30 Jun 2023 10:13:18 +0100 Subject: [PATCH 71/74] linter error --- shared/modules/persistence_module.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/modules/persistence_module.go b/shared/modules/persistence_module.go index 4fef44f32..acfea1473 100644 --- a/shared/modules/persistence_module.go +++ b/shared/modules/persistence_module.go @@ -147,7 +147,7 @@ type PersistenceWriteContext interface { // IBC Operations SetIBCStoreEntry(key, value []byte) error - // Relay Operations + // Relay Operations RecordRelayService(applicationAddress string, key []byte, relay *coreTypes.Relay, response *coreTypes.RelayResponse) error } From e6b53fea1f2ae1312c32da716dd635303d6eb79c Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Tue, 4 Jul 2023 10:37:20 +0100 Subject: [PATCH 72/74] Address comments --- ibc/host.go | 2 +- ibc/module.go | 2 +- ibc/store/provable_store.go | 52 +++++++++++++++++----------- ibc/store/store_manager.go | 6 ++-- persistence/trees/trees.go | 3 +- shared/modules/ibc_module.go | 4 +-- shared/modules/persistence_module.go | 4 +++ utility/unit_of_work/gov.go | 2 +- 8 files changed, 45 insertions(+), 30 deletions(-) diff --git a/ibc/host.go b/ibc/host.go index 3218a554f..b8a415d67 100644 --- a/ibc/host.go +++ b/ibc/host.go @@ -28,7 +28,7 @@ func (h *host) GetTimestamp() uint64 { // GetProvableStore returns a copy of the IBC state tree where all operations observed by // this specific ibc light client were applied to its ephemeral (in memory) state and have not // yet been included in the next block. The aggregation of all light client-provable stores -// propagated throughout the network are happen validated by the block proposer when reaping +// propagated throughout the network are validated by the block proposer when reaping // the mempool, and lead to a valid on-chain state transition when consensus is reached. // TODO(#854): Implement this func (h *host) GetProvableStore(prefix coreTypes.CommitmentPrefix) (modules.ProvableStore, error) { diff --git a/ibc/module.go b/ibc/module.go index e7a423636..d8685059c 100644 --- a/ibc/module.go +++ b/ibc/module.go @@ -86,7 +86,7 @@ func (m *ibcModule) GetModuleName() string { return modules.IBCModuleName } -// HandleMessage accepts a generic IBC message and routes it to the specific handler +// HandleMessage accepts a generic IBC message and routes it to the utility mempool func (m *ibcModule) HandleMessage(message *anypb.Any) error { m.m.Lock() defer m.m.Unlock() diff --git a/ibc/store/provable_store.go b/ibc/store/provable_store.go index 366013d06..c2dbd3390 100644 --- a/ibc/store/provable_store.go +++ b/ibc/store/provable_store.go @@ -5,6 +5,7 @@ import ( "fmt" "strconv" "strings" + "sync" ics23 "github.com/cosmos/ics23/go" "github.com/pokt-network/pocket/ibc/path" @@ -15,11 +16,9 @@ import ( var _ modules.ProvableStore = &provableStore{} -// CachedEntry represents a local change made to the IBC store prior to it being -// committed to the state tree. These should be written to disk in the to prevent a +// cachedEntry represents a local change made to the IBC store prior to it being +// committed to the state tree. These are written to disk in the to prevent a // loss of data and pruned when included in the state tree -// written to disk as follows: -// "{height}/{prefixedKey}" => value type cachedEntry struct { storeName string height uint64 @@ -28,18 +27,21 @@ type cachedEntry struct { } // prepare returns the key and value to be written to disk +// "{height}/{prefixedKey}" => value func (c *cachedEntry) prepare() (key, value []byte) { return []byte(fmt.Sprintf("%s/%d/%s", c.storeName, c.height, string(c.prefixedKey))), c.value } -// provableStore is a struct that interfaces with the PostgresDB instance -// obtained from Persistence. It is used to Get/Set/Delete the keys in the -// IBC state tree, in doing so it will trigger the creation of +// provableStore is a struct that interfaces with the persistence layer to +// Get/Set/Delete the keys in the IBC state tree, in doing so it will trigger +// the creation of IBC messages that are broadcasted through the network and +// included in the mempool/next block to change the state of the IBC tree type provableStore struct { + m sync.Mutex bus modules.Bus // used to interact with persistence (passed from IBCHost) name string // store name in storeManager prefix coreTypes.CommitmentPrefix // []byte(name) - cache []*cachedEntry // in-memory cache of local changes to be written to disk + cache map[string]*cachedEntry // in-memory cache of local changes to be written to disk } // newProvableStore returns a new instance of provableStore with the bus and prefix provided @@ -48,7 +50,7 @@ func newProvableStore(bus modules.Bus, prefix coreTypes.CommitmentPrefix) *prova bus: bus, name: string(prefix), prefix: prefix, - cache: make([]*cachedEntry, 0), + cache: make(map[string]*cachedEntry, 0), } } @@ -134,12 +136,14 @@ func (p *provableStore) Set(key, value []byte) error { if err := rwCtx.SetIBCStoreEntry(prefixed, value); err != nil { return err } - p.cache = append(p.cache, &cachedEntry{ + p.m.Lock() + defer p.m.Unlock() + p.cache[string(prefixed)] = &cachedEntry{ storeName: p.name, height: uint64(currHeight), prefixedKey: prefixed, value: value, - }) + } // TODO(#854): Implement emit functions // return emitUpdateStoreEvent(p.prefix, key, value) return nil @@ -159,12 +163,14 @@ func (p *provableStore) Delete(key []byte) error { if err := rwCtx.SetIBCStoreEntry(prefixed, nil); err != nil { return err } - p.cache = append(p.cache, &cachedEntry{ + p.m.Lock() + defer p.m.Unlock() + p.cache[string(prefixed)] = &cachedEntry{ storeName: p.name, height: uint64(currHeight), prefixedKey: prefixed, value: nil, - }) + } // TODO(#854): Implement emit functions // return emitPruneStoreEvent(p.prefix, key) return nil @@ -183,20 +189,24 @@ func (p *provableStore) Root() ics23.CommitmentRoot { return nil } -// CacheEntries writes all local changes to disk and clears the in-memory cache -func (p *provableStore) CacheEntries(store kvstore.KVStore) error { +// FlushEntries writes all local changes to disk and clears the in-memory cache +func (p *provableStore) FlushEntries(store kvstore.KVStore) error { + p.m.Lock() + defer p.m.Unlock() for _, entry := range p.cache { key, value := entry.prepare() if err := store.Set(key, value); err != nil { return err } + delete(p.cache, string(entry.prefixedKey)) } - p.cache = make([]*cachedEntry, 0) return nil } // PruneCache removes all entries from the cache at the given height func (p *provableStore) PruneCache(store kvstore.KVStore, height uint64) error { + p.m.Lock() + defer p.m.Unlock() keys, _, err := store.GetAll([]byte(fmt.Sprintf("%s/%d", p.name, height)), false) if err != nil { return err @@ -211,23 +221,25 @@ func (p *provableStore) PruneCache(store kvstore.KVStore, height uint64) error { // RestoreCache loads all entries from disk into the cache func (p *provableStore) RestoreCache(store kvstore.KVStore) error { + p.m.Lock() + defer p.m.Unlock() keys, values, err := store.GetAll(p.prefix, false) if err != nil { return err } - for i := 0; i < len(keys); i++ { - parts := strings.SplitN(string(keys[i]), "/", 2) // name, heightStr, prefixedKeyStr + for i, key := range keys { + parts := strings.SplitN(string(key), "/", 2) // name, heightStr, prefixedKeyStr height, err := strconv.ParseUint(parts[1], 10, 64) if err != nil { return err } value := values[i] - p.cache = append(p.cache, &cachedEntry{ + p.cache[parts[2]] = &cachedEntry{ storeName: parts[0], height: height, prefixedKey: []byte(parts[2]), value: value, - }) + } } return nil } diff --git a/ibc/store/store_manager.go b/ibc/store/store_manager.go index 62edc0caa..e7b028e6b 100644 --- a/ibc/store/store_manager.go +++ b/ibc/store/store_manager.go @@ -66,8 +66,8 @@ func (s *storeManager) RemoveStore(name string) error { return nil } -// CacheAllEntries caches all the entries for all stores in the storeManager -func (s *storeManager) CacheAllEntries() error { +// FlushAllEntries caches all the entries for all stores in the storeManager +func (s *storeManager) FlushAllEntries() error { s.m.Lock() defer s.m.Unlock() disk, err := kvstore.NewKVStore(cacheDirs(s.storesDir)) @@ -75,7 +75,7 @@ func (s *storeManager) CacheAllEntries() error { return err } for _, store := range s.stores { - if err := store.CacheEntries(disk); err != nil { + if err := store.FlushEntries(disk); err != nil { return err } } diff --git a/persistence/trees/trees.go b/persistence/trees/trees.go index f12b0871c..74fe6599c 100644 --- a/persistence/trees/trees.go +++ b/persistence/trees/trees.go @@ -355,8 +355,7 @@ func (t *treeStore) updateIBCTree(keys, values [][]byte) error { if len(keys) != len(values) { return fmt.Errorf("keys and values must be the same length") } - for i := 0; i < len(keys); i++ { - key := keys[i] + for i, key := range keys { value := values[i] if value == nil { if err := t.merkleTrees[IBCTreeName].tree.Delete(key); err != nil { diff --git a/shared/modules/ibc_module.go b/shared/modules/ibc_module.go index 638e1f6dd..fb8f76129 100644 --- a/shared/modules/ibc_module.go +++ b/shared/modules/ibc_module.go @@ -212,7 +212,7 @@ type IBCStoreManager interface { AddStore(name string) error GetStore(name string) (ProvableStore, error) RemoveStore(name string) error - CacheAllEntries() error + FlushAllEntries() error PruneCaches(height uint64) error RestoreCaches() error } @@ -228,7 +228,7 @@ type ProvableStore interface { Delete(key []byte) error GetCommitmentPrefix() coreTypes.CommitmentPrefix Root() ics23.CommitmentRoot - CacheEntries(kvstore.KVStore) error + FlushEntries(kvstore.KVStore) error PruneCache(store kvstore.KVStore, height uint64) error RestoreCache(kvstore.KVStore) error } diff --git a/shared/modules/persistence_module.go b/shared/modules/persistence_module.go index acfea1473..55e338fa5 100644 --- a/shared/modules/persistence_module.go +++ b/shared/modules/persistence_module.go @@ -145,6 +145,9 @@ type PersistenceWriteContext interface { SetFlag(paramName string, value any, enabled bool) error // IBC Operations + // SetIBCStoreEntry sets the key-value pair in the ibc_entries table at the current height the + // key-value pairs represent the same key-value pairings in the IBC state tree. This table is + // used for data retrieval purposes and to update the state tree from the mempool of IBC transactions. SetIBCStoreEntry(key, value []byte) error // Relay Operations @@ -241,6 +244,7 @@ type PersistenceReadContext interface { GetBytesFlag(paramName string, height int64) ([]byte, bool, error) // IBC Queries + // GetIBCStoreEntry returns the value of the key at the given height from the ibc_entries table GetIBCStoreEntry(key []byte, height int64) ([]byte, error) } diff --git a/utility/unit_of_work/gov.go b/utility/unit_of_work/gov.go index 0c71c9418..29a561a47 100644 --- a/utility/unit_of_work/gov.go +++ b/utility/unit_of_work/gov.go @@ -150,7 +150,7 @@ func (u *baseUtilityUnitOfWork) getParamOwner(paramName string) ([]byte, coreTyp func (u *baseUtilityUnitOfWork) getFee(msg typesUtil.Message, actorType coreTypes.ActorType) (amount *big.Int, err coreTypes.Error) { switch x := msg.(type) { - // TECHDEBT(M6): Decide on IBC store change fees and move into a governance parameter + // TECHDEBT(M6): Decide on IBC store tx fees and move into a governance parameter case *typesUtil.MessageSend, *ibcTypes.UpdateIBCStore, *ibcTypes.PruneIBCStore: return getGovParam[*big.Int](u, typesUtil.MessageSendFee) case *typesUtil.MessageStake: From 733f53ba4188339f28d92fca04e8dcf063215e0b Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Tue, 11 Jul 2023 11:58:37 +0100 Subject: [PATCH 73/74] merge: squash and merge main --- .github/PULL_REQUEST_TEMPLATE.md | 3 +- .github/workflows/golangci-lint.yml | 40 ++ .github/workflows/main.yml | 10 - .golangci.yml | 4 +- Makefile | 14 +- README.md | 2 +- app/client/cli/consensus.go | 1 + app/client/cli/debug.go | 4 +- app/client/cli/helpers/setup.go | 20 +- build/linters/blockers.go | 19 + build/linters/errs.go | 2 +- build/linters/tests.go | 2 +- build/localnet/Tiltfile | 7 +- consensus/events.go | 2 +- consensus/leader_election/module.go | 2 +- consensus/module.go | 4 +- consensus/pacemaker/module.go | 2 +- docs/devlog/devlog10.md | 154 +++++ ibc/module.go | 2 +- ibc/store/proofs_ics23.go | 1 - logger/module.go | 2 +- p2p/README.md | 575 +++++++++++++++--- p2p/background/router.go | 370 +++++++++-- p2p/background/router_test.go | 217 +++++-- p2p/bootstrap.go | 14 +- p2p/config/config.go | 18 +- p2p/event_handler.go | 35 +- p2p/module.go | 212 +++++-- p2p/module_raintree_test.go | 36 +- p2p/module_test.go | 8 +- p2p/protocol/protocol.go | 18 +- .../current_height_provider.go | 2 +- .../current_height_provider/rpc/provider.go | 4 +- .../peerstore_provider/peerstore_provider.go | 4 +- .../persistence/provider.go | 8 +- .../peerstore_provider/rpc/provider.go | 56 +- p2p/raintree/peers_manager_test.go | 12 +- p2p/raintree/router.go | 58 +- p2p/raintree/router_test.go | 2 + p2p/testutil.go | 48 ++ p2p/transport_encryption_test.go | 12 +- p2p/types/errors.go | 9 +- p2p/types/proto/background.proto | 13 + p2p/{raintree => }/types/proto/raintree.proto | 0 p2p/types/router.go | 3 +- p2p/unicast/router.go | 2 +- p2p/utils/host.go | 9 +- p2p/utils_test.go | 67 +- persistence/actor.go | 2 +- persistence/local/module.go | 4 +- persistence/module.go | 2 +- persistence/trees/trees.go | 7 +- rpc/module.go | 2 +- rpc/noop_module.go | 2 +- rpc/server.go | 6 +- runtime/bus.go | 4 +- runtime/manager.go | 2 +- runtime/modules_registry.go | 10 +- shared/messaging/envelope.go | 7 +- .../base_modules/integratable_module.go | 14 +- shared/modules/bus_module.go | 2 +- shared/modules/doc/README.md | 399 ++++++++++-- shared/modules/module.go | 30 +- shared/modules/modules_registry_module.go | 4 +- shared/modules/treestore_module.go | 2 +- shared/modules/utility_module.go | 2 +- state_machine/module.go | 4 +- telemetry/module.go | 2 +- telemetry/prometheus_module.go | 2 +- utility/doc/E2E_FEATURE_PATH_TEMPLATE.md | 18 +- utility/fisherman/module.go | 2 +- utility/module.go | 2 +- utility/servicer/module.go | 2 +- utility/unit_of_work/module.go | 2 +- utility/validator/module.go | 2 +- 75 files changed, 2155 insertions(+), 491 deletions(-) create mode 100644 .github/workflows/golangci-lint.yml create mode 100644 build/linters/blockers.go create mode 100644 docs/devlog/devlog10.md create mode 100644 p2p/testutil.go create mode 100644 p2p/types/proto/background.proto rename p2p/{raintree => }/types/proto/raintree.proto (100%) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 27adce6af..7acf0c407 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -32,7 +32,8 @@ Please mark the relevant option(s): ## List of changes - Change #1 diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml new file mode 100644 index 000000000..12355c894 --- /dev/null +++ b/.github/workflows/golangci-lint.yml @@ -0,0 +1,40 @@ +name: golangci-lint +# Copied from https://github.com/golangci/golangci-lint-action + +on: + pull_request: + types: [opened, reopened, synchronize] + +permissions: + contents: read + +env: + # Even though we can test against multiple versions, this one is considered a target version. + TARGET_GOLANG_VERSION: "1.19" + PROTOC_VERSION: "3.19.4" + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: ${{ env.TARGET_GOLANG_VERSION }} + cache: false + - name: Install Protoc + uses: arduino/setup-protoc@v1 + with: + version: ${{ env.PROTOC_VERSION }} + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: install CI dependencies + run: make install_ci_deps + - name: generate protobufs, RPC server, RPC client and mocks + run: make protogen_clean && make protogen_local && make mockgen && make generate_rpc_openapi + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: latest + args: --timeout=10m --build-tags=test + skip-cache: true diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4afc72d67..9cfe65d92 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,7 +15,6 @@ on: # - "docs/**" # - "**.md" - env: # Even though we can test against multiple versions, this one is considered a target version. TARGET_GOLANG_VERSION: "1.19" @@ -87,14 +86,6 @@ jobs: uses: codecov/codecov-action@v3 with: files: ./coverage.out - - name: golangci-lint - if: ${{ always() && env.TARGET_GOLANG_VERSION == matrix.go }} - uses: golangci/golangci-lint-action@v3 - with: - version: latest - args: --timeout=10m --build-tags=test - skip-cache: true - only-new-issues: true # TODO(@okdas): reuse artifacts built by the previous job instead # of going through the build process in container build job again @@ -153,7 +144,6 @@ jobs: build-args: | TARGET_GOLANG_VERSION=${{ env.TARGET_GOLANG_VERSION }} - # Run e2e tests on devnet if the PR has a label "e2e-devnet-test" e2e-tests: runs-on: custom-runner diff --git a/.golangci.yml b/.golangci.yml index 8981308b0..bf024354e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -7,18 +7,16 @@ linters: - govet - ineffassign - staticcheck - - typecheck - unused - # Additional linters - gosec - misspell - promlinter - unparam - goimports - # Gocritic allows to use ruleguard; custom linting rules - gocritic + linters-settings: # Gocritic settings; https://go-critic.com/overview gocritic: diff --git a/Makefile b/Makefile index 0db3d4fd0..1d1831bb0 100644 --- a/Makefile +++ b/Makefile @@ -120,13 +120,17 @@ go_fmt: ## Format all the .go files in the project in place. gofmt -w -s . .PHONY: install_cli_deps -install_cli_deps: ## Installs `protoc-gen-go`, `mockgen`, 'protoc-go-inject-tag' and other tooling +install_cli_deps: ## Installs `helm`, `tilt` and the underlying `ci_deps` + make install_ci_deps + curl -fsSL https://raw.githubusercontent.com/tilt-dev/tilt/master/scripts/install.sh | bash + curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash + +.PHONY: install_ci_deps +install_ci_deps: ## Installs `protoc-gen-go`, `mockgen`, 'protoc-go-inject-tag' and other tools necessary for CI go install "google.golang.org/protobuf/cmd/protoc-gen-go@v1.28" && protoc-gen-go --version go install "github.com/golang/mock/mockgen@v1.6.0" && mockgen --version go install "github.com/favadi/protoc-go-inject-tag@latest" go install "github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.11.0" - curl -fsSL https://raw.githubusercontent.com/tilt-dev/tilt/master/scripts/install.sh | bash - curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash .PHONY: develop_start develop_start: ## Run all of the make commands necessary to develop on the project @@ -297,7 +301,7 @@ protogen_local: go_protoc-go-inject-tag ## Generate go structures for all of the $(PROTOC_SHARED) -I=./consensus/types/proto --go_out=./consensus/types ./consensus/types/proto/*.proto # P2P - $(PROTOC_SHARED) -I=./p2p/raintree/types/proto --go_out=./p2p/types ./p2p/raintree/types/proto/*.proto + $(PROTOC_SHARED) -I=./p2p/types/proto --go_out=./p2p/types ./p2p/types/proto/*.proto # IBC $(PROTOC_SHARED) -I=./ibc/types/proto --go_out=./ibc/types ./ibc/types/proto/*.proto @@ -591,4 +595,4 @@ ggshield_secrets_scan: ## Scans the project for secrets using ggshield .PHONY: ggshield_secrets_add ggshield_secrets_add: ## A helper that adds the last results from `make ggshield_secrets_scan`, store in `.cache_ggshield` to `.gitguardian.yaml`. See `ggshield for more configuratiosn` - ggshield secret ignore --last-found \ No newline at end of file + ggshield secret ignore --last-found diff --git a/README.md b/README.md index 57ac746ec..5c324ec3a 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ If you'd like to contribute to the Pocket V1 Protocol, start by: - [Shared Architecture](shared/README.md) - [Utility Architecture](utility/doc/README.md) -- _Coming Soon: Consensus Architecture_ // TODO(olshansky): needs a README file with proper code structure +- [Consensus Architecture](consensus/README.md) - [Persistence Architecture](persistence/docs/README.md) - [P2P Architecture](p2p/README.md) - [APP Architecture](app/client/doc/README.md) diff --git a/app/client/cli/consensus.go b/app/client/cli/consensus.go index a74876949..3cb69b32d 100644 --- a/app/client/cli/consensus.go +++ b/app/client/cli/consensus.go @@ -2,6 +2,7 @@ package cli import ( "fmt" + "github.com/spf13/cobra" "github.com/pokt-network/pocket/app/client/cli/flags" diff --git a/app/client/cli/debug.go b/app/client/cli/debug.go index 07ab0cfb2..23b647933 100644 --- a/app/client/cli/debug.go +++ b/app/client/cli/debug.go @@ -228,7 +228,9 @@ func fetchPeerstore(cmd *cobra.Command) (typesP2P.Peerstore, error) { return nil, errors.New("retrieving bus from CLI context") } modulesRegistry := bus.GetModulesRegistry() - pstoreProvider, err := modulesRegistry.GetModule(peerstore_provider.ModuleName) + // TECHDEBT(#810, #811): use `bus.GetPeerstoreProvider()` after peerstore provider + // is retrievable as a proper submodule + pstoreProvider, err := modulesRegistry.GetModule(peerstore_provider.PeerstoreProviderSubmoduleName) if err != nil { return nil, errors.New("retrieving peerstore provider") } diff --git a/app/client/cli/helpers/setup.go b/app/client/cli/helpers/setup.go index 376e9348e..5f5546a91 100644 --- a/app/client/cli/helpers/setup.go +++ b/app/client/cli/helpers/setup.go @@ -27,24 +27,26 @@ func P2PDependenciesPreRunE(cmd *cobra.Command, _ []string) error { bus := runtimeMgr.GetBus() SetValueInCLIContext(cmd, BusCLICtxKey, bus) - setupPeerstoreProvider(*runtimeMgr, flags.RemoteCLIURL) + if err := setupPeerstoreProvider(*runtimeMgr, flags.RemoteCLIURL); err != nil { + return err + } setupCurrentHeightProvider(*runtimeMgr, flags.RemoteCLIURL) setupAndStartP2PModule(*runtimeMgr) return nil } -func setupPeerstoreProvider(rm runtime.Manager, rpcURL string) { - bus := rm.GetBus() - modulesRegistry := bus.GetModulesRegistry() - pstoreProvider := rpcPSP.Create( - rpcPSP.WithP2PConfig(rm.GetConfig().P2P), - rpcPSP.WithCustomRPCURL(rpcURL), - ) - modulesRegistry.RegisterModule(pstoreProvider) +func setupPeerstoreProvider(rm runtime.Manager, rpcURL string) error { + // Ensure `PeerstoreProvider` exists in the modules registry. + if _, err := rpcPSP.Create(rm.GetBus(), rpcPSP.WithCustomRPCURL(rpcURL)); err != nil { + return err + } + return nil } func setupCurrentHeightProvider(rm runtime.Manager, rpcURL string) { + // TECHDEBT(#810): simplify after current height provider is refactored as + // a submodule. bus := rm.GetBus() modulesRegistry := bus.GetModulesRegistry() currentHeightProvider := rpcCHP.NewRPCCurrentHeightProvider( diff --git a/build/linters/blockers.go b/build/linters/blockers.go new file mode 100644 index 000000000..b139447bd --- /dev/null +++ b/build/linters/blockers.go @@ -0,0 +1,19 @@ +//nolint // it's not a Go code file +//go:build !codeanalysis +// +build !codeanalysis + +// This file includes our custom linters. +// If you want to add/modify an existing linter, please check out ruleguard's documentation: https://github.com/quasilyte/go-ruleguard#documentation + +package gorules + +import ( + "github.com/quasilyte/go-ruleguard/dsl" +) + +// Blocks merge if IN_THIS_ comments are present +func BlockInThisCommitPRComment(m dsl.Matcher) { + m.MatchComment(`//.*IN_THIS_.*`). + Where(!m.File().Name.Matches(`Makefile`) && !m.File().Name.Matches(`blockers.go`)). + Report("'IN_THIS_' comments must be addressed before merging to main") +} diff --git a/build/linters/errs.go b/build/linters/errs.go index bb14ee54d..f63b363fa 100644 --- a/build/linters/errs.go +++ b/build/linters/errs.go @@ -11,7 +11,7 @@ import ( "github.com/quasilyte/go-ruleguard/dsl" ) -// This is a custom linter that checks ensures a use of inline error checks +// This is a custom linter that ensures a use of inline error checks func InlineErrCheck(m dsl.Matcher) { m.Match(`$err := $x; if $err != nil { $*_ }`). Where(m["err"].Type.Is(`error`)). diff --git a/build/linters/tests.go b/build/linters/tests.go index 3b95049db..9b0ee71a2 100644 --- a/build/linters/tests.go +++ b/build/linters/tests.go @@ -11,7 +11,7 @@ import ( "github.com/quasilyte/go-ruleguard/dsl" ) -// This is a custom linter that checks ensures a use of require.Equal +// This is a custom linter that ensures the use of require.Equal func EqualInsteadOfTrue(m dsl.Matcher) { m.Match(`require.True($t, $x == $y, $*args)`). Suggest(`require.Equal($t, $x, $y, $args)`). diff --git a/build/localnet/Tiltfile b/build/localnet/Tiltfile index 31e867fdf..9f5a36b4c 100644 --- a/build/localnet/Tiltfile +++ b/build/localnet/Tiltfile @@ -120,8 +120,11 @@ docker_build_with_restart( "client-image", root_dir, dockerfile_contents="""FROM debian:bullseye -RUN apt-get update && apt-get install -y procps -WORKDIR / +RUN apt-get update && apt-get install -y procps bash-completion jq +RUN echo "source /etc/bash_completion" >> ~/.bashrc +# tail -n +2 removes the first line of the completion script since the CLI spits out some logs +RUN echo "source <(p1 completion bash | tail -n +2)" >> ~/.bashrc +WORKDIR /root COPY bin/p1-linux /usr/local/bin/p1 """, only=["bin/p1-linux"], diff --git a/consensus/events.go b/consensus/events.go index 0e31f8d72..5da26aba8 100644 --- a/consensus/events.go +++ b/consensus/events.go @@ -4,7 +4,7 @@ import ( "github.com/pokt-network/pocket/shared/messaging" ) -// publishNewHeightEvent publishes a new height event to the bus so that other interested IntegratableModules can react to it if necessary +// publishNewHeightEvent publishes a new height event to the bus so that other interested IntegrableModules can react to it if necessary func (m *consensusModule) publishNewHeightEvent(height uint64) { newHeightEvent, err := messaging.PackMessage(&messaging.ConsensusNewHeightEvent{Height: height}) if err != nil { diff --git a/consensus/leader_election/module.go b/consensus/leader_election/module.go index db419c800..25a38024c 100644 --- a/consensus/leader_election/module.go +++ b/consensus/leader_election/module.go @@ -14,7 +14,7 @@ type LeaderElectionModule interface { var _ LeaderElectionModule = &leaderElectionModule{} type leaderElectionModule struct { - base_modules.IntegratableModule + base_modules.IntegrableModule base_modules.InterruptableModule } diff --git a/consensus/module.go b/consensus/module.go index 339378096..2b32b62eb 100644 --- a/consensus/module.go +++ b/consensus/module.go @@ -31,7 +31,7 @@ const ( ) type consensusModule struct { - base_modules.IntegratableModule + base_modules.IntegrableModule privateKey cryptoPocket.Ed25519PrivateKey @@ -209,7 +209,7 @@ func (m *consensusModule) GetModuleName() string { } func (m *consensusModule) SetBus(pocketBus modules.Bus) { - m.IntegratableModule.SetBus(pocketBus) + m.IntegrableModule.SetBus(pocketBus) if m.paceMaker != nil { m.paceMaker.SetBus(pocketBus) } diff --git a/consensus/pacemaker/module.go b/consensus/pacemaker/module.go index 2219d82aa..3a9a16db1 100644 --- a/consensus/pacemaker/module.go +++ b/consensus/pacemaker/module.go @@ -45,7 +45,7 @@ type Pacemaker interface { } type pacemaker struct { - base_modules.IntegratableModule + base_modules.IntegrableModule base_modules.InterruptableModule pacemakerCfg *configs.PacemakerConfig diff --git a/docs/devlog/devlog10.md b/docs/devlog/devlog10.md new file mode 100644 index 000000000..9de0f019f --- /dev/null +++ b/docs/devlog/devlog10.md @@ -0,0 +1,154 @@ +# Pocket V1 DevLog #10 + +**Date Published**: July 5th, 2023 + +We have kept the goals and details in this document short, but feel free to reach out to @Olshansk in the [core-dev-chat](https://discord.com/channels/553741558869131266/986789914379186226) for additional details, links & resources. + +## Table of Contents + +- [Iteration 20 Goals \& Results](#iteration-20-goals--results) + - [V0](#v0) + - [M1: PoS](#m1-pos) + - [M2: DoS](#m2-dos) + - [M3: RoS](#m3-ros) + - [M7: IBC](#m7-ibc) +- [Contribute to V1 🧑💻](#contribute-to-v1-) + - [Links \& References](#links--references) +- [ScreenShots](#screenshots) + - [Iteration 19 - Completed](#iteration-19---completed) + - [Iteration 20 - Planned](#iteration-20---planned) + +## Iteration 20 Goals & Results + +**Iterate Dates**: June 15th - June 30th, 2023 + +🔥 The team frikkin killed it 🔥 + +```bash +git diff 2074a1b0c27ec2c73168b02852cc6145b657c0af --stat +# 107 files changed, 9032 insertions(+), 10508 deletions(-) +``` + +Note that this exclude the work we did on infrastructure support, internal documentation, v0 work, SMT repo, collaboration with other projects and a lot more that happens behind the scenes! + +### V0 + +- 🟢 **TestNet Rehearsal** + + - 100% completeness of TestNet Rehearsal + - **Grade**: 10 / 10 + - Completed + +- 🟢 **MainNet Rehearsal** + - Have 5% of MainNet run the latest beta (w/o protocol upgrade) + - **Grade**: 10 / 10 + - Completed and found some bugs along the way too when synching from scratch + +### M1: PoS + +- 🔴 Consensus + + - Attempt #1: Remove State Sync dependency on FSM + - Attempt #3: finish minimum viable state sync + - **Grade**: 1 / 10 + - Very little time left to work on this + +- 🟢 Persistence + + - MVP of the full commit & rollback DEMO + - **Grade**: 7 / 10 + - The test which was going to be the demo has fought me more than expected but good progress has been made, there’s a design document ready, and the test harness is there, the mocks and the submodule interactions have been the problem. + +- 🟢 P2P + - Attempt #N: Finishing off and merging in everything related to gossip and background + - **Grade**: 8.5 / 10 + +### M2: DoS + +- 🔴 **Primary focus: observability** + - Open question: need to identify issues w/ metric access + - Streamlining logging: Make structured logging system easily available to new devs w/ documentation part of LocalNet instructions + - Attach smaller tickets in a separate repo to V2 + - **Grade**: 0 / 10 + - Other infrastructure related maintenance issues took away time from being able to focus on observability + +### M3: RoS + +- 🟢 **Trustless Relay** + + - Session caching on the client + - Finish all the PRs in flight (review, merge in) + - Provide an E2E test that works, blocks CI if it breaks, documented and visible (DEMO) + - **Grade**: 8 / 10 + +- 🟡 **Feature Flags** + - Scope out the work necessary and create an E2E Feature Path github ticket using the template we created + - **Grade**: 5 / 10 + - Research and design doc made good progress w/ support from bigBoss bus still a lot to do. + +### M7: IBC + +- 🟢 **SMST** + + - Get it reviewed & merged in + - Clean up the documentation & merge it in + - Visualizers: create a visualizer for the tree + - Present: Finish off the SMT presentation + - Stretch goal: potentially start storing trustless relays in it + - **Grade**: 8.5 / 10 + - SMST merged (wrapper around SMT option) + - Visualiser is accurate but not pretty could do with some more work + - Presentation went well but definietly could improve on some packed slides + - Need to work closer with @Arash Deshmeh to get it in prod with M3 + +- 🟢 **ICS23** + + - Put up the github ticket and PR for reivew to merge in the proof mechanisms + - Up to cosmos on ETA to review/merge + - **Grade**: 9 / 10 + - ICS23 merged in our repo using my fork of `cosmos/ics23` as a dependency + - My explanations on why the exclusion proof is needed can improve as others find it hard to understand + - Cosmos PR is ready to merge pending review (probably take a while) + +- 🟡 **ICS24** + + - Put up event logging for review; stretch goal is to merge + - **Grade**: 6 / 10 + - ICS-24 stores have made great progress + - Event logging unfortunately didnt make this fortnight + - Message onto/off of bus as transactions works well 👍🏻 + +- 🟡 **Light client spike** + - Start knowing where to head with research + - **Grade**: 5 / 10 + - ICS-02 specced out well + - ICS-08 needs more work into its design + - Need to learn more about CosmWasm and WasmVM + - WIP document needs to be converted to ticket epic + - https://hackmd.io/0WVMarGpSIGqEyzvnygWpw + +## Contribute to V1 🧑💻 + +### Links & References + +- [V1 Specifications](https://github.com/pokt-network/pocket-network-protocol) +- [V1 Repo](https://github.com/pokt-network/pocket) +- [V1 Wiki](https://github.com/pokt-network/pocket/wiki) +- [V1 Project Dashboard](https://github.com/pokt-network/pocket/projects?query=is%3Aopen) + +## ScreenShots + +Please note that everything that was not `Done` in iteration19 is moving over to iteration20. + +### Iteration 19 - Completed + + + + +### Iteration 20 - Planned + +_tl;dr Aim to demo as much of the work from the previous iteration in action_ + +) + + diff --git a/ibc/module.go b/ibc/module.go index d8685059c..d1382c4db 100644 --- a/ibc/module.go +++ b/ibc/module.go @@ -20,7 +20,7 @@ import ( var _ modules.IBCModule = &ibcModule{} type ibcModule struct { - base_modules.IntegratableModule + base_modules.IntegrableModule m sync.Mutex diff --git a/ibc/store/proofs_ics23.go b/ibc/store/proofs_ics23.go index 53e0b203d..9d0e22961 100644 --- a/ibc/store/proofs_ics23.go +++ b/ibc/store/proofs_ics23.go @@ -75,7 +75,6 @@ func createNonMembershipProof(tree *smt.SMT, key []byte) (*ics23.CommitmentProof if err != nil { return nil, coreTypes.ErrIBCCreatingProof(err) } - return convertSMPToExclusionProof(proof, key), nil } diff --git a/logger/module.go b/logger/module.go index 091c4d5b5..961b1c4a2 100644 --- a/logger/module.go +++ b/logger/module.go @@ -14,7 +14,7 @@ import ( var _ modules.Module = &loggerModule{} type loggerModule struct { - base_modules.IntegratableModule + base_modules.IntegrableModule base_modules.InterruptableModule zerolog.Logger diff --git a/p2p/README.md b/p2p/README.md index 8263cd563..cde1a155c 100644 --- a/p2p/README.md +++ b/p2p/README.md @@ -5,11 +5,15 @@ This document is meant to be a supplement to the living specification of [1.0 Po ## Table of Contents - [Definitions](#definitions) -- [Interface](#interface) -- [Implementation](#implementation) - - [Code Architecture - P2P Module](#code-architecture---p2p-module) - - [Code Architecture - Network Module](#code-architecture---network-module) - - [Code Organization](#code-organization) +- [Interface & Integration](#interface--integration) +- [Module Architecture](#module-architecture) + - [Architecture Design Language](#architecture-design-language) + - [Legends](#legends) + - [P2P Module / Router Decoupling](#p2p-module--router-decoupling) + - [Message Propagation & Handling](#message-propagation--handling) + - [Message Deduplication](#message-deduplication) + - [Peer Discovery](#peer-discovery) + - [Code Organization](#code-organization) - [Testing](#testing) - [Running Unit Tests](#running-unit-tests) - [RainTree testing framework](#raintree-testing-framework) @@ -34,14 +38,11 @@ A structured "gossip" protocol (and implementation) which uses the raintree algo A "gossip" protocol (implementation TBD) which facilitates "gossip" to all P2P participants, including non-staked actors (e.g. full-nodes). -## Interface +## Interface & Integration -This module aims to implement the interface specified in `pocket/shared/modules/p2p_module.go` using the specification above. - -## Implementation - -### P2P Module Architecture +This module aims to implement the interface specified in [`pocket/shared/modules/p2p_module.go`](../shared/modules/p2p_module.go). +_(TODO: diagram legend)_ ```mermaid flowchart TD subgraph P2P["P2P Module"] @@ -67,105 +68,508 @@ flowchart TD class PN pocket_network ``` -`Routers` is where [RainTree](https://github.com/pokt-network/pocket/files/9853354/raintree.pdf) (or the simpler basic approach) is implemented. See `raintree/router.go` for the specific implementation of RainTree, but please refer to the [specifications](https://github.com/pokt-network/pocket-network-protocol/tree/main/p2p) for more details. +`Routers` is where [RainTree](https://github.com/pokt-network/pocket/files/9853354/raintree.pdf) is implemented. +See [`raintree/router.go`](./raintree/router.go) for the specific implementation of RainTree, but please refer to the [specifications](https://github.com/pokt-network/pocket-network-protocol/tree/main/p2p) for more details. -### Raintree Router Architecture +## Module Architecture + +_(TODO: move "arch. design lang." & "legends" sections into `shared` to support common usage)_ -_DISCUSS(team): If you feel this needs a diagram, please reach out to the team for additional details._ -_TODO(olshansky, BenVan): Link to RainTree visualizations once it is complete._ +### Architecture Design Language -### Message Propagation +The architecture design language expressed in this documentation is based on [UML](https://www.uml-diagrams.org/). +Due to limitations in the current version of mermaid, class diagrams are much more adherant to the UML component specification. +Component diagrams however are much more loosely inspired by their UML counterparts. -Given `Local P2P Module` has a message that it needs to propagate: +Regardless, each architecture diagram should be accompanied by a legend which covers all the design language features used to provide disambiguation. -
Raintree Router selects targets from the Pokt Peerstore, which only includes staked actorsBackground Router selects targets from the libp2p Peerstore, which includes all P2P participantsHost manages opening and closing streams to targeted peersRemote P2P module's (i.e. receiver's) handleStream is called (having been registered via setStreamHandler())handleStream propagates message via Raintree RouterhandleStream propagates message via Background RouterRemote P2P Module's perspective targeting its next peersRemote P2P Module's perspective targeting its next peers