github.com/decred/dcrlnd@v0.7.6/chainscan/util_test.go (about)

     1  package chainscan
     2  
     3  import (
     4  	"context"
     5  	"encoding/binary"
     6  	"errors"
     7  	"fmt"
     8  	"math/rand"
     9  	"sync"
    10  	"sync/atomic"
    11  	"time"
    12  
    13  	"github.com/decred/dcrd/chaincfg/chainhash"
    14  	"github.com/decred/dcrd/gcs/v4"
    15  	"github.com/decred/dcrd/gcs/v4/blockcf2"
    16  	"github.com/decred/dcrd/wire"
    17  )
    18  
    19  var (
    20  	testBlockCounter uint32
    21  
    22  	testPkScript = []byte{
    23  		0x76, 0xa9, 0x14, 0x2b, 0xf4, 0x0a, 0x13, 0xea,
    24  		0x4f, 0xf1, 0xf9, 0xd3, 0x5a, 0x54, 0x15, 0xb0,
    25  		0xf7, 0x7d, 0x9e, 0x5d, 0xd9, 0x3b, 0x23, 0x88,
    26  		0xac,
    27  	}
    28  	testSigScript = []byte{
    29  		0x47, 0x30, 0x44, 0x2, 0x20, 0x7b, 0xe8, 0x26,
    30  		0xed, 0x10, 0x5f, 0xaf, 0x88, 0xb1, 0x7d, 0x1,
    31  		0x72, 0x43, 0xd, 0x76, 0x3a, 0x9a, 0x14, 0x99,
    32  		0x8f, 0x2a, 0x96, 0x12, 0x4b, 0x7c, 0x70, 0x4d,
    33  		0xa1, 0x4d, 0x96, 0x20, 0xc2, 0x2, 0x20, 0x45,
    34  		0x8e, 0xf, 0x98, 0x11, 0x3b, 0x35, 0xa2, 0x2d,
    35  		0x43, 0xfc, 0x2, 0xdf, 0xa4, 0xe, 0x17, 0x8,
    36  		0xf7, 0xd8, 0xc7, 0x5f, 0xfe, 0xcd, 0x8e, 0x50,
    37  		0x2c, 0xfd, 0x58, 0xda, 0x4a, 0x2b, 0x10, 0x1,
    38  		0x21, 0x2, 0x6e, 0xa2, 0xca, 0x26, 0x16, 0x56,
    39  		0xe6, 0xbc, 0xae, 0xd9, 0xb2, 0x32, 0xdc, 0xc4,
    40  		0x7b, 0x30, 0x33, 0x20, 0x41, 0xbc, 0x31, 0xd5,
    41  		0x3c, 0x40, 0xa8, 0xa1, 0x4a, 0xb9, 0x60, 0x4f,
    42  		0x25, 0x33,
    43  	}
    44  	testOutPoint = wire.OutPoint{
    45  		Hash: chainhash.Hash{
    46  			0x72, 0x43, 0x0d, 0x76, 0x3a, 0x9a, 0x14, 0x99,
    47  			0x8f, 0x2a, 0x96, 0x12, 0x4b, 0x7c, 0x70, 0x4d,
    48  			0x43, 0xfc, 0x02, 0xdf, 0xa4, 0x0e, 0x17, 0x08,
    49  			0xf7, 0xd8, 0xc7, 0x5f, 0xfe, 0xcd, 0x8e, 0x50,
    50  		},
    51  		Index: 1701,
    52  	}
    53  
    54  	emptyEvent Event
    55  )
    56  
    57  // testingIntf matches both *testing.T and *testing.B
    58  type testingIntf interface {
    59  	Fatal(...interface{})
    60  	Fatalf(string, ...interface{})
    61  	Helper()
    62  }
    63  
    64  func assertFoundChanRcv(t testingIntf, c chan Event) Event {
    65  	t.Helper()
    66  
    67  	var e Event
    68  	select {
    69  	case e = <-c:
    70  		return e
    71  	case <-time.After(5 * time.Second):
    72  		t.Fatal("Timeout waiting for receive in foundChan")
    73  	}
    74  	return e
    75  }
    76  
    77  func assertFoundChanRcvHeight(t testingIntf, c chan Event, wantHeight int32) Event {
    78  	t.Helper()
    79  
    80  	e := assertFoundChanRcv(t, c)
    81  	if e.BlockHeight != wantHeight {
    82  		t.Fatalf("Unexpected BlockHeight in foundChan event. want=%d got=%d",
    83  			wantHeight, e.BlockHeight)
    84  	}
    85  	return e
    86  }
    87  
    88  func assertFoundChanEmpty(t testingIntf, c chan Event) {
    89  	t.Helper()
    90  	select {
    91  	case <-c:
    92  		t.Fatal("Unexpected signal in foundChan")
    93  	case <-time.After(10 * time.Millisecond):
    94  	}
    95  }
    96  
    97  func assertStartWatchHeightSignalled(t testingIntf, c chan int32) int32 {
    98  	t.Helper()
    99  
   100  	var endHeight int32
   101  	select {
   102  	case endHeight = <-c:
   103  	case <-time.After(5 * time.Second):
   104  		t.Fatal("Timeout waiting for start watch height chan signal")
   105  	}
   106  	return endHeight
   107  }
   108  
   109  func assertNoError(t testingIntf, err error) {
   110  	t.Helper()
   111  
   112  	if err == nil {
   113  		return
   114  	}
   115  
   116  	t.Fatalf("Unexpected error: %v", err)
   117  }
   118  
   119  // assertCompleted asserts that the 'c' chan (which should be a completeChan
   120  // passed to a scanner) is closed in a reasonable time.
   121  func assertCompleted(t testingIntf, c chan struct{}) {
   122  	t.Helper()
   123  	select {
   124  	case <-c:
   125  	case <-time.After(5 * time.Second):
   126  		t.Fatal("Timeout waiting for completeChan to close")
   127  	}
   128  }
   129  
   130  type testBlock struct {
   131  	block     *wire.MsgBlock
   132  	blockHash chainhash.Hash
   133  	cfilter   *gcs.FilterV2
   134  	cfKey     [16]byte
   135  }
   136  
   137  type blockMangler struct {
   138  	txMangler    func(tx *wire.MsgTx)
   139  	blockMangler func(b *wire.MsgBlock)
   140  	cfilterData  []byte
   141  }
   142  
   143  func spendOutPoint(outp wire.OutPoint) blockMangler {
   144  	return blockMangler{
   145  		txMangler: func(tx *wire.MsgTx) {
   146  			tx.AddTxIn(wire.NewTxIn(&outp, 0, nil))
   147  		},
   148  	}
   149  }
   150  
   151  func spendScript(sigScript []byte) blockMangler {
   152  	var outp wire.OutPoint
   153  	rand.Read(outp.Hash[:])
   154  	return blockMangler{
   155  		txMangler: func(tx *wire.MsgTx) {
   156  			tx.AddTxIn(wire.NewTxIn(&outp, 0, sigScript))
   157  		},
   158  	}
   159  }
   160  
   161  func confirmScript(pkScript []byte) blockMangler {
   162  	return blockMangler{
   163  		txMangler: func(tx *wire.MsgTx) {
   164  			tx.AddTxOut(wire.NewTxOut(0, pkScript))
   165  		},
   166  	}
   167  }
   168  
   169  func moveRegularToStakeTree() blockMangler {
   170  	return blockMangler{
   171  		blockMangler: func(b *wire.MsgBlock) {
   172  			b.STransactions = b.Transactions
   173  			b.Transactions = make([]*wire.MsgTx, 0)
   174  		},
   175  	}
   176  }
   177  
   178  func cfilterData(pkScript []byte) blockMangler {
   179  	return blockMangler{
   180  		cfilterData: pkScript,
   181  	}
   182  }
   183  
   184  func newTestBlock(height int32, prevBlock chainhash.Hash, blockManglers ...blockMangler) *testBlock {
   185  	bc := atomic.AddUint32(&testBlockCounter, 1)
   186  	tx := &wire.MsgTx{
   187  		TxIn: []*wire.TxIn{
   188  			{
   189  				PreviousOutPoint: wire.OutPoint{
   190  					Index: bc, // Ensure unique tx hash
   191  				},
   192  			},
   193  		},
   194  		TxOut: []*wire.TxOut{
   195  			{
   196  				PkScript: []byte{0x6a},
   197  			},
   198  		},
   199  	}
   200  	var cfData [][]byte
   201  	for _, m := range blockManglers {
   202  		if m.txMangler != nil {
   203  			m.txMangler(tx)
   204  		}
   205  		if m.cfilterData != nil {
   206  			cfData = append(cfData, m.cfilterData)
   207  		}
   208  	}
   209  
   210  	block := &wire.MsgBlock{
   211  		Header: wire.BlockHeader{
   212  			PrevBlock: prevBlock,
   213  			Height:    uint32(height),
   214  			Nonce:     testBlockCounter,
   215  		},
   216  		Transactions: []*wire.MsgTx{tx},
   217  	}
   218  	binary.LittleEndian.PutUint32(block.Header.MerkleRoot[:], bc)
   219  
   220  	for _, m := range blockManglers {
   221  		if m.blockMangler != nil {
   222  			m.blockMangler(block)
   223  		}
   224  	}
   225  
   226  	var cfKey [16]byte
   227  	copy(cfKey[:], block.Header.MerkleRoot[:])
   228  	cf, err := gcs.NewFilterV2(blockcf2.B, blockcf2.M, cfKey, cfData)
   229  	if err != nil {
   230  		panic(err)
   231  	}
   232  
   233  	return &testBlock{
   234  		block:     block,
   235  		blockHash: block.BlockHash(),
   236  		cfilter:   cf,
   237  		cfKey:     cfKey,
   238  	}
   239  }
   240  
   241  // dupeTestTx duplicates the first transaction of the test block (in either the
   242  // regular or stake transaction trees).
   243  func dupeTestTx(b *testBlock) {
   244  	if len(b.block.Transactions) > 0 {
   245  		b.block.Transactions = append(b.block.Transactions, b.block.Transactions[0])
   246  	} else {
   247  		b.block.STransactions = append(b.block.STransactions, b.block.STransactions[0])
   248  	}
   249  }
   250  
   251  type mockChain struct {
   252  	// blocks and byHeight are sync.Maps to avoid triggering the race
   253  	// detector for the chain.
   254  	blocks   *sync.Map // map[chainhash.Hash]*testBlock
   255  	byHeight *sync.Map // map[int32]*testBlock
   256  
   257  	tipMtx sync.Mutex
   258  	tip    *testBlock
   259  
   260  	mtx          sync.Mutex
   261  	eventReaders []*eventReader
   262  
   263  	newTipChan          chan struct{}
   264  	getBlockCount       uint32
   265  	getCfilterCount     uint32
   266  	sendNextCfilterChan chan struct{}
   267  }
   268  
   269  func newMockChain() *mockChain {
   270  	return &mockChain{
   271  		blocks:     &sync.Map{},
   272  		byHeight:   &sync.Map{},
   273  		newTipChan: make(chan struct{}),
   274  	}
   275  }
   276  
   277  func (mc *mockChain) newFromTip(manglers ...blockMangler) *testBlock {
   278  	var height int32
   279  	var prevBlock chainhash.Hash
   280  	mc.tipMtx.Lock()
   281  	if mc.tip != nil {
   282  		height = int32(mc.tip.block.Header.Height + 1)
   283  		prevBlock = mc.tip.block.BlockHash()
   284  	}
   285  	mc.tipMtx.Unlock()
   286  
   287  	b := newTestBlock(height, prevBlock, manglers...)
   288  	bh := b.block.BlockHash()
   289  	mc.blocks.Store(bh, b)
   290  	mc.byHeight.Store(height, b)
   291  	return b
   292  }
   293  
   294  func (mc *mockChain) extend(b *testBlock) {
   295  	mc.tipMtx.Lock()
   296  	mc.tip = b
   297  	mc.tipMtx.Unlock()
   298  }
   299  
   300  func (mc *mockChain) signalNewTip() {
   301  	mc.mtx.Lock()
   302  	readers := mc.eventReaders
   303  	mc.mtx.Unlock()
   304  
   305  	mc.tipMtx.Lock()
   306  	tip := mc.tip
   307  	mc.tipMtx.Unlock()
   308  
   309  	e := BlockConnectedEvent{
   310  		Hash:     tip.blockHash,
   311  		Height:   int32(tip.block.Header.Height),
   312  		PrevHash: tip.block.Header.PrevBlock,
   313  		CFKey:    tip.cfKey,
   314  		Filter:   tip.cfilter,
   315  	}
   316  	for _, r := range readers {
   317  		select {
   318  		case <-r.ctx.Done():
   319  		case r.c <- e:
   320  		}
   321  	}
   322  }
   323  
   324  func (mc *mockChain) genBlocks(n int, manglers ...blockMangler) {
   325  	for i := 0; i < n; i++ {
   326  		mc.extend(mc.newFromTip(manglers...))
   327  	}
   328  }
   329  
   330  func (mc *mockChain) ChainEvents(ctx context.Context) <-chan ChainEvent {
   331  	r := &eventReader{
   332  		ctx: ctx,
   333  		c:   make(chan ChainEvent),
   334  	}
   335  	mc.mtx.Lock()
   336  	mc.eventReaders = append(mc.eventReaders, r)
   337  	mc.mtx.Unlock()
   338  	return r.c
   339  }
   340  
   341  func (mc *mockChain) NextTip(ctx context.Context) (*chainhash.Hash, int32, [16]byte, *gcs.FilterV2, error) {
   342  	select {
   343  	case <-ctx.Done():
   344  		return nil, 0, [16]byte{}, nil, ctx.Err()
   345  	case <-mc.newTipChan:
   346  		mc.tipMtx.Lock()
   347  		tip := mc.tip
   348  		mc.tipMtx.Unlock()
   349  		return &tip.blockHash, int32(tip.block.Header.Height), tip.cfKey, tip.cfilter, nil
   350  	}
   351  }
   352  
   353  func (mc *mockChain) GetBlock(ctx context.Context, bh *chainhash.Hash) (*wire.MsgBlock, error) {
   354  	if b, ok := mc.blocks.Load(*bh); ok {
   355  		atomic.AddUint32(&mc.getBlockCount, 1)
   356  		return b.(*testBlock).block, nil
   357  	}
   358  	return nil, errors.New("block not found")
   359  }
   360  
   361  func (mc *mockChain) CurrentTip(ctx context.Context) (*chainhash.Hash, int32, error) {
   362  	mc.tipMtx.Lock()
   363  	tip := mc.tip
   364  	mc.tipMtx.Unlock()
   365  
   366  	if tip == nil {
   367  		return nil, 0, errors.New("chain uninitialized")
   368  	}
   369  
   370  	return &tip.blockHash, int32(tip.block.Header.Height), nil
   371  }
   372  
   373  func (mc *mockChain) GetCFilter(ctx context.Context, height int32) (*chainhash.Hash, [16]byte, *gcs.FilterV2, error) {
   374  	// If sendNextCfilter is specified then wait until it's signalled by
   375  	// the test routine to send the cfilter.
   376  	if mc.sendNextCfilterChan != nil {
   377  		<-mc.sendNextCfilterChan
   378  	}
   379  
   380  	mc.tipMtx.Lock()
   381  	tip := mc.tip
   382  	mc.tipMtx.Unlock()
   383  	if tip == nil {
   384  		return nil, [16]byte{}, nil, errors.New("chain unitialized")
   385  	}
   386  
   387  	if height > int32(tip.block.Header.Height) {
   388  		return nil, [16]byte{}, nil, ErrBlockAfterTip{Height: height}
   389  	}
   390  
   391  	bl, ok := mc.byHeight.Load(height)
   392  	if !ok {
   393  		return nil, [16]byte{}, nil, fmt.Errorf("unknown block by height %d", height)
   394  	}
   395  	block := bl.(*testBlock)
   396  	atomic.AddUint32(&mc.getCfilterCount, 1)
   397  	return &block.blockHash, block.cfKey, block.cfilter, nil
   398  }