Skip to content

Commit 8aafeb6

Browse files
authored
Merge pull request #90 from kcalvinalvin/2023-12-19-add-packed-hashes
chainhash: add packed hashes support
2 parents 2fe60c0 + a63ea84 commit 8aafeb6

File tree

2 files changed

+91
-0
lines changed

2 files changed

+91
-0
lines changed

chaincfg/chainhash/hash.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
package chainhash
77

88
import (
9+
"encoding/binary"
910
"encoding/hex"
1011
"encoding/json"
1112
"fmt"
13+
"math"
1214
)
1315

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

148150
return nil
149151
}
152+
153+
// Uint64sToPackedHashes packs the passed in uint64s into the 32 byte hashes. 4 uint64s are packed into
154+
// each 32 byte hash and if there's leftovers, it's filled with maxuint64.
155+
func Uint64sToPackedHashes(ints []uint64) []Hash {
156+
// 4 uint64s fit into a 32 byte slice. For len(ints) < 4, count is 0.
157+
count := len(ints) / 4
158+
159+
// If there's leftovers, we need to allocate 1 more.
160+
if len(ints)%4 != 0 {
161+
count++
162+
}
163+
164+
hashes := make([]Hash, count)
165+
hashIdx := 0
166+
for i := range ints {
167+
// Move on to the next hash after putting in 4 uint64s into a hash.
168+
if i != 0 && i%4 == 0 {
169+
hashIdx++
170+
}
171+
172+
// 8 is the size of a uint64.
173+
start := (i % 4) * 8
174+
binary.LittleEndian.PutUint64(hashes[hashIdx][start:start+8], ints[i])
175+
}
176+
177+
// Pad the last hash with math.MaxUint64 if needed. We check this by seeing
178+
// if modulo 4 doesn't equate 0.
179+
if len(ints)%4 != 0 {
180+
// Start at the end.
181+
end := HashSize
182+
183+
// Get the count of how many empty uint64 places we should pad.
184+
padAmount := 4 - len(ints)%4
185+
for i := 0; i < padAmount; i++ {
186+
// 8 is the size of a uint64.
187+
binary.LittleEndian.PutUint64(hashes[len(hashes)-1][end-8:end], math.MaxUint64)
188+
end -= 8
189+
}
190+
}
191+
192+
return hashes
193+
}
194+
195+
// PackedHashesToUint64 returns the uint64s in the packed hashes as a slice of uint64s.
196+
func PackedHashesToUint64(hashes []Hash) []uint64 {
197+
ints := make([]uint64, 0, len(hashes)*4)
198+
for i := range hashes {
199+
// We pack 4 ints per hash.
200+
for j := 0; j < 4; j++ {
201+
// Offset for each int should be calculated by multiplying by
202+
// the size of a uint64.
203+
start := j * 8
204+
read := binary.LittleEndian.Uint64(hashes[i][start : start+8])
205+
206+
// If we reach padded values, break.
207+
if read == math.MaxUint64 {
208+
break
209+
}
210+
211+
// Otherwise we append the read uint64 to the slice.
212+
ints = append(ints, read)
213+
}
214+
}
215+
216+
return ints
217+
}

chaincfg/chainhash/hash_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"bytes"
99
"encoding/hex"
1010
"encoding/json"
11+
"reflect"
1112
"testing"
1213
)
1314

@@ -220,3 +221,25 @@ func TestHashJsonMarshal(t *testing.T) {
220221
t.Errorf("String: wrong hash string - got %v, want %v", newHash.String(), hashStr)
221222
}
222223
}
224+
225+
func TestPackedHashes(t *testing.T) {
226+
tests := []struct {
227+
uints []uint64
228+
}{
229+
{uints: []uint64{0, 1, 2, 3}},
230+
{uints: []uint64{0, 1, 2, 3, 4}},
231+
{uints: []uint64{0, 1, 2, 3, 4, 5}},
232+
{uints: []uint64{0, 1, 2, 3, 4, 5, 6}},
233+
{uints: []uint64{0, 1, 2, 3, 4, 5, 6, 7}},
234+
{uints: []uint64{11, 22, 33, 44, 55}},
235+
}
236+
237+
for _, test := range tests {
238+
hashes := Uint64sToPackedHashes(test.uints)
239+
got := PackedHashesToUint64(hashes)
240+
241+
if !reflect.DeepEqual(test.uints, got) {
242+
t.Fatalf("expected %v but got %v", test.uints, got)
243+
}
244+
}
245+
}

0 commit comments

Comments
 (0)