255 lines
6.4 KiB
Go
255 lines
6.4 KiB
Go
package common
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"testing"
|
|
|
|
"github.com/zcash/lightwalletd/parser"
|
|
"github.com/zcash/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()
|
|
_, err = block.ParseFromSlice(blockData)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
compacts = append(compacts, block.ToCompact())
|
|
}
|
|
|
|
// initially empty cache
|
|
if cache.GetLatestHeight() != -1 {
|
|
t.Fatal("unexpected GetLatestHeight")
|
|
}
|
|
|
|
// Test handling an invalid block (nil will do)
|
|
reorg, err := cache.Add(21, nil)
|
|
if err == nil {
|
|
t.Error("expected error:", err)
|
|
}
|
|
if reorg {
|
|
t.Fatal("unexpected reorg")
|
|
}
|
|
|
|
// normal, sunny-day case, 6 blocks, add as blocks 10-15
|
|
for i, compact := range compacts {
|
|
reorg, err = cache.Add(10+i, compact)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if reorg {
|
|
t.Fatal("unexpected reorg")
|
|
}
|
|
if cache.GetLatestHeight() != 10+i {
|
|
t.Fatal("unexpected GetLatestHeight")
|
|
}
|
|
// 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
|
|
reorg, err = 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)
|
|
reorg, err = 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.GetLatestHeight() != 15 {
|
|
t.Fatal("unexpected GetLatestHeight")
|
|
}
|
|
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.
|
|
reorg, err = 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.GetLatestHeight() != 14 {
|
|
t.Fatal("unexpected GetLatestHeight")
|
|
}
|
|
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).
|
|
reorg, err = 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.GetLatestHeight() != 13 {
|
|
t.Fatal("unexpected GetLatestHeight")
|
|
}
|
|
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
|
|
reorg, err = cache.Add(14, compacts[4])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if reorg {
|
|
t.Fatal("unexpected reorg")
|
|
}
|
|
if cache.GetLatestHeight() != 14 {
|
|
t.Fatal("unexpected GetLatestHeight")
|
|
}
|
|
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")
|
|
}
|
|
reorg, err = cache.Add(10, compacts[0])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if reorg {
|
|
t.Fatal("unexpected reorg")
|
|
}
|
|
if cache.GetLatestHeight() != 10 {
|
|
t.Fatal("unexpected GetLatestHeight")
|
|
}
|
|
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.
|
|
reorg, err = cache.Add(20, compacts[0])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if reorg {
|
|
t.Fatal("unexpected reorg")
|
|
}
|
|
if cache.GetLatestHeight() != 20 {
|
|
t.Fatal("unexpected GetLatestHeight")
|
|
}
|
|
if int(cache.Get(20).Height) != 289460 {
|
|
t.Fatal("unexpected Get")
|
|
}
|
|
if len(cache.m) != 1 {
|
|
t.Fatal("unexpected number of cache entries")
|
|
}
|
|
// the cache deleted block 15 (it's definitely wrong)
|
|
}
|