MetacoSA / NBitcoin

Comprehensive Bitcoin library for the .NET framework.
MIT License
1.88k stars 847 forks source link

Trouble using Bloom Filter #268

Closed sipsorcery closed 7 years ago

sipsorcery commented 7 years ago

I'm attempting to find all the transactions related to a specific address using a Bloom Filter. I've used this previous issue as my stating point.

The testnet address I'm tracking has 4 transactions but my demo program only ever detects the first transaction.

My code is below and undoubtedly I'm doing something silly. I have deliberately removed the persistence of the blockchain to file while I'm messing around.

The output from running the program is:

Tracking mssuKhM1CMDgcCm3LyGunA1o6129FnkHyk TX ID: db76db25df6ce14b1336d789095f67a7b727541c1c8457f8ddc501233fefd706. Node disconnected, chain height 1156500. Tracking mssuKhM1CMDgcCm3LyGunA1o6129FnkHyk Node disconnected, chain height 1156500. Tracking mssuKhM1CMDgcCm3LyGunA1o6129FnkHyk Node disconnected, chain height 1156500. Tracking mssuKhM1CMDgcCm3LyGunA1o6129FnkHyk

Sample Code

`namespace BitCoinTest { class Program { static Network _network = Network.TestNet;

    static void Main(string[] args)
    {
        Key key = Key.Parse("cR7X4Nd5WqA5mNwgX67th4Jo3K9vTTm28w8njLL9JT8hHPdbstL8", Network.TestNet);
        BitcoinPubKeyAddress addr = key.PubKey.GetAddress(_network);

        var ipaddr = IPAddress.Parse("127.0.0.1");
        var peer = new NetworkAddress(ipaddr, 18333);

        var scanLocation = new BlockLocator();
        scanLocation.Blocks.Add(_network.GetGenesis().GetHash());
        var skipBefore = DateTime.Parse("13 Jul 2017"); 

        var chain = new ConcurrentChain(_network);
        ConnectNode(addr, peer, chain, scanLocation, skipBefore);

        Console.ReadLine();
    }

    private static void ConnectNode(BitcoinAddress addr, NetworkAddress peer, ConcurrentChain chain, BlockLocator scanLocation, DateTimeOffset skipBefore)
    {
        var parameters = new NodeConnectionParameters();

        // Create the minimum required behaviours.
        parameters.TemplateBehaviors.FindOrCreate<PingPongBehavior>();
        parameters.TemplateBehaviors.Add(new ChainBehavior(chain));
        parameters.TemplateBehaviors.Add(new TrackerBehavior(new Tracker(), chain));

        var addressManager = new AddressManager();
        addressManager.Add(peer, IPAddress.Loopback);

        parameters.TemplateBehaviors.Add(new AddressManagerBehavior(addressManager));
        var group = new NodesGroup(_network, parameters);
        group.AllowSameGroup = true;
        group.MaximumNodeConnection = 1;
        group.Requirements.SupportSPV = true;
        group.Connect();
        group.ConnectedNodes.Added += (s, e) =>
        {
            var node = e.Node;

            node.Disconnected += n =>
            {
                var _trackerBehavior = n.Behaviors.Find<TrackerBehavior>();
                scanLocation = _trackerBehavior.CurrentProgress;
                Console.WriteLine("Node disconnected, chain height " + chain.Tip.Height + ".");
            };

            // Start tracker scanning.
            var trackerBehavior = node.Behaviors.Find<TrackerBehavior>();
            Console.WriteLine("Tracking {0}", addr);
            trackerBehavior.Tracker.Add(addr.ScriptPubKey);
            trackerBehavior.Tracker.NewOperation += (Tracker sender, Tracker.IOperation trackerOperation) =>
            {
                var walletTransaction = trackerOperation.ToWalletTransaction(chain, "default");
                Console.WriteLine("TX ID: {0}.", walletTransaction.Transaction.GetHash());
            };

            trackerBehavior.Scan(scanLocation, skipBefore);
            trackerBehavior.SendMessageAsync(new MempoolPayload());

            trackerBehavior.RefreshBloomFilter();
        };
    }
}

}`

NicolasDorier commented 7 years ago

I think you should not use the behaviors yourself, this is way too tricky, take a look at https://github.com/NicolasDorier/NBitcoin.SPVSample this show how to use the SPVWallet class.

sipsorcery commented 7 years ago

Thanks for the response.

I have spent a fair bit of time trawling over the SPV sample as well as the NBitCoin source and also the eBook. I'm not specifically interested in writing a wallet and am instead attempting to get a better understanding of the BitCoin protocol.

The specific sample code above is to try and get a grip on BIP-0037 regarding Bloom filters. After some more testing I think the sample above is working but is just very slow due to frequent node disconnects related to false positive rate on the bloom filter.

Is there a different way to interact with filters using NBitCoin other than the TrackerBehavior? I suspect if I want more control over the BloomFilter I should start looking at a node.SendMessage approach rather than behaviors?

NicolasDorier commented 7 years ago

Frankly speaking, I would advise you against BIP37. This is leaking privacy and very difficult to get right. I think either using a block explorer or having a full block SPV (where client download a full block and check its transaction inside) are better solutions for efficiency, user experience, and privacy.

If you want to play with Bloom Filter to understand things, just connect to your own node with Node.ConnectToLocal(), and send the messages directly to it.

You can check how the RPC Tests are done in NBitcoin. I am using a NodeBuilder class which popup a bitcoin core on regtest under the hood. This allows you to test code against bitcoin core in a very repeatable way.

NicolasDorier commented 7 years ago

Behavior are for reusable, easy to attach logic to the node you connect. If your goal is to learn the protocol, just use node.SendMessageAsync and node.CreateListener()/listener.Receive

sipsorcery commented 7 years ago

Ok I'll play around with the direct messages rather than behaviors then, thanks for the pointers.

NicolasDorier commented 7 years ago

The pattern for not missing messages is

using(var listener = node.CreateListener())
{
     node.SendMessageAsync();
     listener.Receive()....
}