MetacoSA / NBitcoin

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

Listening to the Block Chain #321

Closed slashapps closed 6 years ago

slashapps commented 6 years ago

I tried to read the tutorials and eBook and couldn't find any reference - is there a way to use the NBitcoin package in order to listen to messages, transactions, confirmations, ect going on live in the blockchain ? Is there maybe a full documentation of the project, or just the eBook with its examples ?

NicolasDorier commented 6 years ago

@nopara73, I think you had sample usage of NodeBehavior for this?

nopara73 commented 6 years ago

@alonslash It is fairly complicated with NBitcoin for now. You must pretty much understand the peer to peer protocol of Bitcoin to do it properly.

Check out how I built and use my MemPoolBehaviour class. https://github.com/nopara73/HiddenWallet/blob/master/HiddenWallet.FullSpvWallet/MemPool/MemPoolBehaviour.cs

It would be a awesome project to build sample classes those can be used to do what you want.

Btw, if you are not going full decentralized/peer to peer then check out SmartBit's websocket notifications.

NicolasDorier commented 6 years ago

@nopara73 it is actually not complicated to connect to the network directly, will make a small copy pastable sample.

NicolasDorier commented 6 years ago
public class MyBehavior : NodeBehavior
        {

            public MyBehavior()
            {

            }

            protected override void AttachCore()
            {
                AttachedNode.MessageReceived += AttachedNode_MessageReceivedAsync;
            }

            protected override void DetachCore()
            {
                AttachedNode.MessageReceived -= AttachedNode_MessageReceivedAsync;
            }

            private async void AttachedNode_MessageReceivedAsync(Node node, IncomingMessage message)
            {
                if(message.Message.Payload is TxPayload txPayload)
                {
                    ProcessTxPayload(txPayload);
                    return;
                }

                if(message.Message.Payload is InvPayload invPayload)
                {
                    await ProcessInvAsync(node, invPayload);
                    return;
                }

            }

            private async Task ProcessInvAsync(Node node, InvPayload invPayload)
            {
                var send = new GetDataPayload();
                foreach(var inv in invPayload.Inventory.Where(inv => inv.Type.HasFlag(InventoryType.MSG_TX)))
                {
                    send.Inventory.Add(inv);
                }

                if(node.IsConnected)
                    await node.SendMessageAsync(send);
            }

            private void ProcessTxPayload(TxPayload transactionPayload)
            {
                Console.WriteLine("Received Tx" + transactionPayload.Object.GetHash());
            }

            public override object Clone()
            {
                return new MyBehavior();
            }
        }

Then you create a group using this behavior

var group = new NodesGroup(Network.Main);
group.NodeConnectionParameters.TemplateBehaviors.Add(new MyBehavior());
group.Connect();
Console.ReadLine();

It should just work. Might take some times to start as it takes time to discover peers. (will keep connection with 8 peers and print out every tx they send you)

slashapps commented 6 years ago

Works great, thanks! I'll try to test it and get also new blocks data.

NicolasDorier commented 6 years ago

@alonslash you might take a look to this. It for example show you how to save and load the addrman so you don't have to rediscover peers every times. And show other useful behaviors that you might want.

slashapps commented 6 years ago

Thanks. I'm using the PeriodicUiUpdate function to listen for new blocks coming in. This is the code I came up with:


               int PrevHeight = CurrentHeight;
                CurrentHeight = GetChain().Height;
                if (PrevHeight != CurrentHeight)
                {
                    if (_Group.ConnectedNodes.Count > 0)
                    {
                        Node FirstConnectedNode = _Group.ConnectedNodes.FirstOrDefault();
                        ChainedBlock LastChainBlock = GetChain().GetBlock(GetChain().Height);
                        IEnumerable<Block> LastBlockList = FirstConnectedNode.GetBlocks(new ChainedBlock[] { LastChainBlock });
                        if (LastBlockList.Count() > 0)
                        {
                            Block LastBlock = LastBlockList.FirstOrDefault();
                            Console.WriteLine("LastBlock: " + LastBlock);

                            Console.WriteLine("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
                            Console.WriteLine("!!!!!    NEW BLOCK");
                            Console.WriteLine("!!!!!    " + CurrentHeight);
                            Console.WriteLine("!!!!!    " + LastBlock.GetHash());
                            Console.WriteLine("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");

                            Console.WriteLine("LastBlock Transactions: " + LastBlock.Transactions.Count);
                            Console.WriteLine("LastBlock First Transaction: " + LastBlock.Transactions[0].GetHash());
                        }
                    }
                }

In order to get last block I'm using one of the connected nodes from the group. Is this the right way to it?

nopara73 commented 6 years ago

@alonslash As long as no peer wants to sends you funny blocks.

NicolasDorier commented 6 years ago

@alonslash even if it might work:

  1. You are supposing that the first node can give you blocks (this is not always true)
  2. You are blocking the UI thread by not using async/await
  3. You should use Behaviors to fetch blocks and save them in the DB, your loop would just query the DB.
slashapps commented 6 years ago

@NicolasDorier @nopara73 I'm not using the UI, I'm using a console app. It's a separate running thread (I changed the name of the function "PeriodicUiUpdate"). As for Behaviors - I'm using the ChainBehavior added to TemplateBehaviors. Doesn't it get its blocks data from connected nodes? Just for general information - is a situation where no node gives me blocks possible? Or, can a node send invalid (funny) blocks?

NicolasDorier commented 6 years ago

@alonslash ChainBehavior only get the header chain.

Just for general information - is a situation where no node gives me blocks possible? Or, can a node send invalid (funny) blocks?

Yes to both. This is why I advise people to connect only to a trusted node.

slashapps commented 6 years ago

@NicolasDorier How can I control it, is it a parameter on NodeConnectionParameters? How can I know which node is trusted and which isn't?

nnam2404 commented 6 years ago

Works great, thanks very much! But how can i get confirmed of my transaction? I'm a newbie.

NicolasDorier commented 6 years ago

@alonslash Run your own bitcoin core node and configure the NodeGroup to connect only to that.

NicolasDorier commented 6 years ago

@nnam2404 start here https://www.gitbook.com/book/programmingblockchain/programmingblockchain/details

nnam2404 commented 6 years ago

@NicolasDorier Thank you so much! It's very helpful.