lightwalletd/common/cache_test.go

245 lines
6.2 KiB
Go

package common
import (
"encoding/hex"
"encoding/json"
"io/ioutil"
"testing"
"github.com/zcash-hackworks/lightwalletd/parser"
"github.com/zcash-hackworks/lightwalletd/walletrpc"
)
func TestCache(t *testing.T) {
type compactTest struct {
BlockHeight int `json:"block"`
BlockHash string `json:"hash"`
PrevHash string `json:"prev"`
Full string `json:"full"`
Compact string `json:"compact"`
}
var compactTests []compactTest
var compacts []*walletrpc.CompactBlock
blockJSON, err := ioutil.ReadFile("../testdata/compact_blocks.json")
if err != nil {
t.Fatal(err)
}
err = json.Unmarshal(blockJSON, &compactTests)
if err != nil {
t.Fatal(err)
}
cache := NewBlockCache(4)
// derive compact blocks from file data (setup, not part of the test)
for _, test := range compactTests {
blockData, _ := hex.DecodeString(test.Full)
block := parser.NewBlock()
blockData, err = block.ParseFromSlice(blockData)
if err != nil {
t.Fatal(err)
}
compacts = append(compacts, block.ToCompact())
}
// initially empty cache
if cache.GetLatestBlock() != -1 {
t.Fatal("unexpected GetLatestBlock")
}
// normal, sunny-day case, 6 blocks, add as blocks 10-15
for i, compact := range compacts {
err, reorg := cache.Add(10+i, compact)
if err != nil {
t.Fatal(err)
}
if reorg {
t.Fatal("unexpected reorg")
}
if cache.GetLatestBlock() != 10+i {
t.Fatal("unexpected GetLatestBlock")
}
// The test blocks start at height 289460
if int(cache.Get(10+i).Height) != 289460+i {
t.Fatal("unexpected block contents")
}
}
if len(cache.m) != 4 { // max entries is 4
t.Fatal("unexpected number of cache entries")
}
if cache.firstBlock != 16-4 {
t.Fatal("unexpected firstBlock")
}
if cache.nextBlock != 16 {
t.Fatal("unexpected nextBlock")
}
// No entries just before and just after the cache range
if cache.Get(11) != nil || cache.Get(16) != nil {
t.Fatal("unexpected Get")
}
// We can re-add the last block (with the same data) and
// that should just replace and not be considered a reorg
err, reorg := cache.Add(15, compacts[5])
if err != nil {
t.Fatal(err)
}
if reorg {
t.Fatal("unexpected reorg")
}
if len(cache.m) != 4 {
t.Fatal("unexpected number of blocks")
}
if cache.firstBlock != 16-4 {
t.Fatal("unexpected firstBlock")
}
if cache.nextBlock != 16 {
t.Fatal("unexpected nextBlock")
}
// Simulate a reorg by resubmitting as the next block, 16, any block with
// the wrote prev-hash (let's use the first, just because it's handy)
err, reorg = cache.Add(16, compacts[0])
if err != nil {
t.Fatal(err)
}
if !reorg {
t.Fatal("unexpected non-reorg")
}
// The cache shouldn't have changed in any way
if cache.Get(16) != nil {
t.Fatal("unexpected block 16 exists")
}
if cache.GetLatestBlock() != 15 {
t.Fatal("unexpected GetLatestBlock")
}
if int(cache.Get(15).Height) != 289460+5 {
t.Fatal("unexpected Get")
}
if len(cache.m) != 4 {
t.Fatal("unexpected number of cache entries")
}
// In response to the reorg being detected, we must back up until we
// reach a block that's before the reorg (where the chain split).
// Let's back up one block, to height 15, request it from zcashd,
// but let's say this block is from the new branch, so we haven't
// gone back far enough, so this will still be disallowed.
err, reorg = cache.Add(15, compacts[0])
if err != nil {
t.Fatal(err)
}
if !reorg {
t.Fatal("unexpected non-reorg")
}
// the cache deleted block 15 (it's definitely wrong)
if cache.Get(15) != nil {
t.Fatal("unexpected block 15 exists")
}
if cache.GetLatestBlock() != 14 {
t.Fatal("unexpected GetLatestBlock")
}
if int(cache.Get(14).Height) != 289460+4 {
t.Fatal("unexpected Get")
}
// now only 3 entries (12-14)
if len(cache.m) != 3 {
t.Fatal("unexpected number of cache entries")
}
// Back up a couple more, try to re-add height 13, and suppose
// that's before the split (for example, there were two 14s).
// (In this test, we're replacing 13 with the same block; in
// real life, we'd be replacing it with a different version of
// 13 that has the same prev-hash).
err, reorg = cache.Add(13, compacts[3])
if err != nil {
t.Fatal(err)
}
if reorg {
t.Fatal("unexpected reorg")
}
// 13 was replaced (with the same block), but that means
// everything after 13 is deleted
if cache.Get(14) != nil {
t.Fatal("unexpected block 14 exists")
}
if cache.GetLatestBlock() != 13 {
t.Fatal("unexpected GetLatestBlock")
}
if int(cache.Get(13).Height) != 289460+3 {
t.Fatal("unexpected Get")
}
if int(cache.Get(12).Height) != 289460+2 {
t.Fatal("unexpected Get")
}
// down to 2 entries (12-13)
if len(cache.m) != 2 {
t.Fatal("unexpected number of cache entries")
}
// Now we can continue forward from here
err, reorg = cache.Add(14, compacts[4])
if err != nil {
t.Fatal(err)
}
if reorg {
t.Fatal("unexpected reorg")
}
if cache.GetLatestBlock() != 14 {
t.Fatal("unexpected GetLatestBlock")
}
if int(cache.Get(14).Height) != 289460+4 {
t.Fatal("unexpected Get")
}
if len(cache.m) != 3 {
t.Fatal("unexpected number of cache entries")
}
// It's possible, although unlikely, that after a reorg is detected,
// we back up so much that we're before the start of the cache
// (especially if the cache is very small). This should remove the
// entire cache before adding the new entry.
if cache.firstBlock != 12 {
t.Fatal("unexpected firstBlock")
}
err, reorg = cache.Add(10, compacts[0])
if err != nil {
t.Fatal(err)
}
if reorg {
t.Fatal("unexpected reorg")
}
if cache.GetLatestBlock() != 10 {
t.Fatal("unexpected GetLatestBlock")
}
if int(cache.Get(10).Height) != 289460+0 {
t.Fatal("unexpected Get")
}
if len(cache.m) != 1 {
t.Fatal("unexpected number of cache entries")
}
// Another weird case (not currently possible) is adding a block at
// a height that is not one higher than the current latest block.
// This should remove the entire cache before adding the new entry.
err, reorg = cache.Add(20, compacts[0])
if err != nil {
t.Fatal(err)
}
if reorg {
t.Fatal("unexpected reorg")
}
if cache.GetLatestBlock() != 20 {
t.Fatal("unexpected GetLatestBlock")
}
if int(cache.Get(20).Height) != 289460 {
t.Fatal("unexpected Get")
}
if len(cache.m) != 1 {
t.Fatal("unexpected number of cache entries")
}
}