package neutrino import ( "context" "errors" "net" "testing" "git.smesh.lol/nostrino/pkg/btcwire" "git.smesh.lol/nostrino/pkg/chain" ) // TestOneFilterHeaderRequestCoversAtMostTheProtocolLimit: a getcfheaders range is capped at // MaxFilterHashesPerMsg blocks by BIP157, and a peer that is asked for more drops the connection // ("peer requested too many cfilters/cfheaders"). A chain taller than the cap must therefore be // walked in chunks, which is what a regtest rig with 24000 blocks exercises. func TestOneFilterHeaderRequestCoversAtMostTheProtocolLimit(t *testing.T) { const perRequest = uint32(btcwire.MaxFilterHashesPerMsg) if perRequest != 2000 { t.Fatalf("MaxFilterHashesPerMsg is %d, BIP157 says 2000", perRequest) } cases := []struct { name string start, tip uint32 want uint32 }{ {"a single block", 0, 0, 0}, {"shorter than a request", 0, 99, 99}, {"exactly one request", 0, perRequest - 1, perRequest - 1}, {"one block past a request", 0, perRequest, perRequest - 1}, {"a later full request", perRequest, 2*perRequest + 5, 2*perRequest - 1}, {"the last partial request", 24000, 24102, 24102}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { got := filterHeaderStop(tc.start, tc.tip) if got != tc.want { t.Fatalf("stop for start %d, tip %d = %d, want %d", tc.start, tc.tip, got, tc.want) } if covered := got - tc.start + 1; covered > perRequest { t.Fatalf("one request covers %d blocks, the limit is %d", covered, perRequest) } }) } } // TestBlockNeedsAHeader: a height the store has no header for is a named refusal, because the // client has nothing verified to ask about. func TestBlockNeedsAHeader(t *testing.T) { store := NewMemory() profile, err := chain.ByName("regtest") if err != nil { t.Fatal(err) } client, err := New(Config{ Profile: profile, Store: store, Dial: func(context.Context) (net.Conn, error) { return nil, errors.New("no peer") }, }) if err != nil { t.Fatal(err) } if _, err := client.Block(context.Background(), 42); !errors.Is(err, ErrNoHeader) { t.Fatalf("a height with no header: %v", err) } } // TestBroadcastNeedsAPeer: there is nowhere to send a transaction without a connection, and an // empty transaction is refused before it becomes a frame. func TestBroadcastNeedsAPeer(t *testing.T) { profile, err := chain.ByName("regtest") if err != nil { t.Fatal(err) } client, err := New(Config{ Profile: profile, Store: NewMemory(), Dial: func(context.Context) (net.Conn, error) { return nil, errors.New("no peer") }, }) if err != nil { t.Fatal(err) } if err := client.Broadcast(context.Background(), []byte{1, 2, 3}); err == nil { t.Fatal("a transaction was broadcast with no peer") } if err := client.Broadcast(context.Background(), nil); err == nil { t.Fatal("an empty transaction was broadcast") } if err := client.Broadcast(context.Background(), make([]byte, MaxTransactionPayload+1)); !errors.Is(err, ErrTransactionTooLarge) { t.Fatalf("an oversized transaction: %v", err) } }