Skip to content

Commit

Permalink
Merge pull request #90 from kcalvinalvin/2023-12-19-add-packed-hashes
Browse files Browse the repository at this point in the history
chainhash: add packed hashes support
  • Loading branch information
kcalvinalvin committed Dec 20, 2023
2 parents 2fe60c0 + a63ea84 commit 8aafeb6
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 0 deletions.
68 changes: 68 additions & 0 deletions chaincfg/chainhash/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
package chainhash

import (
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"math"
)

// HashSize of array used to store hashes. See Hash.
Expand Down Expand Up @@ -147,3 +149,69 @@ func Decode(dst *Hash, src string) error {

return nil
}

// Uint64sToPackedHashes packs the passed in uint64s into the 32 byte hashes. 4 uint64s are packed into
// each 32 byte hash and if there's leftovers, it's filled with maxuint64.
func Uint64sToPackedHashes(ints []uint64) []Hash {
// 4 uint64s fit into a 32 byte slice. For len(ints) < 4, count is 0.
count := len(ints) / 4

// If there's leftovers, we need to allocate 1 more.
if len(ints)%4 != 0 {
count++
}

hashes := make([]Hash, count)
hashIdx := 0
for i := range ints {
// Move on to the next hash after putting in 4 uint64s into a hash.
if i != 0 && i%4 == 0 {
hashIdx++
}

// 8 is the size of a uint64.
start := (i % 4) * 8
binary.LittleEndian.PutUint64(hashes[hashIdx][start:start+8], ints[i])
}

// Pad the last hash with math.MaxUint64 if needed. We check this by seeing
// if modulo 4 doesn't equate 0.
if len(ints)%4 != 0 {
// Start at the end.
end := HashSize

// Get the count of how many empty uint64 places we should pad.
padAmount := 4 - len(ints)%4
for i := 0; i < padAmount; i++ {
// 8 is the size of a uint64.
binary.LittleEndian.PutUint64(hashes[len(hashes)-1][end-8:end], math.MaxUint64)
end -= 8
}
}

return hashes
}

// PackedHashesToUint64 returns the uint64s in the packed hashes as a slice of uint64s.
func PackedHashesToUint64(hashes []Hash) []uint64 {
ints := make([]uint64, 0, len(hashes)*4)
for i := range hashes {
// We pack 4 ints per hash.
for j := 0; j < 4; j++ {
// Offset for each int should be calculated by multiplying by
// the size of a uint64.
start := j * 8
read := binary.LittleEndian.Uint64(hashes[i][start : start+8])

// If we reach padded values, break.
if read == math.MaxUint64 {
break
}

// Otherwise we append the read uint64 to the slice.
ints = append(ints, read)
}
}

return ints
}
23 changes: 23 additions & 0 deletions chaincfg/chainhash/hash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"bytes"
"encoding/hex"
"encoding/json"
"reflect"
"testing"
)

Expand Down Expand Up @@ -220,3 +221,25 @@ func TestHashJsonMarshal(t *testing.T) {
t.Errorf("String: wrong hash string - got %v, want %v", newHash.String(), hashStr)
}
}

func TestPackedHashes(t *testing.T) {
tests := []struct {
uints []uint64
}{
{uints: []uint64{0, 1, 2, 3}},
{uints: []uint64{0, 1, 2, 3, 4}},
{uints: []uint64{0, 1, 2, 3, 4, 5}},
{uints: []uint64{0, 1, 2, 3, 4, 5, 6}},
{uints: []uint64{0, 1, 2, 3, 4, 5, 6, 7}},
{uints: []uint64{11, 22, 33, 44, 55}},
}

for _, test := range tests {
hashes := Uint64sToPackedHashes(test.uints)
got := PackedHashesToUint64(hashes)

if !reflect.DeepEqual(test.uints, got) {
t.Fatalf("expected %v but got %v", test.uints, got)
}
}
}

0 comments on commit 8aafeb6

Please sign in to comment.