Skip to content

Commit

Permalink
refactor: difficulty in terms of target hash
Browse files Browse the repository at this point in the history
  • Loading branch information
bryanchriswhite committed Jul 16, 2024
1 parent c6d987c commit ffb6902
Show file tree
Hide file tree
Showing 29 changed files with 393 additions and 352 deletions.
166 changes: 92 additions & 74 deletions api/poktroll/proof/params.pulsar.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion e2e/tests/parse_params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func (s *suite) newProofMsgUpdateParams(params paramsMap) cosmostypes.Msg {

for paramName, paramValue := range params {
switch paramName {
case prooftypes.ParamMinRelayDifficultyBits:
case prooftypes.ParamRelayDifficultyTargetHash:
msgUpdateParams.Params.MinRelayDifficultyBits = uint64(paramValue.value.(int64))
case prooftypes.ParamProofRequestProbability:
msgUpdateParams.Params.ProofRequestProbability = paramValue.value.(float32)
Expand Down
2 changes: 1 addition & 1 deletion e2e/tests/update_params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ func (s *suite) assertExpectedModuleParamsUpdated(moduleName string) {
params := prooftypes.DefaultParams()
paramsMap := s.expectedModuleParams[moduleName]

minRelayDifficultyBits, ok := paramsMap[prooftypes.ParamMinRelayDifficultyBits]
minRelayDifficultyBits, ok := paramsMap[prooftypes.ParamRelayDifficultyTargetHash]
if ok {
params.MinRelayDifficultyBits = uint64(minRelayDifficultyBits.value.(int64))
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/client/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ type BlockQueryClient interface {
// protobuf message. Since the generated go types don't include interface types, this
// is necessary to prevent dependency cycles.
type ProofParams interface {
GetMinRelayDifficultyBits() uint64
GetRelayDifficultyTargetHash() []byte
GetProofRequestProbability() float32
GetProofRequirementThreshold() uint64
GetProofMissingPenalty() *cosmostypes.Coin
Expand Down
29 changes: 17 additions & 12 deletions pkg/crypto/protocol/difficulty.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
package protocol

import (
"encoding/binary"
"math/bits"
"crypto/sha256"
"encoding/hex"
"math/big"
)

// CountHashDifficultyBits returns the number of leading zero bits in the given byte slice.
// TODO_MAINNET: Consider generalizing difficulty to a target hash. See:
// Difficulty1Hash represents the "easiest" difficulty.
var (
Difficulty1HashHex = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
Difficulty1HashBz, _ = hex.DecodeString(Difficulty1HashHex)
Difficulty1HashInt = new(big.Int).SetBytes(Difficulty1HashBz)
)

// GetDifficultyFromHash returns the "difficulty" of the given hash, with respect
// to the "highest" target hash, Difficulty1Hash.
// - https://bitcoin.stackexchange.com/questions/107976/bitcoin-difficulty-why-leading-0s
// - https://bitcoin.stackexchange.com/questions/121920/is-it-always-possible-to-find-a-number-whose-hash-starts-with-a-certain-number-o
// - https://github.com/pokt-network/poktroll/pull/656/files#r1666712528
func CountHashDifficultyBits(bz [32]byte) int {
// Using BigEndian for contiguous bit/byte ordering such leading zeros
// accumulate across adjacent bytes.
// E.g.: []byte{0, 0b00111111, 0x00, 0x00} has 10 leading zero bits. If
// LittleEndian were applied instead, it would have 18 leading zeros because it would
// look like []byte{0, 0, 0b00111111, 0}.
return bits.LeadingZeros64(binary.BigEndian.Uint64(bz[:]))
func GetDifficultyFromHash(hashBz [sha256.Size]byte) int64 {
hashInt := new(big.Int).SetBytes(hashBz[:])

// difficulty is the ratio of the highest target hash to the given hash.
return new(big.Int).Div(Difficulty1HashInt, hashInt).Int64()
}
51 changes: 0 additions & 51 deletions pkg/crypto/protocol/difficulty_test.go

This file was deleted.

10 changes: 10 additions & 0 deletions pkg/crypto/protocol/hash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package protocol

import "crypto/sha256"

// GetHashFromBytes returns the hash of the relay (full, request or response) bytes.
// It is used as helper in the case that the relay is already marshaled and
// centralizes the hasher used.
func GetHashFromBytes(relayBz []byte) [sha256.Size]byte {
return sha256.Sum256(relayBz)
}
10 changes: 10 additions & 0 deletions pkg/crypto/rand/integer.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ import (
func SeededInt63(seedParts ...[]byte) int64 {
seedHashInputBz := bytes.Join(append([][]byte{}, seedParts...), nil)
seedHash := crypto.Sha256(seedHashInputBz)

// TODO_MAINNET: To support other language implementations of the protocol, the
// pseudo-random number generator used here should be language-agnostic (i.e. not
// golang specific).
//
// Additionally, there is a precision loss here when converting the hash to an int64.
// Since the math/rand.Source interface only supports int64 seeds, we are forced to
// truncate the hash to 64 bits. This is not ideal, as it reduces the entropy of the
// seed. We should consider using a different random number generator that supports
// byte array seeds.
seed, _ := binary.Varint(seedHash)

return rand.NewSource(seed).Int63()
Expand Down
19 changes: 11 additions & 8 deletions pkg/relayer/miner/miner.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package miner

import (
"bytes"
"context"

"cosmossdk.io/depinject"
Expand All @@ -25,9 +26,11 @@ type miner struct {
// proofQueryClient is used to query for the minimum relay difficulty.
proofQueryClient client.ProofQueryClient

// relayDifficultyBits is the minimum difficulty that a relay must have to be
// volume / reward applicable.
relayDifficultyBits uint64
// relay_difficulty is the target hash which a relay hash must be less than to be volume/reward applicable.
//
// TODO_MAINNET(#543): This is populated by querying the corresponding on-chain parameter during construction.
// If this parameter is updated on-chain the relayminer will need to be restarted to query the new value.
relayDifficultyTargetHash []byte
}

// NewMiner creates a new miner from the given dependencies and options. It
Expand All @@ -37,7 +40,7 @@ type miner struct {
// - ProofQueryClient
//
// Available options:
// - WithDifficulty
// - WithRelayDifficultyTargetHash
func NewMiner(
deps depinject.Config,
opts ...relayer.MinerOption,
Expand Down Expand Up @@ -91,8 +94,8 @@ func (mnr *miner) setDefaults() error {
return err
}

if mnr.relayDifficultyBits == 0 {
mnr.relayDifficultyBits = params.GetMinRelayDifficultyBits()
if len(mnr.relayDifficultyTargetHash) == 0 {
mnr.relayDifficultyTargetHash = params.GetRelayDifficultyTargetHash()
}
return nil
}
Expand All @@ -112,10 +115,10 @@ func (mnr *miner) mapMineRelay(
if err != nil {
return either.Error[*relayer.MinedRelay](err), false
}
relayHash := servicetypes.GetHashFromBytes(relayBz)
relayHash := protocol.GetHashFromBytes(relayBz)

// The relay IS NOT volume / reward applicable
if uint64(protocol.CountHashDifficultyBits(relayHash)) < mnr.relayDifficultyBits {
if bytes.Compare(relayHash[:], mnr.relayDifficultyTargetHash) == 1 {
return either.Success[*relayer.MinedRelay](nil), true
}

Expand Down
9 changes: 5 additions & 4 deletions pkg/relayer/miner/miner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,19 @@ import (
"cosmossdk.io/depinject"
"github.com/stretchr/testify/require"

"github.com/pokt-network/poktroll/pkg/crypto/protocol"
"github.com/pokt-network/poktroll/pkg/observable/channel"
"github.com/pokt-network/poktroll/pkg/relayer"
"github.com/pokt-network/poktroll/pkg/relayer/miner"
"github.com/pokt-network/poktroll/testutil/testclient/testqueryclients"
servicetypes "github.com/pokt-network/poktroll/x/service/types"
)

const testDifficulty = uint64(16)
var testTargetHash, _ = hex.DecodeString("0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")

// TestMiner_MinedRelays constructs an observable of mined relays, through which
// it pipes pre-mined relay fixtures. It asserts that the observable only emits
// mined relays with difficulty equal to or greater than testDifficulty.
// mined relays with difficulty equal to or greater than testTargetHash.
//
// To regenerate all fixtures, use `make go_testgen_fixtures`; to regenerate only this
// test's fixtures run `go generate ./pkg/relayer/miner/miner_test.go`.
Expand All @@ -42,7 +43,7 @@ func TestMiner_MinedRelays(t *testing.T) {

proofQueryClientMock := testqueryclients.NewTestProofQueryClient(t)
deps := depinject.Supply(proofQueryClientMock)
mnr, err := miner.NewMiner(deps, miner.WithDifficulty(testDifficulty))
mnr, err := miner.NewMiner(deps, miner.WithRelayDifficultyTargetHash(testTargetHash))
require.NoError(t, err)

minedRelays := mnr.MinedRelays(ctx, mockRelaysObs)
Expand Down Expand Up @@ -134,7 +135,7 @@ func unmarshalHexMinedRelay(
require.NoError(t, err)

// TODO_TECHDEBT(@red-0ne, #446): Centralize the configuration for the SMT spec.
relayHashArr := servicetypes.GetHashFromBytes(relayBz)
relayHashArr := protocol.GetHashFromBytes(relayBz)
relayHash := relayHashArr[:]

return &relayer.MinedRelay{
Expand Down
7 changes: 3 additions & 4 deletions pkg/relayer/miner/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ package miner

import "github.com/pokt-network/poktroll/pkg/relayer"

// WithDifficulty sets the difficulty of the miner, where difficultyBytes is the
// minimum number of leading zero bytes.
func WithDifficulty(difficultyBits uint64) relayer.MinerOption {
// WithRelayDifficultyTargetHash sets the relayDifficultyTargetHash of the miner.
func WithRelayDifficultyTargetHash(targetHash []byte) relayer.MinerOption {
return func(mnr relayer.Miner) {
mnr.(*miner).relayDifficultyBits = difficultyBits
mnr.(*miner).relayDifficultyTargetHash = targetHash
}
}
5 changes: 2 additions & 3 deletions proto/poktroll/proof/params.proto
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ message Params {
option (amino.name) = "poktroll/x/proof/Params";
option (gogoproto.equal) = true;

// min_relay_difficulty_bits is the minimum difficulty in bits for a relay to
// be included in a Merkle proof.
uint64 min_relay_difficulty_bits = 1 [(gogoproto.jsontag) = "min_relay_difficulty_bits"];
// relay_difficulty is the target hash which a relay hash must be less than to be volume/reward applicable.
bytes relay_difficulty_target_hash = 1 [(gogoproto.jsontag) = "relay_difficulty_target_hash"];

// proof_request_probability is the probability of a session requiring a proof
// if it's cost (i.e. compute unit consumption) is below the ProofRequirementThreshold.
Expand Down
4 changes: 2 additions & 2 deletions telemetry/event_counters.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,15 @@ func ClaimCounter(
// RelayMiningDifficultyGauge sets a gauge which tracks the relay mining difficulty,
// which is represented by number of leading zero bits.
// The serviceId is used as a label to be able to track the difficulty for each service.
func RelayMiningDifficultyGauge(numbLeadingZeroBits int, serviceId string) {
func RelayMiningDifficultyGauge(difficulty int64, serviceId string) {
labels := []metrics.Label{
{Name: "type", Value: "relay_mining_difficulty"},
{Name: "service_id", Value: serviceId},
}

telemetry.SetGaugeWithLabels(
[]string{eventTypeMetricKeyGauge},
float32(numbLeadingZeroBits),
float32(difficulty),
labels,
)
}
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/tokenomics/relay_mining_difficulty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ func TestUpdateRelayMiningDifficulty_NewServiceSeenForTheFirstTime(t *testing.T)
relayMiningEvent := relayMiningEvents[0]
require.Equal(t, "svc1", relayMiningEvent.ServiceId)
// The default difficulty)
require.Equal(t, "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", relayMiningEvent.PrevTargetHashHexEncoded)
require.Equal(t, "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", relayMiningEvent.NewTargetHashHexEncoded)
require.Equal(t, prooftypes.DefaultRelayDifficultyTargetHashHex, relayMiningEvent.PrevTargetHashHexEncoded)
require.Equal(t, prooftypes.DefaultRelayDifficultyTargetHashHex, relayMiningEvent.NewTargetHashHexEncoded)
// The previous EMA is the same as the current one if the service is new
require.Equal(t, uint64(1), relayMiningEvent.PrevNumRelaysEma)
require.Equal(t, uint64(1), relayMiningEvent.NewNumRelaysEma)
Expand Down
5 changes: 3 additions & 2 deletions testutil/testrelayer/relays.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/require"

"github.com/pokt-network/poktroll/pkg/crypto"
"github.com/pokt-network/poktroll/pkg/crypto/protocol"
"github.com/pokt-network/poktroll/pkg/relayer"
testutilkeyring "github.com/pokt-network/poktroll/testutil/testkeyring"
servicetypes "github.com/pokt-network/poktroll/x/service/types"
Expand Down Expand Up @@ -56,7 +57,7 @@ func NewUnsignedMinedRelay(
relayBz, err := relay.Marshal()
require.NoError(t, err)

relayHashArr := servicetypes.GetHashFromBytes(relayBz)
relayHashArr := protocol.GetHashFromBytes(relayBz)
relayHash := relayHashArr[:]

return &relayer.MinedRelay{
Expand Down Expand Up @@ -110,7 +111,7 @@ func NewSignedMinedRelay(
relayBz, err := relay.Marshal()
require.NoError(t, err)

relayHashArr := servicetypes.GetHashFromBytes(relayBz)
relayHashArr := protocol.GetHashFromBytes(relayBz)
relayHash := relayHashArr[:]

return &relayer.MinedRelay{
Expand Down
38 changes: 24 additions & 14 deletions x/proof/keeper/msg_server_submit_proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package keeper
import (
"bytes"
"context"
"crypto/sha256"
"fmt"

cosmoscryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
Expand Down Expand Up @@ -207,7 +208,7 @@ func (k msgServer) SubmitProof(
params := k.GetParams(ctx)

// Verify the relay difficulty is above the minimum required to earn rewards.
if err = validateMiningDifficulty(relayBz, params.MinRelayDifficultyBits); err != nil {
if err = validateRelayDifficulty(relayBz, params.RelayDifficultyTargetHash); err != nil {
return nil, status.Error(codes.FailedPrecondition, err.Error())
}
logger.Debug("successfully validated relay mining difficulty")
Expand All @@ -224,11 +225,6 @@ func (k msgServer) SubmitProof(
}
logger.Debug("successfully validated proof path")

// Verify the relay's difficulty.
if err = validateMiningDifficulty(relayBz, params.MinRelayDifficultyBits); err != nil {
return nil, status.Error(codes.FailedPrecondition, err.Error())
}

// Retrieve the corresponding claim for the proof submitted so it can be
// used in the proof validation below.
claim, err = k.queryAndValidateClaimForProof(ctx, msg)
Expand Down Expand Up @@ -447,21 +443,35 @@ func verifyClosestProof(
return nil
}

// validateMiningDifficulty ensures that the relay's mining difficulty meets the
// validateRelayDifficulty ensures that the relay's mining difficulty meets the
// required minimum threshold.
// TODO_TECHDEBT: Factor out the relay mining difficulty validation into a shared
// function that can be used by both the proof and the miner packages.
func validateMiningDifficulty(relayBz []byte, minRelayDifficultyBits uint64) error {
relayHash := servicetypes.GetHashFromBytes(relayBz)
relayDifficultyBits := protocol.CountHashDifficultyBits(relayHash)
func validateRelayDifficulty(relayBz []byte, targetHash []byte) error {
relayHash := protocol.GetHashFromBytes(relayBz)

if len(targetHash) != sha256.Size {
return types.ErrProofInvalidRelay.Wrapf(
"invalid RelayDifficultyTargetHash: (%x); length wanted: %d; got: %d",
targetHash,
32,
len(targetHash),
)
}

var targetHashArr [sha256.Size]byte
copy(targetHashArr[:], targetHash)

// TODO_MAINNET: Devise a test that tries to attack the network and ensure that there
// is sufficient telemetry.
if uint64(relayDifficultyBits) < minRelayDifficultyBits {
// NB: If relayHash > targetHash, then the difficulty is less than the target difficulty.
if bytes.Compare(relayHash[:], targetHash[:]) == 1 {
relayDifficulty := protocol.GetDifficultyFromHash(relayHash)
targetDifficulty := protocol.GetDifficultyFromHash(targetHashArr)
return types.ErrProofInvalidRelay.Wrapf(
"relay difficulty %d is less than the minimum difficulty %d",
relayDifficultyBits,
minRelayDifficultyBits,
"relay difficulty %d is less than the target difficulty %d",
relayDifficulty,
targetDifficulty,
)
}

Expand Down
Loading

0 comments on commit ffb6902

Please sign in to comment.