chainsource_test.go raw

   1  package chainsource
   2  
   3  import (
   4  	"context"
   5  	"net"
   6  	"testing"
   7  
   8  	"git.smesh.lol/nostrino/pkg/chain"
   9  	"git.smesh.lol/nostrino/pkg/chaindata"
  10  	"git.smesh.lol/nostrino/pkg/neutrino"
  11  )
  12  
  13  // The adapter must be the thing a chaindata domain serves, or the wiring is a comment
  14  // rather than a fact.
  15  var _ chaindata.Source = (*Nostrino)(nil)
  16  
  17  // storeOnly builds a client over a memory store, with a dialer that is never used.
  18  //
  19  // The tests here exercise what the adapter reads from the store and how it handles
  20  // cancellation. Connecting needs a peer, and a peer is what the end-to-end test has.
  21  func storeOnly(t *testing.T, store neutrino.Store) *Nostrino {
  22  	t.Helper()
  23  
  24  	profile := chain.Regtest
  25  
  26  	client, err := neutrino.New(neutrino.Config{
  27  		Profile: &profile,
  28  		Store:   store,
  29  		Dial: func(context.Context) (net.Conn, error) {
  30  			return nil, errNoPeer
  31  		},
  32  	})
  33  
  34  	if err != nil {
  35  		t.Fatal(err)
  36  	}
  37  
  38  	adapter, err := New(client)
  39  
  40  	if err != nil {
  41  		t.Fatal(err)
  42  	}
  43  
  44  	return adapter
  45  }
  46  
  47  // errNoPeer is what the unused dialer returns.
  48  var errNoPeer = errorString("chainsource test: no peer")
  49  
  50  // open is a context that is not cancelled.
  51  type openContext struct{}
  52  
  53  // Done implements chaindata.Context.
  54  func (openContext) Done() <-chan struct{} { return nil }
  55  
  56  // Err implements chaindata.Context.
  57  func (openContext) Err() string { return "" }
  58  
  59  // cancelled is a context that is already cancelled.
  60  type cancelled struct{}
  61  
  62  // Done implements chaindata.Context.
  63  func (cancelled) Done() <-chan struct{} {
  64  	closed := make(chan struct{})
  65  	close(closed)
  66  
  67  	return closed
  68  }
  69  
  70  // Err implements chaindata.Context.
  71  func (cancelled) Err() string { return "cancelled by the test" }
  72  
  73  // TestPositionOfAnEmptyStore: a store with no verified chain is at zero, not at a made-up
  74  // height.
  75  func TestPositionOfAnEmptyStore(t *testing.T) {
  76  	adapter := storeOnly(t, neutrino.NewMemory())
  77  
  78  	position, err := adapter.Position(openContext{})
  79  
  80  	if err != nil {
  81  		t.Fatal(err)
  82  	}
  83  
  84  	if position.Tip != 0 || position.Filters != 0 || position.Running {
  85  		t.Fatalf("position = %+v", position)
  86  	}
  87  }
  88  
  89  // TestMissingFilterIsAnError: a gap in the batch is an error, because a wallet that silently
  90  // skipped a block would miss its own money.
  91  func TestMissingFilterIsAnError(t *testing.T) {
  92  	adapter := storeOnly(t, neutrino.NewMemory())
  93  
  94  	if _, err := adapter.Filters(openContext{}, 5, 1); err == nil {
  95  		t.Fatal("a missing filter was returned as an empty batch")
  96  	}
  97  
  98  	if _, err := adapter.Filters(openContext{}, 0, 0); err == nil {
  99  		t.Fatal("an empty range was accepted")
 100  	}
 101  
 102  	if _, err := adapter.Filters(openContext{}, 0, chaindata.MaxFilters+1); err == nil {
 103  		t.Fatal("an oversized range was accepted")
 104  	}
 105  
 106  	if _, err := adapter.Match(openContext{}, nil); err == nil {
 107  		t.Fatal("a match with no scripts was accepted")
 108  	}
 109  }
 110  
 111  // TestCancellationIsRefusedBeforeTheWork: a cancelled request does not start a sync or a
 112  // scan, which is the point of carrying a channel rather than a flag.
 113  func TestCancellationIsRefusedBeforeTheWork(t *testing.T) {
 114  	adapter := storeOnly(t, neutrino.NewMemory())
 115  
 116  	if _, err := adapter.Sync(cancelled{}, 0); err == nil {
 117  		t.Fatal("a cancelled sync began")
 118  	}
 119  
 120  	if _, err := adapter.Match(cancelled{}, [][]byte{{0x51}}); err == nil {
 121  		t.Fatal("a cancelled match began")
 122  	}
 123  
 124  	if _, err := adapter.Filters(cancelled{}, 0, 1); err == nil {
 125  		t.Fatal("a cancelled filter read began")
 126  	}
 127  
 128  	// Filter reads need no peer, so a cancelled one must be refused rather than served.
 129  	if _, err := adapter.Filters(openContext{}, 0, 1); err == nil {
 130  		t.Fatal("an empty store served a filter")
 131  	}
 132  }
 133  
 134  // TestNoClientIsRefused: the adapter is built from a client, and a nil one is a wiring bug
 135  // this constructor names rather than a panic later.
 136  func TestNoClientIsRefused(t *testing.T) {
 137  	if _, err := New(nil); err == nil {
 138  		t.Fatal("a nil client was wrapped")
 139  	}
 140  }
 141