/// <summary> /// Create a new TrackerBehavior instance /// </summary> /// <param name="tracker">The Tracker registering transactions and confirmations</param> /// <param name="chain">The chain used to fetch height of incoming blocks, if null, use the chain of ChainBehavior</param> public TrackerBehavior(Tracker tracker, ConcurrentChain chain = null) { if(tracker == null) throw new ArgumentNullException("tracker"); FalsePositiveRate = 0.000005; _Chain = chain; _ExplicitChain = chain; _Tracker = tracker; }
public void UnconfirmedTransactionsWithoutReceivedCoinsShouldNotShowUp() { BlockchainBuilder builder = new BlockchainBuilder(); Tracker tracker = new Tracker(); Key bob = new Key(); tracker.Add(bob); Key alice = new Key(); var tx1 = builder.GiveMoney(bob, Money.Coins(1.0m)); var b = builder.FindBlock(); tracker.NotifyTransaction(tx1, builder.Chain.Tip, b); var tx2 = builder.SpendCoin(tx1.Outputs.AsCoins().First(), alice, Money.Coins(0.1m)); b = builder.FindBlock(); tracker.NotifyTransaction(tx2, builder.Chain.Tip, b); var tx3 = builder.SpendCoin(tx2.Outputs.AsCoins().Skip(1).First(), alice, Money.Coins(0.2m)); Assert.True(tracker.NotifyTransaction(tx3)); var transactions = tracker.GetWalletTransactions(builder.Chain); Assert.True(transactions.Count == 3); Assert.True(transactions.Summary.UnConfirmed.TransactionCount == 1); Assert.True(transactions.Summary.UnConfirmed.Amount == -Money.Coins(0.2m)); Assert.True(transactions.Summary.Confirmed.TransactionCount == 2); Assert.True(transactions.Summary.Confirmed.Amount == Money.Coins(0.9m)); Assert.True(transactions.Summary.Spendable.TransactionCount == 3); Assert.True(transactions.Summary.Spendable.Amount == Money.Coins(0.7m)); builder.Chain.SetTip(builder.Chain.GetBlock(1)); transactions = tracker.GetWalletTransactions(builder.Chain); Action _ = () => { Assert.True(transactions.Count == 3); Assert.True(transactions.Summary.Confirmed.TransactionCount == 1); Assert.True(transactions.Summary.Confirmed.Amount == Money.Coins(1.0m)); Assert.True(transactions.Summary.Spendable.TransactionCount == 3); Assert.True(transactions.Summary.Spendable.Amount == Money.Coins(0.7m)); Assert.True(transactions.Summary.UnConfirmed.TransactionCount == 2); Assert.True(transactions.Summary.UnConfirmed.Amount == -Money.Coins(0.3m)); }; _(); tracker.NotifyTransaction(tx2); //Notifying tx2 should have no effect, since it already is accounted because it was orphaned _(); }
public void CanPrune() { BlockchainBuilder builder = new BlockchainBuilder(); Tracker tracker = new Tracker(); Key bob = new Key(); Key alice = new Key(); tracker.Add(bob); var oldUnconf = builder.GiveMoney(bob, Money.Coins(1.0m)); Assert.True(tracker.NotifyTransaction(oldUnconf)); builder.Mempool.Clear(); var oldConf = builder.GiveMoney(bob, Money.Coins(0.9m)); var oldConfSpent = builder.SpendCoin(oldConf.Outputs.AsCoins().First(), alice, Money.Coins(0.01m)); var block = builder.FindBlock(); Assert.True(tracker.NotifyTransaction(oldConf, builder.Chain.Tip, block)); Assert.True(tracker.NotifyTransaction(oldConfSpent, builder.Chain.Tip, block)); for(int i = 0 ; i < 9 ; i++) { builder.FindBlock(); } Assert.True(tracker.Prune(builder.Chain, 10).Count == 0); builder.FindBlock(); //Prune tracked outpoint var pruned = tracker.Prune(builder.Chain, 10); Assert.Equal(1, pruned.Count); Assert.True(pruned.First() is Tracker.TrackedOutpoint); //Prune old unconf pruned = tracker.Prune(builder.Chain, timeExpiration: TimeSpan.Zero); Assert.Equal(1, pruned.Count); var op = pruned.OfType<Tracker.Operation>().First(); Assert.True(op.BlockId == null); var conf = builder.GiveMoney(bob, Money.Coins(0.9m)); block = builder.FindBlock(); Assert.True(tracker.NotifyTransaction(conf, builder.Chain.Tip, block)); var oldSpentForked = builder.SpendCoin(conf.Outputs.AsCoins().First(), alice, Money.Coins(0.021m)); block = builder.FindBlock(); Assert.True(tracker.NotifyTransaction(oldSpentForked, builder.Chain.Tip, block)); var forked = builder.Chain.Tip; builder.Chain.SetTip(builder.Chain.Tip.Previous); for(int i = 0 ; i < 10 ; i++) { builder.FindBlock(); } pruned = tracker.Prune(builder.Chain, 10); Assert.True(pruned.Count == 1); //Tracked outpoint of conf Assert.True(pruned.First() is Tracker.TrackedOutpoint); block = builder.FindBlock(); pruned = tracker.Prune(builder.Chain, 10); //Old forked spent Assert.Equal(1, pruned.Count); op = pruned.OfType<Tracker.Operation>().First(); Assert.Equal(forked.HashBlock, op.BlockId); }
public void CanTrackScriptCoins() { BlockchainBuilder builder = new BlockchainBuilder(); Tracker tracker = new Tracker(); Key bob = new Key(); tracker.Add(bob.PubKey, true); var tx1 = builder.GiveMoney(bob.PubKey.ScriptPubKey.Hash, Money.Coins(1.0m)); Assert.True(tracker.NotifyTransaction(tx1)); Assert.True(tracker.GetWalletTransactions(builder.Chain)[0].ReceivedCoins[0] is ScriptCoin); }
public void CanTrackKey() { BlockchainBuilder builder = new BlockchainBuilder(); Key bob = new Key(); Tracker tracker = new Tracker(); tracker.Add(bob); var tx1 = builder.GiveMoney(bob, Money.Coins(1.0m)); var coin = tx1.Outputs.AsCoins().First(); Assert.True(tracker.NotifyTransaction(tx1)); Thread.Sleep(10); Key alice = new Key(); var tx2 = builder.SpendCoin(coin, alice, Money.Coins(0.6m)); Assert.True(tracker.NotifyTransaction(tx2)); var block = builder.FindBlock(); foreach(var btx in block.Transactions) { if(!btx.IsCoinBase) { Assert.True(tracker.NotifyTransaction(btx, builder.Chain.GetBlock(block.GetHash()), block)); Assert.True(tracker.NotifyTransaction(btx, builder.Chain.GetBlock(block.GetHash()), block)); //Idempotent } } var transactions = tracker.GetWalletTransactions(builder.Chain); Assert.True(transactions.Count == 2); Assert.True(transactions[0].Transaction.GetHash() == tx2.GetHash()); Assert.True(transactions[1].Transaction.GetHash() == tx1.GetHash()); Assert.True(transactions[0].Balance == -Money.Coins(0.6m)); var tx3 = builder.GiveMoney(bob, Money.Coins(0.01m)); coin = tx3.Outputs.AsCoins().First(); block = builder.FindBlock(); Assert.True(tracker.NotifyTransaction(block.Transactions[1], builder.Chain.GetBlock(block.GetHash()), block)); transactions = tracker.GetWalletTransactions(builder.Chain); Assert.True(transactions.Count == 3); Assert.True(transactions.Summary.UnConfirmed.TransactionCount == 0); Assert.True(transactions[0].Transaction.GetHash() == block.Transactions[1].GetHash()); Assert.Equal(2, transactions.GetSpendableCoins().Count()); // the 1 change + 1 gift builder.Chain.SetTip(builder.Chain.Tip.Previous); transactions = tracker.GetWalletTransactions(builder.Chain); Assert.True(transactions.Count == 3); Assert.True(transactions.Summary.UnConfirmed.TransactionCount == 1); //Test roundtrip serialization var filterBefore = tracker.CreateBloomFilter(0.005); MemoryStream ms = new MemoryStream(); tracker.Save(ms); tracker = new Tracker(); ms.Position = 0; tracker = Tracker.Load(ms); transactions = tracker.GetWalletTransactions(builder.Chain); Assert.True(transactions.Count == 3); Assert.True(transactions.Summary.UnConfirmed.TransactionCount == 1); var filterAfter = tracker.CreateBloomFilter(0.005); Assert.True(filterBefore.ToBytes().SequenceEqual(filterAfter.ToBytes())); ///// }
public static Tracker Load(Stream stream) { var tracker = new Tracker(); tracker.LoadCore(stream); return tracker; }
void _ListenerTracked_NewOperation(Tracker sender, Tracker.IOperation trackerOperation) { var newWalletTransaction = NewWalletTransaction; if(newWalletTransaction != null && _Group != null) { if(trackerOperation.ContainsWallet(Name)) { newWalletTransaction(this, trackerOperation.ToWalletTransaction(Chain, Name)); } } }
/// <summary> /// Configure the components of the wallet /// </summary> /// <param name="chain">The chain to keep in sync, if not provided the whole chain will be downloaded on the network (more than 30MB)</param> /// <param name="addrman">The Address Manager for speeding up peer discovery</param> /// <param name="tracker">The tracker responsible for providing bloom filters</param> public void Configure(ConcurrentChain chain = null, AddressManager addrman = null, Tracker tracker = null) { if(State != WalletState.Created) throw new InvalidOperationException("The wallet is already connecting or connected"); var parameters = new NodeConnectionParameters(); ConfigureDefaultNodeConnectionParameters(parameters); //Pick the behaviors if(addrman != null) parameters.TemplateBehaviors.Add(new AddressManagerBehavior(addrman)); //Listen addr, help for node discovery if(chain != null) parameters.TemplateBehaviors.Add(new ChainBehavior(chain)); //Keep chain in sync if(tracker != null) parameters.TemplateBehaviors.Add(new TrackerBehavior(tracker, chain)); //Set bloom filters and scan the blockchain Configure(parameters); }
private void AddKnown(KeyPath keyPath, Tracker tracker, bool isInternal) { if(_Parameters.UseP2SH) { var script = GetScriptPubKey(keyPath, true); _KnownScripts.Add(script.Hash.ScriptPubKey, keyPath); tracker.Add(script, true, isInternal, wallet: Name); } else { var script = GetScriptPubKey(keyPath, false); _KnownScripts.Add(script, keyPath); tracker.Add(script, false, isInternal, wallet: Name); } }
/// <summary> /// Connect the wallet with the given connection parameters /// </summary> /// <param name="parameters"></param> public void Connect(NodeConnectionParameters parameters) { if(State != WalletState.Created) throw new InvalidOperationException("The wallet is already connecting or connected"); var group = NodesGroup.GetNodeGroup(parameters); if(group == null) { group = new NodesGroup(_Parameters.Network, parameters); } parameters = group.NodeConnectionParameters; group.Requirements.MinVersion = ProtocolVersion.PROTOCOL_VERSION; group.Requirements.RequiredServices = NodeServices.Network; var chain = parameters.TemplateBehaviors.Find<ChainBehavior>(); if(chain == null) { chain = new ChainBehavior(new ConcurrentChain(_Parameters.Network)); parameters.TemplateBehaviors.Add(chain); } if(chain.Chain.Genesis.HashBlock != _Parameters.Network.GetGenesis().GetHash()) throw new InvalidOperationException("ChainBehavior with invalid network chain detected"); var addrman = parameters.TemplateBehaviors.Find<AddressManagerBehavior>(); if(addrman == null) { addrman = new AddressManagerBehavior(new AddressManager()); parameters.TemplateBehaviors.Add(addrman); } var tracker = parameters.TemplateBehaviors.Find<TrackerBehavior>(); if(tracker == null) { tracker = new TrackerBehavior(new Tracker(), chain.Chain); parameters.TemplateBehaviors.Add(tracker); } _Chain = chain.Chain; _AddressManager = addrman.AddressManager; _Tracker = tracker.Tracker; _TrackerBehavior = tracker; _Group = group; if(AddKnownScriptToTracker()) _Group.Purge("Bloom filter renew"); _State = WalletState.Disconnected; _Group.Connect(); _Group.ConnectedNodes.Added += ConnectedNodes_Added; _Group.ConnectedNodes.Removed += ConnectedNodes_Added; foreach(var node in _Group.ConnectedNodes) { node.Behaviors.Find<TrackerBehavior>().Scan(_ScanLocation, Created); } }
#pragma warning disable CS0612 // Type or member is obsolete public WalletTransactionsCollection GetTransactions() #pragma warning restore CS0612 // Type or member is obsolete { AssertGroupAffected(); return(Tracker.GetWalletTransactions(Chain, Name)); }