func TestMigrationStoreMaxReorgDepth(t *testing.T) {
	t.Parallel()
	testCases := []struct {
		name      string
		numBlocks int32
	}{
		{
			name:      "genesis only",
			numBlocks: 0,
		},
		{
			name:      "below max reorg depth",
			numBlocks: MaxReorgDepth - 1,
		},
		{
			name:      "above max reorg depth",
			numBlocks: MaxReorgDepth + 1,
		},
		{
			name:      "double max reorg depth",
			numBlocks: MaxReorgDepth * 2,
		},
	}
	for _, testCase := range testCases {
		success := t.Run(testCase.name, func(t *testing.T) {
			blocks := make([]*BlockStamp, 0, testCase.numBlocks)
			for i := int32(1); i <= testCase.numBlocks; i++ {
				var hash chainhash.Hash
				binary.BigEndian.PutUint32(hash[:], uint32(i))
				blocks = append(blocks, &BlockStamp{
					Hash:   hash,
					Height: i,
				})
			}
			beforeMigration := func(ns walletdb.ReadWriteBucket) error {
				if testCase.numBlocks == 0 {
					return nil
				}
				for _, block := range blocks {
					err := addBlockHash(
						ns, block.Height, block.Hash,
					)
					if err != nil {
						return err
					}
					err = updateSyncedTo(ns, block)
					if err != nil {
						return err
					}
				}
				for _, block := range blocks {
					hash, err := fetchBlockHash(
						ns, block.Height,
					)
					if err != nil {
						return err
					}
					if *hash != block.Hash {
						return fmt.Errorf("expected "+
							"hash %v for height "+
							"%v, got %v",
							block.Hash,
							block.Height, hash)
					}
				}
				block, err := fetchSyncedTo(ns)
				if err != nil {
					return err
				}
				expectedBlock := blocks[len(blocks)-1]
				if block.Height != block.Height {
					return fmt.Errorf("expected synced to "+
						"block height %v, got %v",
						expectedBlock.Height,
						block.Height)
				}
				if block.Hash != block.Hash {
					return fmt.Errorf("expected synced to "+
						"block hash %v, got %v",
						expectedBlock.Hash,
						block.Hash)
				}
				return nil
			}
			afterMigration := func(ns walletdb.ReadWriteBucket) error {
				maxStaleHeight := staleHeight(testCase.numBlocks)
				for _, block := range blocks {
					if block.Height <= maxStaleHeight {
						_, err := fetchBlockHash(
							ns, block.Height,
						)
						if IsError(err, ErrBlockNotFound) {
							continue
						}
						return fmt.Errorf("expected "+
							"ErrBlockNotFound for "+
							"height %v, got %v",
							block.Height, err)
					}
					hash, err := fetchBlockHash(
						ns, block.Height,
					)
					if err != nil {
						return err
					}
					if *hash != block.Hash {
						return fmt.Errorf("expected "+
							"hash %v for height "+
							"%v, got %v",
							block.Hash,
							block.Height, hash)
					}
				}
				return nil
			}
			applyMigration(
				t, beforeMigration, afterMigration,
				storeMaxReorgDepth, false,
			)
		})
		if !success {
			return
		}
	}
}
TestMigrationStoreMaxReorgDepth ensures that the storeMaxReorgDepth migration
works as expected under different sync scenarios.
package waddrmgr
import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"testing"
	"time"
	"github.com/gcash/bchd/chaincfg"
	"github.com/gcash/bchd/chaincfg/chainhash"
	"github.com/gcash/bchwallet/walletdb"
)
func applyMigration(t *testing.T, beforeMigration, afterMigration,
	migration func(walletdb.ReadWriteBucket) error, shouldFail bool) {
	t.Helper()
	teardown, db, _ := setupManager(t)
	defer teardown()
	err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
		ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
		if ns == nil {
			return errors.New("top-level namespace does not exist")
		}
		return beforeMigration(ns)
	})
	if err != nil {
		t.Fatalf("unable to run beforeMigration func: %v", err)
	}
	err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
		ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
		if ns == nil {
			return errors.New("top-level namespace does not exist")
		}
		return migration(ns)
	})
	if err != nil && !shouldFail {
		t.Fatalf("unable to perform migration: %v", err)
	} else if err == nil && shouldFail {
		t.Fatal("expected migration to fail, but did not")
	}
	err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
		ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
		if ns == nil {
			return errors.New("top-level namespace does not exist")
		}
		return afterMigration(ns)
	})
	if err != nil {
		t.Fatalf("unable to run afterMigration func: %v", err)
	}
}
func TestMigrationPopulateBirthdayBlock(t *testing.T) {
	t.Parallel()
	var expectedHeight int32
	beforeMigration := func(ns walletdb.ReadWriteBucket) error {
		block := &BlockStamp{}
		for i := int32(1); i <= 10; i++ {
			block.Height = i
			blockHash := bytes.Repeat([]byte(string(i)), 32)
			copy(block.Hash[:], blockHash)
			if err := PutSyncedTo(ns, block); err != nil {
				return err
			}
		}
		genesisTimestamp := chaincfg.MainNetParams.GenesisBlock.Header.Timestamp
		delta := time.Hour
		expectedHeight = int32(delta.Seconds() / 600)
		birthday := genesisTimestamp.Add(delta)
		if err := putBirthday(ns, birthday); err != nil {
			return err
		}
		_, err := FetchBirthdayBlock(ns)
		if !IsError(err, ErrBirthdayBlockNotSet) {
			return fmt.Errorf("expected ErrBirthdayBlockNotSet, "+
				"got %v", err)
		}
		return nil
	}
	afterMigration := func(ns walletdb.ReadWriteBucket) error {
		birthdayBlock, err := FetchBirthdayBlock(ns)
		if err != nil {
			return err
		}
		if birthdayBlock.Height != expectedHeight {
			return fmt.Errorf("expected birthday block with "+
				"height %d, got %d", expectedHeight,
				birthdayBlock.Height)
		}
		return nil
	}
	applyMigration(
		t, beforeMigration, afterMigration, populateBirthdayBlock,
		false,
	)
}
func TestMigrationPopulateBirthdayBlockEstimateTooFar(t *testing.T) {
	t.Parallel()
	const numBlocks = 1000
	chainParams := chaincfg.MainNetParams
	var expectedHeight int32
	beforeMigration := func(ns walletdb.ReadWriteBucket) error {
		block := &BlockStamp{}
		for i := int32(1); i < numBlocks; i++ {
			block.Height = i
			blockHash := bytes.Repeat([]byte(string(i)), 32)
			copy(block.Hash[:], blockHash)
			if err := PutSyncedTo(ns, block); err != nil {
				return err
			}
		}
		genesisTimestamp := chainParams.GenesisBlock.Header.Timestamp
		delta := numBlocks * 10 * time.Minute
		expectedHeight = numBlocks - 144
		birthday := genesisTimestamp.Add(delta)
		if err := putBirthday(ns, birthday); err != nil {
			return err
		}
		_, err := FetchBirthdayBlock(ns)
		if !IsError(err, ErrBirthdayBlockNotSet) {
			return fmt.Errorf("expected ErrBirthdayBlockNotSet, "+
				"got %v", err)
		}
		return nil
	}
	afterMigration := func(ns walletdb.ReadWriteBucket) error {
		birthdayBlock, err := FetchBirthdayBlock(ns)
		if err != nil {
			return err
		}
		if birthdayBlock.Height != expectedHeight {
			return fmt.Errorf("expected birthday block height %d, "+
				"got %d", expectedHeight, birthdayBlock.Height)
		}
		return nil
	}
	applyMigration(
		t, beforeMigration, afterMigration, populateBirthdayBlock,
		false,
	)
}
func TestMigrationResetSyncedBlockToBirthday(t *testing.T) {
	t.Parallel()
	var birthdayBlock BlockStamp
	beforeMigration := func(ns walletdb.ReadWriteBucket) error {
		block := &BlockStamp{}
		for i := int32(1); i < 100; i++ {
			block.Height = i
			blockHash := bytes.Repeat([]byte(string(i)), 32)
			copy(block.Hash[:], blockHash)
			if err := PutSyncedTo(ns, block); err != nil {
				return err
			}
		}
		const birthdayHeight = 50
		birthdayHash, err := fetchBlockHash(ns, birthdayHeight)
		if err != nil {
			return err
		}
		birthdayBlock = BlockStamp{
			Hash: *birthdayHash, Height: birthdayHeight,
		}
		return PutBirthdayBlock(ns, birthdayBlock)
	}
	afterMigration := func(ns walletdb.ReadWriteBucket) error {
		syncedBlock, err := fetchSyncedTo(ns)
		if err != nil {
			return err
		}
		if syncedBlock.Height != birthdayBlock.Height {
			return fmt.Errorf("expected synced block height %d, "+
				"got %d", birthdayBlock.Height,
				syncedBlock.Height)
		}
		if !syncedBlock.Hash.IsEqual(&birthdayBlock.Hash) {
			return fmt.Errorf("expected synced block height %v, "+
				"got %v", birthdayBlock.Hash, syncedBlock.Hash)
		}
		return nil
	}
	applyMigration(
		t, beforeMigration, afterMigration, resetSyncedBlockToBirthday,
		false,
	)
}
func TestMigrationResetSyncedBlockToBirthdayWithNoBirthdayBlock(t *testing.T) {
	t.Parallel()
	beforeMigration := func(walletdb.ReadWriteBucket) error {
		return nil
	}
	afterMigration := func(walletdb.ReadWriteBucket) error {
		return nil
	}
	applyMigration(
		t, beforeMigration, afterMigration, resetSyncedBlockToBirthday,
		true,
	)
}