Skip to content

Commit 7cfe086

Browse files
author
Larry Ruane
committed
Change hash representation from slice to 32-byte array
This change is non-functional, just a clean-up, reducing tech debt. This codebase manipulates many 32-byte hashes, such as block hashes, transaction IDs, and merkle roots. These should always have been represented as fixed-size 32-byte arrays rather than variable-length slices. This prevents bugs (a slice that's supposed to be of length 32 bytes could be a different length) and makes assigning, comparing, function argument passing, and function return value passing simpler and less fragile. The new hash type, hash32.T, which is defined as [32]byte (32-byte array of bytes), can be treated like any simple variable, such as an integer. A slice, in contrast, is like a string; it's really a structure that includes a length, capacity, and pointer to separately-allocated memory to hold the elements of the slice. The only point of friction is that protobuf doesn't support fixed-sized arrays, only (in effect) slices. So conversion must be done in each direction.
1 parent 339b6d3 commit 7cfe086

18 files changed

+316
-262
lines changed

common/cache.go

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"path/filepath"
1515
"sync"
1616

17+
"github.com/zcash/lightwalletd/hash32"
1718
"github.com/zcash/lightwalletd/walletrpc"
1819
"google.golang.org/protobuf/proto"
1920
)
@@ -22,10 +23,10 @@ import (
2223
type BlockCache struct {
2324
lengthsName, blocksName string // pathnames
2425
lengthsFile, blocksFile *os.File
25-
starts []int64 // Starting offset of each block within blocksFile
26-
firstBlock int // height of the first block in the cache (usually Sapling activation)
27-
nextBlock int // height of the first block not in the cache
28-
latestHash []byte // hash of the most recent (highest height) block, for detecting reorgs.
26+
starts []int64 // Starting offset of each block within blocksFile
27+
firstBlock int // height of the first block in the cache (usually Sapling activation)
28+
nextBlock int // height of the first block not in the cache
29+
latestHash hash32.T // hash of the most recent (highest height) block, for detecting reorgs.
2930
mutex sync.RWMutex
3031
}
3132

@@ -44,18 +45,18 @@ func (c *BlockCache) GetFirstHeight() int {
4445
}
4546

4647
// GetLatestHash returns the hash (block ID) of the most recent (highest) known block.
47-
func (c *BlockCache) GetLatestHash() []byte {
48+
func (c *BlockCache) GetLatestHash() hash32.T {
4849
c.mutex.RLock()
4950
defer c.mutex.RUnlock()
5051
return c.latestHash
5152
}
5253

5354
// HashMatch indicates if the given prev-hash matches the most recent block's hash
5455
// so reorgs can be detected.
55-
func (c *BlockCache) HashMatch(prevhash []byte) bool {
56+
func (c *BlockCache) HashMatch(prevhash hash32.T) bool {
5657
c.mutex.RLock()
5758
defer c.mutex.RUnlock()
58-
return c.latestHash == nil || bytes.Equal(c.latestHash, prevhash)
59+
return c.latestHash == hash32.Nil || c.latestHash == prevhash
5960
}
6061

6162
// Make the block at the given height the lowest height that we don't have.
@@ -167,7 +168,7 @@ func (c *BlockCache) readBlock(height int) *walletrpc.CompactBlock {
167168

168169
// Caller should hold c.mutex.Lock().
169170
func (c *BlockCache) setLatestHash() {
170-
c.latestHash = nil
171+
c.latestHash = hash32.Nil
171172
// There is at least one block; get the last block's hash
172173
if c.nextBlock > c.firstBlock {
173174
// At least one block remains; get the last block's hash
@@ -176,8 +177,7 @@ func (c *BlockCache) setLatestHash() {
176177
c.recoverFromCorruption(c.nextBlock - 10000)
177178
return
178179
}
179-
c.latestHash = make([]byte, len(block.Hash))
180-
copy(c.latestHash, block.Hash)
180+
c.latestHash = hash32.T(block.Hash)
181181
}
182182
}
183183

@@ -312,10 +312,7 @@ func (c *BlockCache) Add(height int, block *walletrpc.CompactBlock) error {
312312
offset := c.starts[len(c.starts)-1]
313313
c.starts = append(c.starts, offset+int64(len(data)+8))
314314

315-
if c.latestHash == nil {
316-
c.latestHash = make([]byte, len(block.Hash))
317-
}
318-
copy(c.latestHash, block.Hash)
315+
c.latestHash = hash32.T(block.Hash)
319316
c.nextBlock++
320317
// Invariant: m[firstBlock..nextBlock) are valid.
321318
return nil

common/cache_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os"
1010
"testing"
1111

12+
"github.com/zcash/lightwalletd/hash32"
1213
"github.com/zcash/lightwalletd/parser"
1314
"github.com/zcash/lightwalletd/walletrpc"
1415
)
@@ -84,7 +85,7 @@ func TestCache(t *testing.T) {
8485

8586
// Reorg to before the first block moves back to only the first block
8687
cache.Reorg(289459)
87-
if cache.latestHash != nil {
88+
if cache.latestHash != hash32.Nil {
8889
t.Fatal("unexpected latestHash, should be nil")
8990
}
9091
if cache.nextBlock != 289460 {

common/common.go

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"time"
1515

1616
"github.com/sirupsen/logrus"
17+
"github.com/zcash/lightwalletd/hash32"
1718
"github.com/zcash/lightwalletd/parser"
1819
"github.com/zcash/lightwalletd/walletrpc"
1920
)
@@ -369,12 +370,12 @@ func getBlockFromRPC(height int) (*walletrpc.CompactBlock, error) {
369370
return nil, errors.New("received unexpected height block")
370371
}
371372
for i, t := range block.Transactions() {
372-
txid, err := hex.DecodeString(block1.Tx[i])
373+
txid, err := hash32.Decode(block1.Tx[i])
373374
if err != nil {
374375
return nil, fmt.Errorf("error decoding getblock txid: %w", err)
375376
}
376377
// convert from big-endian
377-
t.SetTxID(parser.Reverse(txid))
378+
t.SetTxID(hash32.Reverse(txid))
378379
}
379380
r := block.ToCompact()
380381
r.ChainMetadata.SaplingCommitmentTreeSize = block1.Trees.Sapling.Size
@@ -426,13 +427,13 @@ func BlockIngestor(c *BlockCache, rep int) {
426427
if err != nil {
427428
Log.Fatal("bad getbestblockhash return:", err, result)
428429
}
429-
lastBestBlockHash, err := hex.DecodeString(hashHex)
430+
lastBestBlockHash, err := hash32.Decode(hashHex)
430431
if err != nil {
431432
Log.Fatal("error decoding getbestblockhash", err, hashHex)
432433
}
433434

434435
height := c.GetNextHeight()
435-
if string(lastBestBlockHash) == string(parser.Reverse(c.GetLatestHash())) {
436+
if lastBestBlockHash == hash32.Reverse(c.GetLatestHash()) {
436437
// Synced
437438
c.Sync()
438439
if lastHeightLogged != height-1 {
@@ -450,14 +451,14 @@ func BlockIngestor(c *BlockCache, rep int) {
450451
Time.Sleep(8 * time.Second)
451452
continue
452453
}
453-
if block != nil && c.HashMatch(block.PrevHash) {
454+
if block != nil && c.HashMatch(hash32.T(block.PrevHash)) {
454455
if err = c.Add(height, block); err != nil {
455456
Log.Fatal("Cache add failed:", err)
456457
}
457458
// Don't log these too often.
458459
if DarksideEnabled || Time.Now().Sub(lastLog).Seconds() >= 4 {
459460
lastLog = Time.Now()
460-
Log.Info("Adding block to cache ", height, " ", displayHash(block.Hash))
461+
Log.Info("Adding block to cache ", height, " ", displayHash(hash32.T(block.Hash)))
461462
}
462463
continue
463464
}
@@ -537,29 +538,29 @@ func GetBlockRange(cache *BlockCache, blockOut chan<- *walletrpc.CompactBlock, e
537538
// the meanings of the `Height` field of the `RawTransaction` type are as
538539
// follows:
539540
//
540-
// * height 0: the transaction is in the mempool
541-
// * height 0xffffffffffffffff: the transaction has been mined on a fork that
542-
// is not currently the main chain
543-
// * any other height: the transaction has been mined in the main chain at the
544-
// given height
541+
// - height 0: the transaction is in the mempool
542+
// - height 0xffffffffffffffff: the transaction has been mined on a fork that
543+
// is not currently the main chain
544+
// - any other height: the transaction has been mined in the main chain at the
545+
// given height
545546
func ParseRawTransaction(message json.RawMessage) (*walletrpc.RawTransaction, error) {
546-
// Many other fields are returned, but we need only these two.
547-
var txinfo ZcashdRpcReplyGetrawtransaction
548-
err := json.Unmarshal(message, &txinfo)
549-
if err != nil {
550-
return nil, err
551-
}
552-
txBytes, err := hex.DecodeString(txinfo.Hex)
553-
if err != nil {
554-
return nil, err
555-
}
547+
// Many other fields are returned, but we need only these two.
548+
var txinfo ZcashdRpcReplyGetrawtransaction
549+
err := json.Unmarshal(message, &txinfo)
550+
if err != nil {
551+
return nil, err
552+
}
553+
txBytes, err := hex.DecodeString(txinfo.Hex)
554+
if err != nil {
555+
return nil, err
556+
}
556557

557-
return &walletrpc.RawTransaction{
558-
Data: txBytes,
559-
Height: uint64(txinfo.Height),
560-
}, nil
558+
return &walletrpc.RawTransaction{
559+
Data: txBytes,
560+
Height: uint64(txinfo.Height),
561+
}, nil
561562
}
562563

563-
func displayHash(hash []byte) string {
564-
return hex.EncodeToString(parser.Reverse(hash))
564+
func displayHash(hash hash32.T) string {
565+
return hash32.Encode(hash32.Reverse(hash))
565566
}

0 commit comments

Comments
 (0)