diff --git a/pkg/relayer/miner/miner.go b/pkg/relayer/miner/miner.go index 3537128c5..21d706f2f 100644 --- a/pkg/relayer/miner/miner.go +++ b/pkg/relayer/miner/miner.go @@ -21,7 +21,7 @@ var ( // TODO_BLOCKER: query on-chain governance params once available. // Setting this to 0 to effectively disables mining for now. // I.e., all relays are added to the tree. - defaultRelayDifficulty = 0 + defaultRelayDifficultyBits = 0 ) // Miner is responsible for observing servedRelayObs, hashing and checking the @@ -107,7 +107,7 @@ func (mnr *miner) mapMineRelay( relayHash := mnr.hash(relayBz) // The relay IS NOT volume / reward applicable - if !protocol.BytesDifficultyGreaterThan(relayHash, defaultRelayDifficulty) { + if protocol.MustCountDifficultyBits(relayHash) < defaultRelayDifficultyBits { return either.Success[*relayer.MinedRelay](nil), true } diff --git a/pkg/relayer/protocol/difficulty.go b/pkg/relayer/protocol/difficulty.go index 4743d546d..a33c4bac6 100644 --- a/pkg/relayer/protocol/difficulty.go +++ b/pkg/relayer/protocol/difficulty.go @@ -1,17 +1,42 @@ package protocol import ( - "encoding/hex" - "strings" + "math/bits" ) // TODO_BLOCKER: Revisit this part of the algorithm after initial TestNet Launch. // TODO_TEST: Add extensive tests for the core relay mining business logic. -// BytesDifficultyGreaterThan determines if the bytes exceed a certain difficulty, and it -// is used to determine if a relay is volume applicable. See the spec for more details: https://github.com/pokt-network/pocket-network-protocol -func BytesDifficultyGreaterThan(bz []byte, compDifficultyBytes int) bool { - hexZerosPrefix := strings.Repeat("0", compDifficultyBytes*2) // 2 hex chars per byte. - hexBz := hex.EncodeToString(bz) - return strings.HasPrefix(hexBz, hexZerosPrefix) +// MustCountDifficultyBits returns the number of leading zero bits in the given +// byte slice. It panics if an error is encountered. +func MustCountDifficultyBits(bz []byte) int { + diff, err := CountDifficultyBits(bz) + if err != nil { + panic(err) + } + + return diff +} + +// CountDifficultyBits returns the number of leading zero bits in the given byte +// slice. It returns an error if the byte slice is all zero bits. +func CountDifficultyBits(bz []byte) (int, error) { + bzLen := len(bz) + + var zeroBits int + for byteIdx, byteValue := range bz { + if byteValue != 0 { + zeroBits = bits.LeadingZeros8(byteValue) + if zeroBits == 8 { + // we already checked that byteValue != 0. + return 0, ErrDifficulty.Wrap("impossible code path") + } + + // We have byteIdx bytes that are all 0s and one byte that has + // zeroBits number of leading 0 bits. + return (byteIdx)*8 + zeroBits, nil + } + } + + return 0, ErrDifficulty.Wrapf("difficulty matches bytes length: %d; bytes (hex): % x", bzLen, bz) } diff --git a/pkg/relayer/protocol/difficulty_test.go b/pkg/relayer/protocol/difficulty_test.go new file mode 100644 index 000000000..ae5548634 --- /dev/null +++ b/pkg/relayer/protocol/difficulty_test.go @@ -0,0 +1,56 @@ +package protocol_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/pokt-network/poktroll/pkg/relayer/protocol" +) + +func TestCountDifficultyBits(t *testing.T) { + tests := []struct { + bz []byte + difficulty int + }{ + { + bz: []byte{0b11111111, 255, 255, 255}, + difficulty: 0, + }, + { + bz: []byte{0b01111111, 255, 255, 255}, + difficulty: 1, + }, + { + bz: []byte{0, 255, 255, 255}, + difficulty: 8, + }, + { + bz: []byte{0, 0b01111111, 255, 255}, + difficulty: 9, + }, + { + bz: []byte{0, 0b00111111, 255, 255}, + difficulty: 10, + }, + { + bz: []byte{0, 0, 255, 255}, + difficulty: 16, + }, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("difficulty_%d_zero_bits", tt.difficulty), func(t *testing.T) { + actualDifficulty, err := protocol.CountDifficultyBits(tt.bz) + require.NoError(t, err) + require.Equal(t, tt.difficulty, actualDifficulty) + }) + } +} + +func TestCountDifficultyBits_Error(t *testing.T) { + _, err := protocol.CountDifficultyBits([]byte{0, 0, 0, 0}) + require.ErrorIs(t, err, protocol.ErrDifficulty) + require.ErrorContains(t, err, "difficulty matches bytes length") +} diff --git a/pkg/relayer/protocol/errors.go b/pkg/relayer/protocol/errors.go new file mode 100644 index 000000000..f578bf8e0 --- /dev/null +++ b/pkg/relayer/protocol/errors.go @@ -0,0 +1,8 @@ +package protocol + +import errorsmod "cosmossdk.io/errors" + +var ( + ErrDifficulty = errorsmod.New(codespace, 1, "difficulty error") + codespace = "relayer/protocol" +)