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") } }