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