github.com/btcsuite/btcd@v0.24.0/blockchain/utxocache_test.go (about)

     1  // Copyright (c) 2023 The btcsuite developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  package blockchain
     5  
     6  import (
     7  	"crypto/sha256"
     8  	"encoding/binary"
     9  	"fmt"
    10  	"path/filepath"
    11  	"reflect"
    12  	"sync"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/btcsuite/btcd/btcutil"
    17  	"github.com/btcsuite/btcd/chaincfg"
    18  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    19  	"github.com/btcsuite/btcd/database"
    20  	"github.com/btcsuite/btcd/database/ffldb"
    21  	"github.com/btcsuite/btcd/wire"
    22  )
    23  
    24  func TestMapSlice(t *testing.T) {
    25  	tests := []struct {
    26  		keys []wire.OutPoint
    27  	}{
    28  		{
    29  			keys: func() []wire.OutPoint {
    30  				outPoints := make([]wire.OutPoint, 1000)
    31  				for i := uint32(0); i < uint32(len(outPoints)); i++ {
    32  					var buf [4]byte
    33  					binary.BigEndian.PutUint32(buf[:], i)
    34  					hash := sha256.Sum256(buf[:])
    35  
    36  					op := wire.OutPoint{Hash: hash, Index: i}
    37  					outPoints[i] = op
    38  				}
    39  				return outPoints
    40  			}(),
    41  		},
    42  	}
    43  
    44  	for _, test := range tests {
    45  		m := make(map[wire.OutPoint]*UtxoEntry)
    46  
    47  		maxSize := calculateRoughMapSize(1000, bucketSize)
    48  
    49  		maxEntriesFirstMap := 500
    50  		ms1 := make(map[wire.OutPoint]*UtxoEntry, maxEntriesFirstMap)
    51  		ms := mapSlice{
    52  			maps:                []map[wire.OutPoint]*UtxoEntry{ms1},
    53  			maxEntries:          []int{maxEntriesFirstMap},
    54  			maxTotalMemoryUsage: uint64(maxSize),
    55  		}
    56  
    57  		for _, key := range test.keys {
    58  			m[key] = nil
    59  			ms.put(key, nil, 0)
    60  		}
    61  
    62  		// Put in the same elements twice to test that the map slice won't hold duplicates.
    63  		for _, key := range test.keys {
    64  			m[key] = nil
    65  			ms.put(key, nil, 0)
    66  		}
    67  
    68  		if len(m) != ms.length() {
    69  			t.Fatalf("expected len of %d, got %d", len(m), ms.length())
    70  		}
    71  
    72  		for _, key := range test.keys {
    73  			expected, found := m[key]
    74  			if !found {
    75  				t.Fatalf("expected key %s to exist in the go map", key.String())
    76  			}
    77  
    78  			got, found := ms.get(key)
    79  			if !found {
    80  				t.Fatalf("expected key %s to exist in the map slice", key.String())
    81  			}
    82  
    83  			if !reflect.DeepEqual(got, expected) {
    84  				t.Fatalf("expected value of %v, got %v", expected, got)
    85  			}
    86  		}
    87  	}
    88  }
    89  
    90  // TestMapsliceConcurrency just tests that the mapslice won't result in a panic
    91  // on concurrent access.
    92  func TestMapsliceConcurrency(t *testing.T) {
    93  	tests := []struct {
    94  		keys []wire.OutPoint
    95  	}{
    96  		{
    97  			keys: func() []wire.OutPoint {
    98  				outPoints := make([]wire.OutPoint, 10000)
    99  				for i := uint32(0); i < uint32(len(outPoints)); i++ {
   100  					var buf [4]byte
   101  					binary.BigEndian.PutUint32(buf[:], i)
   102  					hash := sha256.Sum256(buf[:])
   103  
   104  					op := wire.OutPoint{Hash: hash, Index: i}
   105  					outPoints[i] = op
   106  				}
   107  				return outPoints
   108  			}(),
   109  		},
   110  	}
   111  
   112  	for _, test := range tests {
   113  		maxSize := calculateRoughMapSize(1000, bucketSize)
   114  
   115  		maxEntriesFirstMap := 500
   116  		ms1 := make(map[wire.OutPoint]*UtxoEntry, maxEntriesFirstMap)
   117  		ms := mapSlice{
   118  			maps:                []map[wire.OutPoint]*UtxoEntry{ms1},
   119  			maxEntries:          []int{maxEntriesFirstMap},
   120  			maxTotalMemoryUsage: uint64(maxSize),
   121  		}
   122  
   123  		var wg sync.WaitGroup
   124  
   125  		wg.Add(1)
   126  		go func(m *mapSlice, keys []wire.OutPoint) {
   127  			defer wg.Done()
   128  			for i := 0; i < 5000; i++ {
   129  				m.put(keys[i], nil, 0)
   130  			}
   131  		}(&ms, test.keys)
   132  
   133  		wg.Add(1)
   134  		go func(m *mapSlice, keys []wire.OutPoint) {
   135  			defer wg.Done()
   136  			for i := 5000; i < 10000; i++ {
   137  				m.put(keys[i], nil, 0)
   138  			}
   139  		}(&ms, test.keys)
   140  
   141  		wg.Add(1)
   142  		go func(m *mapSlice) {
   143  			defer wg.Done()
   144  			for i := 0; i < 10000; i++ {
   145  				m.size()
   146  			}
   147  		}(&ms)
   148  
   149  		wg.Add(1)
   150  		go func(m *mapSlice) {
   151  			defer wg.Done()
   152  			for i := 0; i < 10000; i++ {
   153  				m.length()
   154  			}
   155  		}(&ms)
   156  
   157  		wg.Add(1)
   158  		go func(m *mapSlice, keys []wire.OutPoint) {
   159  			defer wg.Done()
   160  			for i := 0; i < 10000; i++ {
   161  				m.get(keys[i])
   162  			}
   163  		}(&ms, test.keys)
   164  
   165  		wg.Add(1)
   166  		go func(m *mapSlice, keys []wire.OutPoint) {
   167  			defer wg.Done()
   168  			for i := 0; i < 5000; i++ {
   169  				m.delete(keys[i])
   170  			}
   171  		}(&ms, test.keys)
   172  
   173  		wg.Wait()
   174  	}
   175  }
   176  
   177  // getValidP2PKHScript returns a valid P2PKH script.  Useful as unspendables cannot be
   178  // added to the cache.
   179  func getValidP2PKHScript() []byte {
   180  	validP2PKHScript := []byte{
   181  		// OP_DUP
   182  		0x76,
   183  		// OP_HASH160
   184  		0xa9,
   185  		// OP_DATA_20
   186  		0x14,
   187  		// <20-byte pubkey hash>
   188  		0xf0, 0x7a, 0xb8, 0xce, 0x72, 0xda, 0x4e, 0x76,
   189  		0x0b, 0x74, 0x7d, 0x48, 0xd6, 0x65, 0xec, 0x96,
   190  		0xad, 0xf0, 0x24, 0xf5,
   191  		// OP_EQUALVERIFY
   192  		0x88,
   193  		// OP_CHECKSIG
   194  		0xac,
   195  	}
   196  	return validP2PKHScript
   197  }
   198  
   199  // outpointFromInt generates an outpoint from an int by hashing the int and making
   200  // the given int the index.
   201  func outpointFromInt(i int) wire.OutPoint {
   202  	// Boilerplate to create an outpoint.
   203  	var buf [4]byte
   204  	binary.BigEndian.PutUint32(buf[:], uint32(i))
   205  	hash := sha256.Sum256(buf[:])
   206  	return wire.OutPoint{Hash: hash, Index: uint32(i)}
   207  }
   208  
   209  func TestUtxoCacheEntrySize(t *testing.T) {
   210  	type block struct {
   211  		txOuts []*wire.TxOut
   212  		outOps []wire.OutPoint
   213  		txIns  []*wire.TxIn
   214  	}
   215  	tests := []struct {
   216  		name         string
   217  		blocks       []block
   218  		expectedSize uint64
   219  	}{
   220  		{
   221  			name: "one entry",
   222  			blocks: func() []block {
   223  				return []block{
   224  					{
   225  						txOuts: []*wire.TxOut{
   226  							{Value: 10000, PkScript: getValidP2PKHScript()},
   227  						},
   228  						outOps: []wire.OutPoint{
   229  							outpointFromInt(0),
   230  						},
   231  					},
   232  				}
   233  			}(),
   234  			expectedSize: pubKeyHashLen + baseEntrySize,
   235  		},
   236  		{
   237  			name: "10 entries, 4 spend",
   238  			blocks: func() []block {
   239  				blocks := make([]block, 0, 10)
   240  				for i := 0; i < 10; i++ {
   241  					op := outpointFromInt(i)
   242  
   243  					block := block{
   244  						txOuts: []*wire.TxOut{
   245  							{Value: 10000, PkScript: getValidP2PKHScript()},
   246  						},
   247  						outOps: []wire.OutPoint{
   248  							op,
   249  						},
   250  					}
   251  
   252  					// Spend all outs in blocks less than 4.
   253  					if i < 4 {
   254  						block.txIns = []*wire.TxIn{
   255  							{PreviousOutPoint: op},
   256  						}
   257  					}
   258  
   259  					blocks = append(blocks, block)
   260  				}
   261  				return blocks
   262  			}(),
   263  			// Multipled by 6 since we'll have 6 entries left.
   264  			expectedSize: (pubKeyHashLen + baseEntrySize) * 6,
   265  		},
   266  		{
   267  			name: "spend everything",
   268  			blocks: func() []block {
   269  				blocks := make([]block, 0, 500)
   270  				for i := 0; i < 500; i++ {
   271  					op := outpointFromInt(i)
   272  
   273  					block := block{
   274  						txOuts: []*wire.TxOut{
   275  							{Value: 1000, PkScript: getValidP2PKHScript()},
   276  						},
   277  						outOps: []wire.OutPoint{
   278  							op,
   279  						},
   280  					}
   281  
   282  					// Spend all outs in blocks less than 4.
   283  					block.txIns = []*wire.TxIn{
   284  						{PreviousOutPoint: op},
   285  					}
   286  
   287  					blocks = append(blocks, block)
   288  				}
   289  				return blocks
   290  			}(),
   291  			expectedSize: 0,
   292  		},
   293  	}
   294  
   295  	for _, test := range tests {
   296  		// Size is just something big enough so that the mapslice doesn't
   297  		// run out of memory.
   298  		s := newUtxoCache(nil, 1*1024*1024)
   299  
   300  		for height, block := range test.blocks {
   301  			for i, out := range block.txOuts {
   302  				s.addTxOut(block.outOps[i], out, true, int32(height))
   303  			}
   304  
   305  			for _, in := range block.txIns {
   306  				s.addTxIn(in, nil)
   307  			}
   308  		}
   309  
   310  		if s.totalEntryMemory != test.expectedSize {
   311  			t.Errorf("Failed test %s. Expected size of %d, got %d",
   312  				test.name, test.expectedSize, s.totalEntryMemory)
   313  		}
   314  	}
   315  }
   316  
   317  // assertConsistencyState asserts the utxo consistency states of the blockchain.
   318  func assertConsistencyState(chain *BlockChain, hash *chainhash.Hash) error {
   319  	var bytes []byte
   320  	err := chain.db.View(func(dbTx database.Tx) (err error) {
   321  		bytes = dbFetchUtxoStateConsistency(dbTx)
   322  		return
   323  	})
   324  	if err != nil {
   325  		return fmt.Errorf("Error fetching utxo state consistency: %v", err)
   326  	}
   327  	actualHash, err := chainhash.NewHash(bytes)
   328  	if err != nil {
   329  		return err
   330  	}
   331  	if !actualHash.IsEqual(hash) {
   332  		return fmt.Errorf("Unexpected consistency hash: %v instead of %v",
   333  			actualHash, hash)
   334  	}
   335  
   336  	return nil
   337  }
   338  
   339  // assertNbEntriesOnDisk asserts that the total number of utxo entries on the
   340  // disk is equal to the given expected number.
   341  func assertNbEntriesOnDisk(chain *BlockChain, expectedNumber int) error {
   342  	var nb int
   343  	err := chain.db.View(func(dbTx database.Tx) error {
   344  		cursor := dbTx.Metadata().Bucket(utxoSetBucketName).Cursor()
   345  		nb = 0
   346  		for b := cursor.First(); b; b = cursor.Next() {
   347  			nb++
   348  			_, err := deserializeUtxoEntry(cursor.Value())
   349  			if err != nil {
   350  				return fmt.Errorf("Failed to deserialize entry: %v", err)
   351  			}
   352  		}
   353  		return nil
   354  	})
   355  	if err != nil {
   356  		return fmt.Errorf("Error fetching utxo entries: %v", err)
   357  	}
   358  	if nb != expectedNumber {
   359  		return fmt.Errorf("Expected %d elements in the UTXO set, but found %d",
   360  			expectedNumber, nb)
   361  	}
   362  
   363  	return nil
   364  }
   365  
   366  // utxoCacheTestChain creates a test BlockChain to be used for utxo cache tests.
   367  // It uses the regression test parameters, a coin matutiry of 1 block and sets
   368  // the cache size limit to 10 MiB.
   369  func utxoCacheTestChain(testName string) (*BlockChain, *chaincfg.Params, func()) {
   370  	params := chaincfg.RegressionNetParams
   371  	chain, tearDown, err := chainSetup(testName, &params)
   372  	if err != nil {
   373  		panic(fmt.Sprintf("error loading blockchain with database: %v", err))
   374  	}
   375  
   376  	chain.TstSetCoinbaseMaturity(1)
   377  	chain.utxoCache.maxTotalMemoryUsage = 10 * 1024 * 1024
   378  	chain.utxoCache.cachedEntries.maxTotalMemoryUsage = chain.utxoCache.maxTotalMemoryUsage
   379  
   380  	return chain, &params, tearDown
   381  }
   382  
   383  func TestUtxoCacheFlush(t *testing.T) {
   384  	chain, params, tearDown := utxoCacheTestChain("TestUtxoCacheFlush")
   385  	defer tearDown()
   386  	cache := chain.utxoCache
   387  	tip := btcutil.NewBlock(params.GenesisBlock)
   388  
   389  	// The chainSetup init triggers the consistency status write.
   390  	err := assertConsistencyState(chain, params.GenesisHash)
   391  	if err != nil {
   392  		t.Fatal(err)
   393  	}
   394  
   395  	err = assertNbEntriesOnDisk(chain, 0)
   396  	if err != nil {
   397  		t.Fatal(err)
   398  	}
   399  
   400  	// LastFlushHash starts with genesis.
   401  	if cache.lastFlushHash != *params.GenesisHash {
   402  		t.Fatalf("lastFlushHash before first flush expected to be "+
   403  			"genesis block hash, instead was %v", cache.lastFlushHash)
   404  	}
   405  
   406  	// First, add 10 utxos without flushing.
   407  	outPoints := make([]wire.OutPoint, 10)
   408  	for i := range outPoints {
   409  		op := outpointFromInt(i)
   410  		outPoints[i] = op
   411  
   412  		// Add the txout.
   413  		txOut := wire.TxOut{Value: 10000, PkScript: getValidP2PKHScript()}
   414  		cache.addTxOut(op, &txOut, true, int32(i))
   415  	}
   416  
   417  	if cache.cachedEntries.length() != len(outPoints) {
   418  		t.Fatalf("Expected 10 entries, has %d instead", cache.cachedEntries.length())
   419  	}
   420  
   421  	// All entries should be fresh and modified.
   422  	for _, m := range cache.cachedEntries.maps {
   423  		for outpoint, entry := range m {
   424  			if entry == nil {
   425  				t.Fatalf("Unexpected nil entry found for %v", outpoint)
   426  			}
   427  			if !entry.isModified() {
   428  				t.Fatal("Entry should be marked mofified")
   429  			}
   430  			if !entry.isFresh() {
   431  				t.Fatal("Entry should be marked fresh")
   432  			}
   433  		}
   434  	}
   435  
   436  	// Spend the last outpoint and pop it off from the outpoints slice.
   437  	var spendOp wire.OutPoint
   438  	spendOp, outPoints = outPoints[len(outPoints)-1], outPoints[:len(outPoints)-1]
   439  	cache.addTxIn(&wire.TxIn{PreviousOutPoint: spendOp}, nil)
   440  
   441  	if cache.cachedEntries.length() != len(outPoints) {
   442  		t.Fatalf("Expected %d entries, has %d instead",
   443  			len(outPoints), cache.cachedEntries.length())
   444  	}
   445  
   446  	// Not flushed yet.
   447  	err = assertConsistencyState(chain, params.GenesisHash)
   448  	if err != nil {
   449  		t.Fatal(err)
   450  	}
   451  
   452  	err = assertNbEntriesOnDisk(chain, 0)
   453  	if err != nil {
   454  		t.Fatal(err)
   455  	}
   456  
   457  	// Flush.
   458  	err = chain.db.Update(func(dbTx database.Tx) error {
   459  		return cache.flush(dbTx, FlushRequired, chain.stateSnapshot)
   460  	})
   461  	if err != nil {
   462  		t.Fatalf("unexpected error while flushing cache: %v", err)
   463  	}
   464  	if cache.cachedEntries.length() != 0 {
   465  		t.Fatalf("Expected 0 entries, has %d instead", cache.cachedEntries.length())
   466  	}
   467  
   468  	err = assertConsistencyState(chain, tip.Hash())
   469  	if err != nil {
   470  		t.Fatal(err)
   471  	}
   472  	err = assertNbEntriesOnDisk(chain, len(outPoints))
   473  	if err != nil {
   474  		t.Fatal(err)
   475  	}
   476  
   477  	// Fetch the flushed utxos.
   478  	entries, err := cache.fetchEntries(outPoints)
   479  	if err != nil {
   480  		t.Fatal(err)
   481  	}
   482  
   483  	// Check that the returned entries are not marked fresh and modified.
   484  	for _, entry := range entries {
   485  		if entry.isFresh() {
   486  			t.Fatal("Entry should not be marked fresh")
   487  		}
   488  		if entry.isModified() {
   489  			t.Fatal("Entry should not be marked modified")
   490  		}
   491  	}
   492  
   493  	// Check that the fetched entries in the cache are not marked fresh and modified.
   494  	for _, m := range cache.cachedEntries.maps {
   495  		for outpoint, elem := range m {
   496  			if elem == nil {
   497  				t.Fatalf("Unexpected nil entry found for %v", outpoint)
   498  			}
   499  			if elem.isFresh() {
   500  				t.Fatal("Entry should not be marked fresh")
   501  			}
   502  			if elem.isModified() {
   503  				t.Fatal("Entry should not be marked modified")
   504  			}
   505  		}
   506  	}
   507  
   508  	// Spend 5 utxos.
   509  	prevLen := len(outPoints)
   510  	for i := 0; i < 5; i++ {
   511  		spendOp, outPoints = outPoints[len(outPoints)-1], outPoints[:len(outPoints)-1]
   512  		cache.addTxIn(&wire.TxIn{PreviousOutPoint: spendOp}, nil)
   513  	}
   514  
   515  	// Should still have the entries in cache so they can be flushed to disk.
   516  	if cache.cachedEntries.length() != prevLen {
   517  		t.Fatalf("Expected 10 entries, has %d instead", cache.cachedEntries.length())
   518  	}
   519  
   520  	// Flush.
   521  	err = chain.db.Update(func(dbTx database.Tx) error {
   522  		return cache.flush(dbTx, FlushRequired, chain.stateSnapshot)
   523  	})
   524  	if err != nil {
   525  		t.Fatalf("unexpected error while flushing cache: %v", err)
   526  	}
   527  	if cache.cachedEntries.length() != 0 {
   528  		t.Fatalf("Expected 0 entries, has %d instead", cache.cachedEntries.length())
   529  	}
   530  
   531  	err = assertConsistencyState(chain, tip.Hash())
   532  	if err != nil {
   533  		t.Fatal(err)
   534  	}
   535  	err = assertNbEntriesOnDisk(chain, len(outPoints))
   536  	if err != nil {
   537  		t.Fatal(err)
   538  	}
   539  
   540  	// Add 5 utxos without flushing and test for periodic flushes.
   541  	outPoints1 := make([]wire.OutPoint, 5)
   542  	for i := range outPoints1 {
   543  		// i + prevLen here to avoid collision since we're just hashing
   544  		// the int.
   545  		op := outpointFromInt(i + prevLen)
   546  		outPoints1[i] = op
   547  
   548  		// Add the txout.
   549  		txOut := wire.TxOut{Value: 10000, PkScript: getValidP2PKHScript()}
   550  		cache.addTxOut(op, &txOut, true, int32(i+prevLen))
   551  	}
   552  	if cache.cachedEntries.length() != len(outPoints1) {
   553  		t.Fatalf("Expected %d entries, has %d instead",
   554  			len(outPoints1), cache.cachedEntries.length())
   555  	}
   556  
   557  	// Attempt to flush with flush periodic.  Shouldn't flush.
   558  	err = chain.db.Update(func(dbTx database.Tx) error {
   559  		return cache.flush(dbTx, FlushPeriodic, chain.stateSnapshot)
   560  	})
   561  	if err != nil {
   562  		t.Fatalf("unexpected error while flushing cache: %v", err)
   563  	}
   564  	if cache.cachedEntries.length() == 0 {
   565  		t.Fatalf("Expected %d entries, has %d instead",
   566  			len(outPoints1), cache.cachedEntries.length())
   567  	}
   568  
   569  	// Arbitrarily set the last flush time to 6 minutes ago.
   570  	cache.lastFlushTime = time.Now().Add(-time.Minute * 6)
   571  
   572  	// Attempt to flush with flush periodic.  Should flush now.
   573  	err = chain.db.Update(func(dbTx database.Tx) error {
   574  		return cache.flush(dbTx, FlushPeriodic, chain.stateSnapshot)
   575  	})
   576  	if err != nil {
   577  		t.Fatalf("unexpected error while flushing cache: %v", err)
   578  	}
   579  	if cache.cachedEntries.length() != 0 {
   580  		t.Fatalf("Expected 0 entries, has %d instead", cache.cachedEntries.length())
   581  	}
   582  
   583  	err = assertConsistencyState(chain, tip.Hash())
   584  	if err != nil {
   585  		t.Fatal(err)
   586  	}
   587  	err = assertNbEntriesOnDisk(chain, len(outPoints)+len(outPoints1))
   588  	if err != nil {
   589  		t.Fatal(err)
   590  	}
   591  }
   592  
   593  func TestFlushNeededAfterPrune(t *testing.T) {
   594  	// Construct a synthetic block chain with a block index consisting of
   595  	// the following structure.
   596  	// 	genesis -> 1 -> 2 -> ... -> 15 -> 16  -> 17  -> 18
   597  	tip := tstTip
   598  	chain := newFakeChain(&chaincfg.MainNetParams)
   599  	chain.utxoCache = newUtxoCache(nil, 0)
   600  	branchNodes := chainedNodes(chain.bestChain.Genesis(), 18)
   601  	for _, node := range branchNodes {
   602  		chain.index.SetStatusFlags(node, statusValid)
   603  		chain.index.AddNode(node)
   604  	}
   605  	chain.bestChain.SetTip(tip(branchNodes))
   606  
   607  	tests := []struct {
   608  		name          string
   609  		lastFlushHash chainhash.Hash
   610  		delHashes     []chainhash.Hash
   611  		expected      bool
   612  	}{
   613  		{
   614  			name: "deleted block up to height 9, last flush hash at block 10",
   615  			delHashes: func() []chainhash.Hash {
   616  				delBlockHashes := make([]chainhash.Hash, 0, 9)
   617  				for i := range branchNodes {
   618  					if branchNodes[i].height < 10 {
   619  						delBlockHashes = append(delBlockHashes, branchNodes[i].hash)
   620  					}
   621  				}
   622  
   623  				return delBlockHashes
   624  			}(),
   625  			lastFlushHash: func() chainhash.Hash {
   626  				// Just some sanity checking to make sure the height is 10.
   627  				if branchNodes[9].height != 10 {
   628  					panic("was looking for height 10")
   629  				}
   630  				return branchNodes[9].hash
   631  			}(),
   632  			expected: false,
   633  		},
   634  		{
   635  			name: "deleted blocks up to height 10, last flush hash at block 10",
   636  			delHashes: func() []chainhash.Hash {
   637  				delBlockHashes := make([]chainhash.Hash, 0, 10)
   638  				for i := range branchNodes {
   639  					if branchNodes[i].height < 11 {
   640  						delBlockHashes = append(delBlockHashes, branchNodes[i].hash)
   641  					}
   642  				}
   643  				return delBlockHashes
   644  			}(),
   645  			lastFlushHash: func() chainhash.Hash {
   646  				// Just some sanity checking to make sure the height is 10.
   647  				if branchNodes[9].height != 10 {
   648  					panic("was looking for height 10")
   649  				}
   650  				return branchNodes[9].hash
   651  			}(),
   652  			expected: true,
   653  		},
   654  		{
   655  			name: "deleted block height 17, last flush hash at block 5",
   656  			delHashes: func() []chainhash.Hash {
   657  				delBlockHashes := make([]chainhash.Hash, 1)
   658  				delBlockHashes[0] = branchNodes[16].hash
   659  				// Just some sanity checking to make sure the height is 10.
   660  				if branchNodes[16].height != 17 {
   661  					panic("was looking for height 17")
   662  				}
   663  				return delBlockHashes
   664  			}(),
   665  			lastFlushHash: func() chainhash.Hash {
   666  				// Just some sanity checking to make sure the height is 10.
   667  				if branchNodes[4].height != 5 {
   668  					panic("was looking for height 5")
   669  				}
   670  				return branchNodes[4].hash
   671  			}(),
   672  			expected: true,
   673  		},
   674  		{
   675  			name: "deleted block height 3, last flush hash at block 4",
   676  			delHashes: func() []chainhash.Hash {
   677  				delBlockHashes := make([]chainhash.Hash, 1)
   678  				delBlockHashes[0] = branchNodes[2].hash
   679  				// Just some sanity checking to make sure the height is 10.
   680  				if branchNodes[2].height != 3 {
   681  					panic("was looking for height 3")
   682  				}
   683  				return delBlockHashes
   684  			}(),
   685  			lastFlushHash: func() chainhash.Hash {
   686  				// Just some sanity checking to make sure the height is 10.
   687  				if branchNodes[3].height != 4 {
   688  					panic("was looking for height 4")
   689  				}
   690  				return branchNodes[3].hash
   691  			}(),
   692  			expected: false,
   693  		},
   694  	}
   695  
   696  	for _, test := range tests {
   697  		chain.utxoCache.lastFlushHash = test.lastFlushHash
   698  		got, err := chain.flushNeededAfterPrune(test.delHashes)
   699  		if err != nil {
   700  			t.Fatal(err)
   701  		}
   702  
   703  		if got != test.expected {
   704  			t.Fatalf("for test %s, expected need flush to return %v but got %v",
   705  				test.name, test.expected, got)
   706  		}
   707  	}
   708  }
   709  
   710  func TestFlushOnPrune(t *testing.T) {
   711  	chain, tearDown, err := chainSetup("TestFlushOnPrune", &chaincfg.MainNetParams)
   712  	if err != nil {
   713  		panic(fmt.Sprintf("error loading blockchain with database: %v", err))
   714  	}
   715  	defer tearDown()
   716  
   717  	chain.utxoCache.maxTotalMemoryUsage = 10 * 1024 * 1024
   718  	chain.utxoCache.cachedEntries.maxTotalMemoryUsage = chain.utxoCache.maxTotalMemoryUsage
   719  
   720  	// Set the maxBlockFileSize and the prune target small so that we can trigger a
   721  	// prune to happen.
   722  	maxBlockFileSize := uint32(8192)
   723  	chain.pruneTarget = uint64(maxBlockFileSize) * 2
   724  
   725  	// Read blocks from the file.
   726  	blocks, err := loadBlocks("blk_0_to_14131.dat")
   727  	if err != nil {
   728  		t.Fatalf("failed to read block from file. %v", err)
   729  	}
   730  
   731  	syncBlocks := func() {
   732  		for i, block := range blocks {
   733  			if i == 0 {
   734  				// Skip the genesis block.
   735  				continue
   736  			}
   737  			isMainChain, _, err := chain.ProcessBlock(block, BFNone)
   738  			if err != nil {
   739  				t.Fatal(err)
   740  			}
   741  
   742  			if !isMainChain {
   743  				t.Fatalf("expected block %s to be on the main chain", block.Hash())
   744  			}
   745  		}
   746  	}
   747  
   748  	// Sync the chain.
   749  	ffldb.TstRunWithMaxBlockFileSize(chain.db, maxBlockFileSize, syncBlocks)
   750  
   751  	// Function that errors out if the block that should exist doesn't exist.
   752  	shouldExist := func(dbTx database.Tx, blockHash *chainhash.Hash) {
   753  		bytes, err := dbTx.FetchBlock(blockHash)
   754  		if err != nil {
   755  			t.Fatal(err)
   756  		}
   757  		block, err := btcutil.NewBlockFromBytes(bytes)
   758  		if err != nil {
   759  			t.Fatalf("didn't find block %v. %v", blockHash, err)
   760  		}
   761  
   762  		if !block.Hash().IsEqual(blockHash) {
   763  			t.Fatalf("expected to find block %v but got %v",
   764  				blockHash, block.Hash())
   765  		}
   766  	}
   767  
   768  	// Function that errors out if the block that shouldn't exist exists.
   769  	shouldNotExist := func(dbTx database.Tx, blockHash *chainhash.Hash) {
   770  		bytes, err := dbTx.FetchBlock(chaincfg.MainNetParams.GenesisHash)
   771  		if err == nil {
   772  			t.Fatalf("expected block %s to be pruned", blockHash)
   773  		}
   774  		if len(bytes) != 0 {
   775  			t.Fatalf("expected block %s to be pruned but got %v",
   776  				blockHash, bytes)
   777  		}
   778  	}
   779  
   780  	// The below code checks that the correct blocks were pruned.
   781  	chain.db.View(func(dbTx database.Tx) error {
   782  		exist := false
   783  		for _, block := range blocks {
   784  			// Blocks up to the last flush hash should not exist.
   785  			// The utxocache is big enough so that it shouldn't flush
   786  			// on it being full.  It should only flush on prunes.
   787  			if block.Hash().IsEqual(&chain.utxoCache.lastFlushHash) {
   788  				exist = true
   789  			}
   790  
   791  			if exist {
   792  				shouldExist(dbTx, block.Hash())
   793  			} else {
   794  				shouldNotExist(dbTx, block.Hash())
   795  			}
   796  
   797  		}
   798  
   799  		return nil
   800  	})
   801  }
   802  
   803  func TestInitConsistentState(t *testing.T) {
   804  	//  Boilerplate for creating a chain.
   805  	dbName := "TestFlushOnPrune"
   806  	chain, tearDown, err := chainSetup(dbName, &chaincfg.MainNetParams)
   807  	if err != nil {
   808  		panic(fmt.Sprintf("error loading blockchain with database: %v", err))
   809  	}
   810  	defer tearDown()
   811  	chain.utxoCache.maxTotalMemoryUsage = 10 * 1024 * 1024
   812  	chain.utxoCache.cachedEntries.maxTotalMemoryUsage = chain.utxoCache.maxTotalMemoryUsage
   813  
   814  	// Read blocks from the file.
   815  	blocks, err := loadBlocks("blk_0_to_14131.dat")
   816  	if err != nil {
   817  		t.Fatalf("failed to read block from file. %v", err)
   818  	}
   819  
   820  	// Sync up to height 13,000.  Flush the utxocache at height 11_000.
   821  	cacheFlushHeight := 9000
   822  	initialSyncHeight := 12_000
   823  	for i, block := range blocks {
   824  		if i == 0 {
   825  			// Skip the genesis block.
   826  			continue
   827  		}
   828  
   829  		isMainChain, _, err := chain.ProcessBlock(block, BFNone)
   830  		if err != nil {
   831  			t.Fatal(err)
   832  		}
   833  
   834  		if !isMainChain {
   835  			t.Fatalf("expected block %s to be on the main chain", block.Hash())
   836  		}
   837  
   838  		if i == cacheFlushHeight {
   839  			err = chain.FlushUtxoCache(FlushRequired)
   840  			if err != nil {
   841  				t.Fatal(err)
   842  			}
   843  		}
   844  		if i == initialSyncHeight {
   845  			break
   846  		}
   847  	}
   848  
   849  	// Sanity check.
   850  	if chain.BestSnapshot().Height != int32(initialSyncHeight) {
   851  		t.Fatalf("expected the chain to sync up to height %d", initialSyncHeight)
   852  	}
   853  
   854  	// Close the database without flushing the utxocache.  This leaves the
   855  	// chaintip at height 13,000 but the utxocache consistent state at 11,000.
   856  	err = chain.db.Close()
   857  	if err != nil {
   858  		t.Fatal(err)
   859  	}
   860  	chain.db = nil
   861  
   862  	// Re-open the database and pass the re-opened db to internal structs.
   863  	dbPath := filepath.Join(testDbRoot, dbName)
   864  	ndb, err := database.Open(testDbType, dbPath, blockDataNet)
   865  	if err != nil {
   866  		t.Fatal(err)
   867  	}
   868  	chain.db = ndb
   869  	chain.utxoCache.db = ndb
   870  	chain.index.db = ndb
   871  
   872  	// Sanity check to see that the utxo cache was flushed before the
   873  	// current chain tip.
   874  	var statusBytes []byte
   875  	ndb.View(func(dbTx database.Tx) error {
   876  		statusBytes = dbFetchUtxoStateConsistency(dbTx)
   877  		return nil
   878  	})
   879  	statusHash, err := chainhash.NewHash(statusBytes)
   880  	if err != nil {
   881  		t.Fatal(err)
   882  	}
   883  	if !statusHash.IsEqual(blocks[cacheFlushHeight].Hash()) {
   884  		t.Fatalf("expected the utxocache to be flushed at "+
   885  			"block hash %s but got %s",
   886  			blocks[cacheFlushHeight].Hash(), statusHash)
   887  	}
   888  
   889  	// Call InitConsistentState.  This will make the utxocache catch back
   890  	// up to the tip.
   891  	err = chain.InitConsistentState(chain.bestChain.tip(), nil)
   892  	if err != nil {
   893  		t.Fatal(err)
   894  	}
   895  
   896  	// Sync the reset of the blocks.
   897  	for i, block := range blocks {
   898  		if i <= initialSyncHeight {
   899  			continue
   900  		}
   901  		isMainChain, _, err := chain.ProcessBlock(block, BFNone)
   902  		if err != nil {
   903  			t.Fatal(err)
   904  		}
   905  
   906  		if !isMainChain {
   907  			t.Fatalf("expected block %s to be on the main chain", block.Hash())
   908  		}
   909  	}
   910  
   911  	if chain.BestSnapshot().Height != blocks[len(blocks)-1].Height() {
   912  		t.Fatalf("expected the chain to sync up to height %d",
   913  			blocks[len(blocks)-1].Height())
   914  	}
   915  }