Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TODOs] refactor: proof path calculation #659

Merged
merged 6 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions pkg/crypto/protocol/proof_path.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package protocol

import (
"crypto/sha256"

"github.com/pokt-network/smt"
)

// SMT specification used for the proof verification.
var (
newHasher = sha256.New
SmtSpec smt.TrieSpec
)

func init() {
// Use a spec that does not prehash values in the smst. This returns a nil value
// hasher for the proof verification in order to avoid hashing the value twice.
SmtSpec = smt.NewTrieSpec(
newHasher(), true,
smt.WithValueHasher(nil),
)
}

// GetPathForProof computes the path to be used for proof validation by hashing
// the block hash and session id.
func GetPathForProof(blockHash []byte, sessionId string) []byte {
hasher := newHasher()
if _, err := hasher.Write(append(blockHash, []byte(sessionId)...)); err != nil {
panic(err)
}

return hasher.Sum(nil)
Comment on lines +24 to +32
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider handling potential errors more gracefully.

The hash computation logic looks good. However, consider handling potential errors more gracefully instead of using panic.

-  panic(err)
+  log.Fatalf("failed to write hash: %v", err)
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// GetPathForProof computes the path to be used for proof validation by hashing
// the block hash and session id.
func GetPathForProof(blockHash []byte, sessionId string) []byte {
hasher := newHasher()
if _, err := hasher.Write(append(blockHash, []byte(sessionId)...)); err != nil {
panic(err)
}
return hasher.Sum(nil)
// GetPathForProof computes the path to be used for proof validation by hashing
// the block hash and session id.
func GetPathForProof(blockHash []byte, sessionId string) []byte {
hasher := newHasher()
if _, err := hasher.Write(append(blockHash, []byte(sessionId)...)); err != nil {
log.Fatalf("failed to write hash: %v", err)
}
return hasher.Sum(nil)

}
8 changes: 6 additions & 2 deletions pkg/relayer/session/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import (
"fmt"

"github.com/pokt-network/poktroll/pkg/client"
"github.com/pokt-network/poktroll/pkg/crypto/protocol"
"github.com/pokt-network/poktroll/pkg/either"
"github.com/pokt-network/poktroll/pkg/observable"
"github.com/pokt-network/poktroll/pkg/observable/channel"
"github.com/pokt-network/poktroll/pkg/observable/filter"
"github.com/pokt-network/poktroll/pkg/observable/logging"
"github.com/pokt-network/poktroll/pkg/relayer"
proofkeeper "github.com/pokt-network/poktroll/x/proof/keeper"
"github.com/pokt-network/poktroll/x/shared"
)

Expand Down Expand Up @@ -229,6 +229,8 @@ func (rs *relayerSessionsManager) goProveClaims(
proofsGeneratedCh chan<- []relayer.SessionTree,
failSubmitProofsSessionsCh chan<- []relayer.SessionTree,
) {
logger := rs.logger.With("method", "goProveClaims")

// Separate the sessionTrees into those that failed to generate a proof
// and those that succeeded, then send them on their respective channels.
failedProofs := []relayer.SessionTree{}
Expand All @@ -241,13 +243,15 @@ func (rs *relayerSessionsManager) goProveClaims(
}
// Generate the proof path for the sessionTree using the previously committed
// sessionPathBlock hash.
path := proofkeeper.GetPathForProof(
path := protocol.GetPathForProof(
sessionPathBlock.Hash(),
sessionTree.GetSessionHeader().GetSessionId(),
)

// If the proof cannot be generated, add the sessionTree to the failedProofs.
if _, err := sessionTree.ProveClosest(path); err != nil {
logger.Error().Err(err).Msg("failed to generate proof")

failedProofs = append(failedProofs, sessionTree)
continue
}
Expand Down
16 changes: 11 additions & 5 deletions tests/integration/tokenomics/relay_mining_difficulty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/require"

"github.com/pokt-network/poktroll/cmd/poktrolld/cmd"
"github.com/pokt-network/poktroll/pkg/crypto/protocol"
testutilevents "github.com/pokt-network/poktroll/testutil/events"
"github.com/pokt-network/poktroll/testutil/integration"
testutil "github.com/pokt-network/poktroll/testutil/integration"
Expand All @@ -28,7 +29,7 @@ func init() {
}

func TestUpdateRelayMiningDifficulty_NewServiceSeenForTheFirstTime(t *testing.T) {
var claimWindowOpenBlockHash, proofWindowOpenBlockHash []byte
var claimWindowOpenBlockHash, proofWindowOpenBlockHash, proofPathSeedBlockHash []byte

// Create a new integration app
integrationApp := integration.NewCompleteIntegrationApp(t)
Expand Down Expand Up @@ -89,7 +90,7 @@ func TestUpdateRelayMiningDifficulty_NewServiceSeenForTheFirstTime(t *testing.T)
createProofMsg := prooftypes.MsgSubmitProof{
SupplierAddress: integrationApp.DefaultSupplier.Address,
SessionHeader: session.Header,
Proof: getProof(t, trie),
Proof: getProof(t, trie, proofPathSeedBlockHash, session.GetHeader().GetSessionId()),
}
result = integrationApp.RunMsg(t,
&createProofMsg,
Expand Down Expand Up @@ -202,11 +203,16 @@ func prepareSMST(
// getProof returns a proof for the given session for the empty path.
// If there is only one relay in the trie, the proof will be for that single
// relay since it is "closest" to any path provided, empty or not.
func getProof(t *testing.T, trie *smt.SMST) []byte {
func getProof(
t *testing.T,
trie *smt.SMST,
pathSeedBlockHash []byte,
sessionId string,
) []byte {
t.Helper()

emptyPath := make([]byte, trie.PathHasherSize())
proof, err := trie.ProveClosest(emptyPath)
path := protocol.GetPathForProof(pathSeedBlockHash, sessionId)
proof, err := trie.ProveClosest(path)
require.NoError(t, err)

proofBz, err := proof.Marshal()
Expand Down
36 changes: 4 additions & 32 deletions x/proof/keeper/msg_server_submit_proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ package keeper
import (
"bytes"
"context"
"crypto/sha256"
"fmt"
"hash"

cosmoscryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
cosmostypes "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -26,22 +24,6 @@ import (
sharedtypes "github.com/pokt-network/poktroll/x/shared/types"
)

// SMT specification used for the proof verification.
var (
hasher hash.Hash
SmtSpec smt.TrieSpec
)

func init() {
// Use a spec that does not prehash values in the smst. This returns a nil value
// hasher for the proof verification in order to to avoid hashing the value twice.
hasher = sha256.New()
SmtSpec = smt.NewTrieSpec(
hasher, true,
smt.WithValueHasher(nil),
)
}

// SubmitProof is the server handler to submit and store a proof on-chain.
// A proof that's stored on-chain is what leads to rewards (i.e. inflation)
// downstream, making the series of checks a critical part of the protocol.
Expand Down Expand Up @@ -165,7 +147,7 @@ func (k msgServer) SubmitProof(
// TODO_MAINNET(#427): Utilize smt.VerifyCompactClosestProof here to
// reduce on-chain storage requirements for proofs.
// Get the relay request and response from the proof.GetClosestMerkleProof.
relayBz := sparseMerkleClosestProof.GetValueHash(&SmtSpec)
relayBz := sparseMerkleClosestProof.GetValueHash(&protocol.SmtSpec)
relay := &servicetypes.Relay{}
if err = k.cdc.Unmarshal(relayBz, relay); err != nil {
return nil, status.Error(
Expand Down Expand Up @@ -454,7 +436,7 @@ func verifyClosestProof(
proof *smt.SparseMerkleClosestProof,
claimRootHash []byte,
) error {
valid, err := smt.VerifyClosestProof(proof, claimRootHash, &SmtSpec)
valid, err := smt.VerifyClosestProof(proof, claimRootHash, &protocol.SmtSpec)
if err != nil {
return err
}
Expand Down Expand Up @@ -528,24 +510,14 @@ func (k msgServer) validateClosestPath(
// error that may occur due to block height differing from the off-chain part.
k.logger.Info("E2E_DEBUG: height for block hash when verifying the proof", earliestSupplierProofCommitHeight, sessionHeader.GetSessionId())

expectedProofPath := GetPathForProof(proofPathSeedBlockHash, sessionHeader.GetSessionId())
expectedProofPath := protocol.GetPathForProof(proofPathSeedBlockHash, sessionHeader.GetSessionId())
if !bytes.Equal(proof.Path, expectedProofPath) {
return types.ErrProofInvalidProof.Wrapf(
"the proof for the path provided (%x) does not match one expected by the on-chain protocol (%x)",
"the path of the proof provided (%x) does not match one expected by the on-chain protocol (%x)",
proof.Path,
expectedProofPath,
)
}

return nil
}

func GetPathForProof(blockHash []byte, sessionId string) []byte {
// TODO_BLOCKER(@Olshansk): We need to replace the return
// statement below and change all relevant parts in the codebase.
// See the conversation in the following thread for more details: https://github.com/pokt-network/poktroll/pull/406#discussion_r1520790083
path := make([]byte, SmtSpec.PathHasherSize())
copy(path, blockHash)
return path
// return pathHasher.Sum(append(blockHash, []byte(sessionId)...))
}
88 changes: 64 additions & 24 deletions x/proof/keeper/msg_server_submit_proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,10 @@ var (
func init() {
// The CometBFT header hash is 32 bytes: https://docs.cometbft.com/main/spec/core/data_structures
blockHeaderHash = make([]byte, 32)
expectedMerkleProofPath = keeper.GetPathForProof(blockHeaderHash, "TODO_BLOCKER_session_id_currently_unused")
expectedMerkleProofPath = protocol.GetPathForProof(blockHeaderHash, "TODO_BLOCKER_session_id_currently_unused")
}

func TestMsgServer_SubmitProof_Success(t *testing.T) {
var claimWindowOpenBlockHash []byte

tests := []struct {
desc string
getProofMsgHeight func(
Expand All @@ -75,7 +73,7 @@ func TestMsgServer_SubmitProof_Success(t *testing.T) {
return shared.GetEarliestSupplierProofCommitHeight(
sharedParams,
queryHeight,
claimWindowOpenBlockHash,
blockHeaderHash,
supplierAddr,
)
},
Expand Down Expand Up @@ -165,11 +163,10 @@ func TestMsgServer_SubmitProof_Success(t *testing.T) {
claimMsgHeight := shared.GetEarliestSupplierClaimCommitHeight(
&sharedParams,
sessionHeader.GetSessionEndBlockHeight(),
claimWindowOpenBlockHash,
blockHeaderHash,
supplierAddr,
)
sdkCtx = sdkCtx.WithBlockHeight(claimMsgHeight)
ctx = sdkCtx
ctx = keepertest.SetBlockHeight(ctx, claimMsgHeight)

// Create a valid claim.
claim := createClaimAndStoreBlockHash(
Expand All @@ -183,10 +180,25 @@ func TestMsgServer_SubmitProof_Success(t *testing.T) {
keepers,
)

// Advance the block height to the proof path seed height.
earliestSupplierProofCommitHeight := shared.GetEarliestSupplierProofCommitHeight(
&sharedParams,
sessionHeader.GetSessionEndBlockHeight(),
blockHeaderHash,
supplierAddr,
)
ctx = keepertest.SetBlockHeight(ctx, earliestSupplierProofCommitHeight-1)

// Store proof path seed block hash in the session keeper so that it can
// look it up during proof validation.
keepers.StoreBlockHash(ctx)

// Compute expected proof path.
expectedMerkleProofPath := protocol.GetPathForProof(blockHeaderHash, sessionHeader.GetSessionId())

// Advance the block height to the test proof msg height.
proofMsgHeight := test.getProofMsgHeight(&sharedParams, sessionHeader.GetSessionEndBlockHeight(), supplierAddr)
sdkCtx = sdkCtx.WithBlockHeight(proofMsgHeight)
ctx = sdkCtx
ctx = keepertest.SetBlockHeight(ctx, proofMsgHeight)

proofMsg := newTestProofMsg(t,
supplierAddr,
Expand Down Expand Up @@ -388,12 +400,10 @@ func TestMsgServer_SubmitProof_Error_OutsideOfWindow(t *testing.T) {
}

func TestMsgServer_SubmitProof_Error(t *testing.T) {
var claimWindowOpenBlockHash, proofCommitBlockHash []byte

opts := []keepertest.ProofKeepersOpt{
// Set block hash such that on-chain closest merkle proof validation
// uses the expected path.
keepertest.WithBlockHash(expectedMerkleProofPath),
keepertest.WithBlockHash(blockHeaderHash),
// Set block height to 1 so there is a valid session on-chain.
keepertest.WithBlockHeight(1),
}
Expand Down Expand Up @@ -507,7 +517,7 @@ func TestMsgServer_SubmitProof_Error(t *testing.T) {
claimMsgHeight := shared.GetEarliestSupplierClaimCommitHeight(
&sharedParams,
validSessionHeader.GetSessionEndBlockHeight(),
claimWindowOpenBlockHash,
blockHeaderHash,
supplierAddr,
)
sdkCtx := cosmostypes.UnwrapSDKContext(ctx)
Expand Down Expand Up @@ -986,8 +996,7 @@ func TestMsgServer_SubmitProof_Error(t *testing.T) {
require.NoError(t, err)

// Re-set the block height to the earliest claim commit height to create a new claim.
claimCtx := cosmostypes.UnwrapSDKContext(ctx)
claimCtx = claimCtx.WithBlockHeight(claimMsgHeight)
claimCtx := keepertest.SetBlockHeight(ctx, claimMsgHeight)

// Create a valid claim with the expected merkle root.
claimMsg := newTestClaimMsg(t,
Expand All @@ -1008,9 +1017,9 @@ func TestMsgServer_SubmitProof_Error(t *testing.T) {
expectedErr: status.Error(
codes.FailedPrecondition,
prooftypes.ErrProofInvalidProof.Wrapf(
"the proof for the path provided (%x) does not match one expected by the on-chain protocol (%x)",
"the path of the proof provided (%x) does not match one expected by the on-chain protocol (%x)",
wrongClosestProofPath,
blockHeaderHash,
protocol.GetPathForProof(sdkCtx.HeaderHash(), validSessionHeader.GetSessionId()),
).Error(),
),
},
Expand Down Expand Up @@ -1074,6 +1083,12 @@ func TestMsgServer_SubmitProof_Error(t *testing.T) {
_, err = unclaimedSessionTree.Flush()
require.NoError(t, err)

// Compute expected proof path for the unclaimed session.
expectedMerkleProofPath := protocol.GetPathForProof(
blockHeaderHash,
unclaimedSessionHeader.GetSessionId(),
)

// Construct new proof message using the supplier & session header
// from the session which is *not* expected to be claimed.
return newTestProofMsg(t,
Expand Down Expand Up @@ -1109,8 +1124,7 @@ func TestMsgServer_SubmitProof_Error(t *testing.T) {
require.NoError(t, err)

// Re-set the block height to the earliest claim commit height to create a new claim.
claimCtx := cosmostypes.UnwrapSDKContext(ctx)
claimCtx = claimCtx.WithBlockHeight(claimMsgHeight)
claimCtx := keepertest.SetBlockHeight(ctx, claimMsgHeight)

// Create a claim with the incorrect Merkle root.
wrongMerkleRootClaimMsg := newTestClaimMsg(t,
Expand All @@ -1124,6 +1138,25 @@ func TestMsgServer_SubmitProof_Error(t *testing.T) {
_, err = srv.CreateClaim(claimCtx, wrongMerkleRootClaimMsg)
require.NoError(t, err)

// Construct a valid session tree with 5 relays.
validSessionTree := newFilledSessionTree(
ctx, t,
uint(5),
supplierUid, supplierAddr,
validSessionHeader, validSessionHeader, validSessionHeader,
keyRing,
ringClient,
)

_, err = validSessionTree.Flush()
require.NoError(t, err)

// Compute expected proof path for the session.
expectedMerkleProofPath := protocol.GetPathForProof(
blockHeaderHash,
validSessionHeader.GetSessionId(),
)

return newTestProofMsg(t,
supplierAddr,
validSessionHeader,
Expand Down Expand Up @@ -1162,16 +1195,23 @@ func TestMsgServer_SubmitProof_Error(t *testing.T) {
// Submit the corresponding proof.
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
// Increment the block height to the test proof height.
proofMsg := test.newProofMsg(t)
proofMsgHeight := shared.GetEarliestSupplierProofCommitHeight(

// Advance the block height to the proof path seed height.
earliestSupplierProofCommitHeight := shared.GetEarliestSupplierProofCommitHeight(
&sharedParams,
proofMsg.GetSessionHeader().GetSessionEndBlockHeight(),
proofCommitBlockHash,
blockHeaderHash,
proofMsg.GetSupplierAddress(),
)
ctx = keepertest.SetBlockHeight(ctx, earliestSupplierProofCommitHeight-1)

// Store proof path seed block hash in the session keeper so that it can
// look it up during proof validation.
keepers.StoreBlockHash(ctx)

ctx = cosmostypes.UnwrapSDKContext(ctx).WithBlockHeight(proofMsgHeight)
// Advance the block height to the earliest proof commit height.
ctx = keepertest.SetBlockHeight(ctx, earliestSupplierProofCommitHeight)

submitProofRes, err := srv.SubmitProof(ctx, proofMsg)

Expand Down Expand Up @@ -1385,7 +1425,7 @@ func getClosestRelayDifficultyBits(

// Extract the Relay (containing the RelayResponse & RelayRequest) from the merkle proof.
relay := new(servicetypes.Relay)
relayBz := closestMerkleProof.GetValueHash(&keeper.SmtSpec)
relayBz := closestMerkleProof.GetValueHash(&protocol.SmtSpec)
err = relay.Unmarshal(relayBz)
require.NoError(t, err)

Expand Down
Loading
Loading