public void RegistrationStoreAddTest()
        {
            var privateKey = new Key();
            var token      = new RegistrationToken(255,
                                                   IPAddress.Parse("127.0.0.1"),
                                                   IPAddress.Parse("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
                                                   "",
                                                   37123,
                                                   new KeyId("dbb476190a81120928763ee8ce97e4c0bcfd6624"),
                                                   new KeyId("dbb476190a81120928763ee8ce97e4c0bcfd6624"),
                                                   privateKey.PubKey,
                                                   new Uri("https://redstone.com/test"));

            token.Signature = CryptoUtils.SignData(token.GetHeaderBytes().ToArray(), privateKey);

            RegistrationRecord record = new RegistrationRecord(DateTime.Now,
                                                               Guid.NewGuid(),
                                                               "137cc93a23383252348de58e53e193db471099e78ef89edf1fdea6ff340a7261",
                                                               "0100000027c16c59051604707fcfc75e7ab33decbe72e72baff44ad2b38b78a13929eb5d092df004b20200000048473044022067c3c50f13a1b68402549c6a049a9298d8de03e176909cc6b44f9fd9d9db532502203e7416233a125264aef46698b22f429f60be9ed3b2115ec24f0a05de2a4e4cd901feffffff1d92cb2776b3c3cb3e89ad84c38c72fc72646238d23312a6b1931e7980f90f590200000049483045022100d4141e26371a198fb22347e0cc64fd0563ee11640108836e5f427873e4aa0d00022012960447890e151b46a19c2d1d480f7e81e5ceb615c9566be03189108cb08ce401feffffffa23c4e0da07acb439735e6f2a3c7a1ee15521138cc3815fdbac428e1756a3976010000004948304502210083dfda6e8a7584741c79d60a8c69c372789eb28680fdf43fe15e67eb05c7628e022041cde42e79e83c5be5902c035abcde02579e1841473efbbeb5a909d4520a8ea801fefffffff3b56cf319b4b4895beae1e047d77fddcfd8b86a3964226c0d24dda83335e40802000000484730440220219623b855cd1ac0ad43cb3b76097cb52db2d37b4a6a768a5911dfa3571ff4b602203eb843072f746e225e6c7225438f4f4bf012c18f1a1572d4ead288190d8d61cb01feffffff15209f56f6f5d1246f43057ab546be192659a3c88365529667eb3aa364ed4392000000006b483045022100a5791707155d03fb6e770c0dd0924cf08a3bd3b6e0b5713d7d3e84dc5c2e18bc02200370afce15733f5139a018d3ffc5e594e646ce372274818d9780906f9fb3699101210344e875df3990bf55d7218020b09aea6e1383206ec88344847771b3bc0d72251bfeffffff0220188fba000000001976a914b88f742a0a07af27ccfe21de8a40b9f7541f3e0088ac00e87648170000001976a914db0be998354d2139b14e06459d295de03b94fadb88ac90920000",
                                                               token,
                                                               null);
            RegistrationStore store = new RegistrationStore(Path.GetTempFileName());

            Assert.True(store.Add(record));
        }
示例#2
0
        public void RegistrationStoreGetOneTest()
        {
            var token = new RegistrationToken(255,
                                              "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2",
                                              IPAddress.Parse("127.0.0.1"),
                                              IPAddress.Parse("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
                                              "0123456789ABCDEF",
                                              37123);

            token.RsaSignature   = Encoding.ASCII.GetBytes("xyz");
            token.EcdsaSignature = Encoding.ASCII.GetBytes("abc");

            RegistrationRecord record = new RegistrationRecord(DateTime.Now,
                                                               Guid.NewGuid(),
                                                               "137cc93a23383252348de58e53e193db471099e78ef89edf1fdea6ff340a7261",
                                                               "0100000027c16c59051604707fcfc75e7ab33decbe72e72baff44ad2b38b78a13929eb5d092df004b20200000048473044022067c3c50f13a1b68402549c6a049a9298d8de03e176909cc6b44f9fd9d9db532502203e7416233a125264aef46698b22f429f60be9ed3b2115ec24f0a05de2a4e4cd901feffffff1d92cb2776b3c3cb3e89ad84c38c72fc72646238d23312a6b1931e7980f90f590200000049483045022100d4141e26371a198fb22347e0cc64fd0563ee11640108836e5f427873e4aa0d00022012960447890e151b46a19c2d1d480f7e81e5ceb615c9566be03189108cb08ce401feffffffa23c4e0da07acb439735e6f2a3c7a1ee15521138cc3815fdbac428e1756a3976010000004948304502210083dfda6e8a7584741c79d60a8c69c372789eb28680fdf43fe15e67eb05c7628e022041cde42e79e83c5be5902c035abcde02579e1841473efbbeb5a909d4520a8ea801fefffffff3b56cf319b4b4895beae1e047d77fddcfd8b86a3964226c0d24dda83335e40802000000484730440220219623b855cd1ac0ad43cb3b76097cb52db2d37b4a6a768a5911dfa3571ff4b602203eb843072f746e225e6c7225438f4f4bf012c18f1a1572d4ead288190d8d61cb01feffffff15209f56f6f5d1246f43057ab546be192659a3c88365529667eb3aa364ed4392000000006b483045022100a5791707155d03fb6e770c0dd0924cf08a3bd3b6e0b5713d7d3e84dc5c2e18bc02200370afce15733f5139a018d3ffc5e594e646ce372274818d9780906f9fb3699101210344e875df3990bf55d7218020b09aea6e1383206ec88344847771b3bc0d72251bfeffffff0220188fba000000001976a914b88f742a0a07af27ccfe21de8a40b9f7541f3e0088ac00e87648170000001976a914db0be998354d2139b14e06459d295de03b94fadb88ac90920000",
                                                               token);
            RegistrationStore store = new RegistrationStore(Path.GetTempFileName());

            store.Add(record);

            var retrievedRecords = store.GetAll();

            Assert.Equal(retrievedRecords.Count, 1);

            var retrievedRecord = retrievedRecords[0].Record;

            Assert.Equal(retrievedRecord.ProtocolVersion, 255);
            Assert.Equal(retrievedRecord.ServerId, "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2");
            Assert.Equal(retrievedRecord.Ipv4Addr, IPAddress.Parse("127.0.0.1"));
            Assert.Equal(retrievedRecord.Ipv6Addr, IPAddress.Parse("2001:0db8:85a3:0000:0000:8a2e:0370:7334"));
            Assert.Equal(retrievedRecord.OnionAddress, "0123456789ABCDEF");
            Assert.Equal(retrievedRecord.Port, 37123);
        }
示例#3
0
		public void Register(object tag, Action<int> callback)
		{
			var rr = new RegistrationRecord();
			rr.tag = tag;
			rr.callback = callback;

			Unregister(tag);
			records.Add(rr);
			OnHooksChanged();
		}
示例#4
0
        public void Register(object tag, Action <int> callback)
        {
            var rr = new RegistrationRecord();

            rr.tag      = tag;
            rr.callback = callback;

            Unregister(tag);
            records.Add(rr);
            OnHooksChanged();
        }
示例#5
0
        public bool Delete(RegistrationRecord record)
        {
            try
            {
                Delete(record.RecordGuid);
            }
            catch (Exception)
            {
                return(false);
            }

            return(true);
        }
示例#6
0
        /// <summary>
        /// Checks if the server ID in the supplied record already exists in the store,
        /// and removes all other records before adding.
        /// </summary>
        /// <param name="regRecord"></param>
        /// <returns></returns>
        public bool AddWithReplace(RegistrationRecord regRecord)
        {
            foreach (RegistrationRecord record in GetByServerId(regRecord.Token.ServerId))
            {
                Delete(record.RecordGuid);
            }

            lock (lock_object)
            {
                List <RegistrationRecord> registrations = GetRecordsOrCreateFile();

                registrations.Add(regRecord);

                string regJson = JsonConvert.SerializeObject(registrations);
                File.WriteAllText(this.StorePath, regJson);

                return(true);
            }
        }
示例#7
0
        /// <inheritdoc />
        public async Task <Result <ClassicTumblerParameters> > ChangeServerAsync()
        {
            // First stop the state machine if applicable
            if (this.stateMachine != null && this.stateMachine.Started)
            {
                await this.stateMachine.Stop().ConfigureAwait(false);
            }

            this.State = TumbleState.OnlyMonitor;

            // Now select a different masternode
            List <RegistrationRecord> registrations = this.registrationStore.GetAll();

            if (registrations.Count < MINIMUM_MASTERNODE_COUNT)
            {
                this.logger.LogDebug("Not enough masternode registrations downloaded yet: " + registrations.Count);
                return(Result.Fail <ClassicTumblerParameters>("Not enough masternode registrations downloaded yet"));
            }

            registrations.Shuffle();

            // Since the list is shuffled, we can simply try the first one in the list.
            // Unlike the connect method, we only try one server here. That is because
            // a timeout can take in the order of minutes for each server tried.
            RegistrationRecord record = registrations.First();

            this.TumblerAddress = "ctb://" + record.Record.OnionAddress + ".onion?h=" + record.Record.ConfigurationHash;

            var attemptConnection = await TryUseServer();

            if (!attemptConnection.Failure)
            {
                return(attemptConnection);
            }

            // If we reach this point, the server was unreachable
            this.logger.LogDebug("Failed to connect to server, try another");
            return(Result.Fail <ClassicTumblerParameters>("Failed to connect to server, try another"));
        }
示例#8
0
        public bool Add(RegistrationRecord regRecord)
        {
            lock (lock_object)
            {
                List <RegistrationRecord> registrations = GetRecordsOrCreateFile();

                registrations.Add(regRecord);

                //JsonSerializerSettings settings = new JsonSerializerSettings();
                //settings.Converters.Add(new IPAddressConverter());
                //settings.Formatting = Formatting.Indented;

                //JsonSerializerSettings isoDateFormatSettings = new JsonSerializerSettings
                //{
                //    DateFormatHandling = DateFormatHandling.IsoDateFormat
                //};

                string regJson = JsonConvert.SerializeObject(registrations);
                File.WriteAllText(this.StorePath, regJson);

                return(true);
            }
        }
示例#9
0
        // TODO: how do we deal with re-orgs?
        private void ScanForServiceNodeRegistrations(int height, Block block)
        {
            foreach (Transaction tx in block.Transactions.Where(RegistrationToken.HasMarker))
            {
                this.logger.LogDebug("Received a new service node registration transaction: " + tx.GetHash());

                try
                {
                    var registrationToken = new RegistrationToken();
                    registrationToken.ParseTransaction(tx, this.network);

                    if (!registrationToken.Validate(this.network))
                    {
                        this.logger.LogDebug("Registration token failed validation");
                        continue;
                    }

                    // TODO: doesn't belong here
                    var merkleBlock        = new MerkleBlock(block, new[] { tx.GetHash() });
                    var registrationRecord = new RegistrationRecord(DateTime.Now, Guid.NewGuid(), tx.GetHash().ToString(), tx.ToHex(), registrationToken, merkleBlock.PartialMerkleTree, height);

                    string collateralAddress = registrationRecord.Token.CollateralPubKeyHash.GetAddress(this.network).ToString();

                    this.logger.LogTrace("New Service Node Registration");
                    var serviceNode = new Redstone.ServiceNode.Models.ServiceNode(registrationRecord);
                    this.serviceNodeManager.AddServiceNode(serviceNode);
                }
                catch (Exception e)
                {
                    this.logger.LogDebug("Failed to parse registration transaction, exception: " + e);
                }
            }

            this.serviceNodeManager.SyncedHeight    = height;
            this.serviceNodeManager.SyncedBlockHash = block.GetHash();
        }
示例#10
0
        /// <inheritdoc />
        public void ProcessBlock(int height, Block block)
        {
            // Check for any server registration transactions
            if (block.Transactions != null)
            {
                foreach (Transaction tx in block.Transactions)
                {
                    // Minor optimisation to disregard transactions that cannot be registrations
                    if (tx.Outputs.Count < 2)
                    {
                        continue;
                    }

                    // Check if the transaction has the Breeze registration marker output (literal text BREEZE_REGISTRATION_MARKER)
                    if (tx.Outputs[0].ScriptPubKey.ToHex().ToLower().Equals("6a1a425245455a455f524547495354524154494f4e5f4d41524b4552"))
                    {
                        this.logger.LogDebug("Received a new registration transaction: " + tx.GetHash());

                        try
                        {
                            RegistrationToken registrationToken = new RegistrationToken();
                            registrationToken.ParseTransaction(tx, this.network);

                            if (!registrationToken.Validate(this.network))
                            {
                                this.logger.LogDebug("Registration token failed validation");
                                continue;
                            }

                            MerkleBlock        merkleBlock        = new MerkleBlock(block, new uint256[] { tx.GetHash() });
                            RegistrationRecord registrationRecord = new RegistrationRecord(DateTime.Now, Guid.NewGuid(), tx.GetHash().ToString(), tx.ToHex(), registrationToken, merkleBlock.PartialMerkleTree, height);

                            // Ignore protocol versions outside the accepted bounds
                            if ((registrationRecord.Record.ProtocolVersion < MIN_PROTOCOL_VERSION) ||
                                (registrationRecord.Record.ProtocolVersion > MAX_PROTOCOL_VERSION))
                            {
                                this.logger.LogDebug("Registration protocol version out of bounds " + tx.GetHash());
                                continue;
                            }

                            // If there were other registrations for this server previously, remove them and add the new one
                            this.registrationStore.AddWithReplace(registrationRecord);

                            this.logger.LogDebug("Registration transaction for server collateral address: " + registrationRecord.Record.ServerId);
                            this.logger.LogDebug("Server Onion address: " + registrationRecord.Record.OnionAddress);
                            this.logger.LogDebug("Server configuration hash: " + registrationRecord.Record.ConfigurationHash);

                            // Add collateral address to watch only wallet so that any funding transactions can be detected
                            this.watchOnlyWalletManager.WatchAddress(registrationRecord.Record.ServerId);
                        }
                        catch (Exception e)
                        {
                            this.logger.LogDebug("Failed to parse registration transaction, exception: " + e);
                        }
                    }
                }

                WatchOnlyWallet watchOnlyWallet = this.watchOnlyWalletManager.GetWatchOnlyWallet();

                // TODO: Need to have 'current height' field in watch-only wallet so that we don't start rebalancing collateral balances before the latest block has been processed & incorporated

                // Perform watch-only wallet housekeeping - iterate through known servers
                foreach (RegistrationRecord record in this.registrationStore.GetAll())
                {
                    Script scriptToCheck = BitcoinAddress.Create(record.Record.ServerId, this.network).ScriptPubKey;

                    this.logger.LogDebug("Recalculating collateral balance for server: " + record.Record.ServerId);

                    if (!watchOnlyWallet.WatchedAddresses.ContainsKey(scriptToCheck.ToString()))
                    {
                        this.logger.LogDebug("Server address missing from watch-only wallet. Deleting stored registrations for server: " + record.Record.ServerId);
                        this.registrationStore.DeleteAllForServer(record.Record.ServerId);
                        continue;
                    }

                    Money serverCollateralBalance = this.watchOnlyWalletManager.GetRelativeBalance(scriptToCheck.ToString());

                    this.logger.LogDebug("Collateral balance for server " + record.Record.ServerId + " is " + serverCollateralBalance.ToString() + ", original registration height " + record.BlockReceived + ", current height " + height);

                    if ((serverCollateralBalance < MASTERNODE_COLLATERAL_THRESHOLD) && ((height - record.BlockReceived) > WINDOW_PERIOD_BLOCK_COUNT))
                    {
                        // Remove server registrations as funding has not been performed timeously,
                        // or funds have been removed from the collateral address subsequent to the
                        // registration being performed
                        this.logger.LogDebug("Insufficient collateral within window period for server: " + record.Record.ServerId);
                        this.logger.LogDebug("Deleting registration records for server: " + record.Record.ServerId);
                        this.registrationStore.DeleteAllForServer(record.Record.ServerId);

                        // TODO: Remove unneeded transactions from the watch-only wallet?
                        // TODO: Need to make the TumbleBitFeature change its server address if this is the address it was using
                    }
                }

                this.watchOnlyWalletManager.SaveWatchOnlyWallet();
            }
        }
示例#11
0
        /// <inheritdoc />
        public async Task <Result <ClassicTumblerParameters> > ConnectToTumblerAsync()
        {
            // Temporary hardcoding for testnet
            if (this.TumblerAddress == null)
            {
                this.TumblerAddress = "ctb://6cvi6ulcd4qn42mi.onion?h=95cb936fde9ae1856bcfd953746c26724a25dc46";
            }

            // If the -ppuri command line option wasn't used to bypass the registration store lookup
            if (this.TumblerAddress == null)
            {
                List <RegistrationRecord> registrations = this.registrationStore.GetAll();

                if (registrations.Count < MINIMUM_MASTERNODE_COUNT)
                {
                    this.logger.LogDebug("Not enough masternode registrations downloaded yet: " + registrations.Count);
                    return(null);
                }

                RegistrationRecord record            = null;
                RegistrationToken  registrationToken = null;
                bool validRegistrationFound          = false;

                // TODO: Search the registration store more robustly
                for (int i = 1; i < 10; i++)
                {
                    record            = registrations[random.Next(registrations.Count)];
                    registrationToken = record.Record;

                    // Implicitly, the registration feature will have deleted the registration if the collateral
                    // requirement was not met within 30 blocks
                    if ((this.walletManager.LastBlockHeight() - record.BlockReceived) >= 32)
                    {
                        validRegistrationFound = true;
                        break;
                    }
                }

                if (!validRegistrationFound)
                {
                    this.logger.LogDebug("Did not find a valid registration");
                    return(Result.Fail <ClassicTumblerParameters>("Did not find a valid registration"));
                }

                this.TumblerAddress = "ctb://" + registrationToken.OnionAddress + ".onion?h=" + registrationToken.ConfigurationHash;
            }

            this.tumblingState.TumblerUri = new Uri(this.TumblerAddress);

            FullNodeTumblerClientConfiguration config;

            if (this.TumblerAddress.Contains("127.0.0.1"))
            {
                config = new FullNodeTumblerClientConfiguration(this.tumblingState, onlyMonitor: false,
                                                                connectionTest: true, useProxy: false);
            }
            else
            {
                config = new FullNodeTumblerClientConfiguration(this.tumblingState, onlyMonitor: false,
                                                                connectionTest: true, useProxy: true);
            }

            TumblerClientRuntime rt = null;

            try
            {
                rt = await TumblerClientRuntime.FromConfigurationAsync(config, connectionTest : true)
                     .ConfigureAwait(false);

                // This is overwritten by the tumble method, but it is needed at the beginning of that method for the balance check
                this.TumblerParameters = rt.TumblerParameters;

                return(Result.Ok(rt.TumblerParameters));
            }
            catch (Exception cex) when(cex is PrivacyProtocolConfigException || cex is ConfigException)
            {
                this.logger.LogError("Error obtaining tumbler parameters: " + cex);
                return(Result.Fail <ClassicTumblerParameters>(
                           cex is PrivacyProtocolConfigException
                        ? "Tor is required for connectivity to an active Stratis Masternode. Please restart Breeze Wallet with Privacy Protocol and ensure that an instance of Tor is running."
                        : cex.Message));
            }
            catch (Exception e)
            {
                this.logger.LogError("Error obtaining tumbler parameters: " + e);
                return(Result.Fail <ClassicTumblerParameters>("Error obtaining tumbler parameters"));
            }
            finally
            {
                rt?.Dispose();
            }
        }
示例#12
0
        public bool CheckBreezeRegistration(BreezeConfiguration config, string regStorePath, string configurationHash, string onionAddress, RsaKey tumblerKey)
        {
            // In order to determine if the registration sequence has been performed
            // before, and to see if a previous performance is still valid, interrogate
            // the database to see if any transactions have been recorded.

            RegistrationStore regStore = new RegistrationStore(regStorePath);

            List <RegistrationRecord> transactions = regStore.GetByServerId(config.TumblerEcdsaKeyAddress);

            // If no transactions exist, the registration definitely needs to be done
            if (transactions == null || transactions.Count == 0)
            {
                return(false);
            }

            RegistrationRecord mostRecent = null;

            foreach (RegistrationRecord record in transactions)
            {
                // Find most recent transaction
                if (mostRecent == null)
                {
                    mostRecent = record;
                }

                if (record.RecordTimestamp > mostRecent.RecordTimestamp)
                {
                    mostRecent = record;
                }
            }

            // Check if the stored record matches the current configuration

            RegistrationToken registrationToken;

            try
            {
                registrationToken = mostRecent.Record;
            }
            catch (NullReferenceException e)
            {
                Console.WriteLine(e);
                return(false);
            }

            // IPv4
            if (config.Ipv4Address == null && registrationToken.Ipv4Addr != null)
            {
                return(false);
            }

            if (config.Ipv4Address != null && registrationToken.Ipv4Addr == null)
            {
                return(false);
            }

            if (config.Ipv4Address != null && registrationToken.Ipv4Addr != null)
            {
                if (!config.Ipv4Address.Equals(registrationToken.Ipv4Addr))
                {
                    return(false);
                }
            }

            // IPv6
            if (config.Ipv6Address == null && registrationToken.Ipv6Addr != null)
            {
                return(false);
            }

            if (config.Ipv6Address != null && registrationToken.Ipv6Addr == null)
            {
                return(false);
            }

            if (config.Ipv6Address != null && registrationToken.Ipv6Addr != null)
            {
                if (!config.Ipv6Address.Equals(registrationToken.Ipv6Addr))
                {
                    return(false);
                }
            }

            // Onion
            if (onionAddress != registrationToken.OnionAddress)
            {
                return(false);
            }

            if (config.Port != registrationToken.Port)
            {
                return(false);
            }

            // This verifies that the tumbler parameters are unchanged
            if (configurationHash != registrationToken.ConfigurationHash)
            {
                return(false);
            }

            // TODO: Check if transaction is actually confirmed on the blockchain?

            return(true);
        }
示例#13
0
        public Transaction PerformBreezeRegistration(BreezeConfiguration config, string regStorePath, string configurationHash, string onionAddress, RsaKey tumblerKey)
        {
            Network network = Network.ImpleumMain;

            if (config.TumblerNetwork == Network.TestNet || config.TumblerNetwork == Network.RegTest)
            {
                network = Network.ImpleumTest;
            }

            RPCHelper     stratisHelper   = null;
            RPCClient     stratisRpc      = null;
            BitcoinSecret privateKeyEcdsa = null;

            try {
                stratisHelper   = new RPCHelper(network);
                stratisRpc      = stratisHelper.GetClient(config.RpcUser, config.RpcPassword, config.RpcUrl);
                privateKeyEcdsa = stratisRpc.DumpPrivKey(BitcoinAddress.Create(config.TumblerEcdsaKeyAddress));
            }
            catch (Exception e) {
                Console.WriteLine("ERROR: Unable to retrieve private key to fund registration transaction");
                Console.WriteLine("Is the Stratis wallet unlocked & RPC enabled?");
                Console.WriteLine(e);
                Environment.Exit(0);
            }

            RegistrationToken registrationToken = new RegistrationToken(PROTOCOL_VERSION_TO_USE, config.TumblerEcdsaKeyAddress, config.Ipv4Address, config.Ipv6Address, onionAddress, configurationHash, config.Port, privateKeyEcdsa.PubKey);

            byte[] msgBytes = registrationToken.GetRegistrationTokenBytes(tumblerKey, privateKeyEcdsa);

            // Create the registration transaction using the bytes generated above
            Transaction rawTx = CreateBreezeRegistrationTx(network, msgBytes, config.TxOutputValueSetting);

            TransactionUtils  txUtils  = new TransactionUtils();
            RegistrationStore regStore = new RegistrationStore(regStorePath);

            try {
                // Replace fundrawtransaction with C# implementation. The legacy wallet
                // software does not support the RPC call.
                Transaction fundedTx = txUtils.FundRawTx(stratisRpc, rawTx, config.TxFeeValueSetting, BitcoinAddress.Create(config.TumblerEcdsaKeyAddress));
                RPCResponse signedTx = stratisRpc.SendCommand("signrawtransaction", fundedTx.ToHex());
                Transaction txToSend = new Transaction(((JObject)signedTx.Result)["hex"].Value <string>());

                RegistrationRecord regRecord = new RegistrationRecord(DateTime.Now,
                                                                      Guid.NewGuid(),
                                                                      txToSend.GetHash().ToString(),
                                                                      txToSend.ToHex(),
                                                                      registrationToken,
                                                                      null);

                regStore.Add(regRecord);

                stratisRpc.SendRawTransaction(txToSend);

                return(txToSend);
            }
            catch (Exception e)
            {
                Console.WriteLine("ERROR: Unable to broadcast registration transaction");
                Console.WriteLine(e);
            }

            return(null);
        }
        /// <inheritdoc />
        public void ProcessBlock(int height, Block block)
        {
            // Check for any server registration transactions
            if (block.Transactions != null)
            {
                foreach (Transaction tx in block.Transactions)
                {
                    // Check if the transaction has the Breeze registration marker output (literal text BREEZE_REGISTRATION_MARKER)
                    if (tx.Outputs[0].ScriptPubKey.ToHex().ToLower() == "6a1a425245455a455f524547495354524154494f4e5f4d41524b4552")
                    {
                        this.logger.LogDebug("Received a new registration transaction: " + tx.GetHash());

                        try
                        {
                            RegistrationToken registrationToken = new RegistrationToken();
                            registrationToken.ParseTransaction(tx, this.network);

                            if (!registrationToken.Validate(this.network))
                            {
                                continue;
                            }

                            MerkleBlock        merkleBlock        = new MerkleBlock(block, new uint256[] { tx.GetHash() });
                            RegistrationRecord registrationRecord = new RegistrationRecord(DateTime.Now, Guid.NewGuid(), tx.GetHash().ToString(), tx.ToHex(), registrationToken, merkleBlock.PartialMerkleTree, height);

                            // Ignore protocol versions outside the accepted bounds
                            if (registrationRecord.Record.ProtocolVersion < MIN_PROTOCOL_VERSION)
                            {
                                continue;
                            }

                            if (registrationRecord.Record.ProtocolVersion > MAX_PROTOCOL_VERSION)
                            {
                                continue;
                            }

                            // If there were other registrations for this server previously, remove them and add the new one
                            this.registrationStore.AddWithReplace(registrationRecord);

                            this.logger.LogDebug("Registration transaction for server collateral address: " + registrationRecord.Record.ServerId);
                            this.logger.LogDebug("Server Onion address: " + registrationRecord.Record.OnionAddress);
                            this.logger.LogDebug("Server configuration hash: " + registrationRecord.Record.ConfigurationHash);

                            this.watchOnlyWalletManager.WatchAddress(registrationRecord.Record.ServerId);
                        }
                        catch (Exception e)
                        {
                            this.logger.LogDebug("Failed to parse registration transaction " + tx.GetHash() + ", exception: " + e);
                        }
                    }
                }

                WatchOnlyWallet watchOnlyWallet = this.watchOnlyWalletManager.GetWatchOnlyWallet();

                // TODO: Need to have 'current height' field in watch-only wallet so that we don't start rebalancing collateral balances before the latest block has been processed & incorporated
                // Perform watch-only wallet housekeeping - iterate through known servers
                HashSet <string> knownServers = new HashSet <string>();

                // TODO: This is very CPU intensive and slows down the initial sync.
                // TODO: Incorporate a cache of server balances and a dirty flag on transaction receipt to trigger rebalance?
                foreach (RegistrationRecord record in this.registrationStore.GetAll())
                {
                    if (knownServers.Add(record.Record.ServerId))
                    {
                        this.logger.LogDebug("Calculating collateral balance for server: " + record.Record.ServerId);

                        //var addrToCheck = new WatchedAddress
                        //{
                        //    Script = BitcoinAddress.Create(record.Record.ServerId, this.network).ScriptPubKey,
                        //    Address = record.Record.ServerId
                        //};

                        var scriptToCheck = BitcoinAddress.Create(record.Record.ServerId, this.network).ScriptPubKey;

                        if (!watchOnlyWallet.WatchedAddresses.ContainsKey(scriptToCheck.ToString()))
                        {
                            this.logger.LogDebug("Server address missing from watch-only wallet. Deleting stored registrations for server: " + record.Record.ServerId);
                            this.registrationStore.DeleteAllForServer(record.Record.ServerId);
                            continue;
                        }

                        // Initially the server's balance is zero
                        Money serverCollateralBalance = new Money(0);

                        // Need this for looking up other transactions in the watch-only wallet
                        TransactionData prevTransaction;

                        // TODO: Move balance evaluation logic into helper methods in watch-only wallet itself
                        // Now evaluate the watch-only balance for this server
                        foreach (string txId in watchOnlyWallet.WatchedAddresses[scriptToCheck.ToString()].Transactions.Keys)
                        {
                            TransactionData transaction = watchOnlyWallet.WatchedAddresses[scriptToCheck.ToString()].Transactions[txId];

                            // First check if the inputs contain the watched address
                            foreach (TxIn input in transaction.Transaction.Inputs)
                            {
                                // See if we have the previous transaction in our watch-only wallet.
                                watchOnlyWallet.WatchedAddresses[scriptToCheck.ToString()].Transactions.TryGetValue(input.PrevOut.Hash.ToString(), out prevTransaction);

                                // If it is null, it can't be related to one of the watched addresses (or it is the very first watched transaction)
                                if (prevTransaction == null)
                                {
                                    continue;
                                }

                                if (prevTransaction.Transaction.Outputs[input.PrevOut.N].ScriptPubKey == scriptToCheck)
                                {
                                    // Input = funds are being paid out of the address in question

                                    // Computing the input value is a bit more complex than it looks, as the value is not directly stored
                                    // in a TxIn object. We need to check the output being spent by the input to get this information.
                                    // But even an OutPoint does not contain the Value - we need to check the other transactions in the
                                    // watch-only wallet to see if we have the prior transaction being referenced.

                                    // This does imply that the earliest transaction in the watch-only wallet (for this address) will not
                                    // have full previous transaction information stored. Therefore we can only reason about the address
                                    // balance after a given block height; any prior transactions are ignored.

                                    serverCollateralBalance -= prevTransaction.Transaction.Outputs[input.PrevOut.N].Value;
                                }
                            }

                            // Check if the outputs contain the watched address
                            foreach (var output in transaction.Transaction.Outputs)
                            {
                                //if (output.ScriptPubKey.GetScriptAddress(this.network).ToString() == record.Record.ServerId)
                                if (output.ScriptPubKey == scriptToCheck)
                                {
                                    // Output = funds are being paid into the address in question
                                    serverCollateralBalance += output.Value;
                                }
                            }
                        }

                        this.logger.LogDebug("Collateral balance for server " + record.Record.ServerId + " is " + serverCollateralBalance.ToString() + ", original registration height " + record.BlockReceived + " current height " + height);

                        // Ignore collateral requirements for protocol version 252, just in case
                        if ((serverCollateralBalance < MASTERNODE_COLLATERAL_THRESHOLD) && ((height - record.BlockReceived) > WINDOW_PERIOD_BLOCK_COUNT))
                        {
                            // Remove server registrations
                            this.logger.LogDebug("Insufficient collateral within window period for server: " + record.Record.ServerId);

                            this.logger.LogDebug("Deleting registration records for server: " + record.Record.ServerId);
                            this.registrationStore.DeleteAllForServer(record.Record.ServerId);

                            // TODO: Remove unneeded transactions from the watch-only wallet?

                            // TODO: Need to make the TumbleBitFeature change its server address if this is the address it was using
                        }
                    }
                }

                this.watchOnlyWalletManager.SaveWatchOnlyWallet();
            }
        }
示例#15
0
        /// <inheritdoc />
        public async Task <ClassicTumblerParameters> ConnectToTumblerAsync()
        {
            if (this.network == Network.TestNet)
            {
                this.TumblerAddress = "ctb://sz64kj6ev5576w34.onion?h=ceced829426faf63cb906b99e5ee1ff83f001a95";
            }
            else
            {
                this.TumblerAddress = "ctb://frspe6yz6en4wbrt.onion?h=14dad7205ff3632f5ab903b9052116397bf7302f";
            }

            // If the -ppuri command line option wasn't used to bypass the registration store lookup
            if (this.TumblerAddress == null)
            {
                List <RegistrationRecord> registrations = this.registrationStore.GetAll();

                if (registrations.Count < MINIMUM_MASTERNODE_COUNT)
                {
                    this.logger.LogDebug("Not enough masternode registrations downloaded yet: " + registrations.Count);
                    return(null);
                }

                RegistrationRecord record            = null;
                RegistrationToken  registrationToken = null;
                bool validRegistrationFound          = false;

                // TODO: Search the registration store more robustly
                for (int i = 1; i < 10; i++)
                {
                    record            = registrations[random.Next(registrations.Count)];
                    registrationToken = record.Record;

                    // Implicitly, the registration feature will have deleted the registration if the collateral
                    // requirement was not met within 30 blocks
                    if ((this.walletManager.LastBlockHeight() - record.BlockReceived) >= 32)
                    {
                        validRegistrationFound = true;
                        break;
                    }
                }

                if (!validRegistrationFound)
                {
                    this.logger.LogDebug("Did not find a valid registration");
                    return(null);
                }

                this.TumblerAddress = "ctb://" + registrationToken.OnionAddress + ".onion?h=" + registrationToken.ConfigurationHash;
            }

            this.tumblingState.TumblerUri = new Uri(this.TumblerAddress);
            var config = new FullNodeTumblerClientConfiguration(this.tumblingState, onlyMonitor: false, connectionTest: true);
            TumblerClientRuntime rt = null;

            try
            {
                rt = await TumblerClientRuntime.FromConfigurationAsync(config, connectionTest : true).ConfigureAwait(false);

                // This is overwritten by the tumble method, but it is needed at the beginning of that method for the balance check
                this.TumblerParameters = rt.TumblerParameters;

                return(rt.TumblerParameters);
            }
            catch (Exception e)
            {
                this.logger.LogError("Error obtaining tumbler parameters: " + e);
                return(null);
            }
            finally
            {
                rt?.Dispose();
            }
        }
示例#16
0
        /// <inheritdoc />
        public async Task <Result <ClassicTumblerParameters> > ConnectToTumblerAsync(HashSet <string> masternodeBlacklist = null)
        {
            // Assumptions about the current state coming into this method:
            // - If this is a first connection, this.TumblerAddress will be null
            // - If we were previously connected to a server, its URI would have been stored in the
            //   tumbling_state.json, and will have been loaded into this.TumblerAddress already
            List <RegistrationRecord> registrations = this.GetValidRegistrations();

            if (this.TumblerAddress == null)
            {
                if (registrations.Count < MINIMUM_MASTERNODE_COUNT)
                {
                    this.logger.LogDebug($"Not enough masternode registrations downloaded yet: {registrations.Count}");
                    return(Result.Fail <ClassicTumblerParameters>("Not enough masternode registrations downloaded yet", PostResultActionType.CanContinue));
                }

                registrations.Shuffle();

                // Since the list is shuffled, we can simply iterate through it and try each server until one is valid & reachable.
                foreach (RegistrationRecord record in registrations)
                {
                    if (record.Record.IsDummyAddress)
                    {
                        this.TumblerAddress = $"ctb://{record.Record.Ipv4Addr}:{record.Record.Port}?h={record.Record.ConfigurationHash}";
                    }
                    else
                    {
                        this.TumblerAddress = $"ctb://{record.Record.OnionAddress}.onion?h={record.Record.ConfigurationHash}";
                    }

                    //Do not attempt a connection to the Masternode which is blacklisted
                    if (masternodeBlacklist != null && masternodeBlacklist.Contains(this.TumblerAddress))
                    {
                        this.logger.LogDebug($"Skipping connection attempt to blacklisted masternode {this.TumblerAddress}");
                        continue;
                    }

                    //Overwrite tumbler address for regtest to indicate to the user that it it not a legimimate masternode
                    if (record.Record.IsDummyAddress && UseDummyAddress)
                    {
                        this.TumblerDisplayAddress = $"ctb://{record.Record.OnionAddress}.dummy?h={record.Record.ConfigurationHash}";
                    }
                    else
                    {
                        this.TumblerDisplayAddress = this.TumblerAddress;
                    }

                    var tumblerParameterResult = await TryUseServer();

                    if (tumblerParameterResult.Success)
                    {
                        return(tumblerParameterResult);
                    }
                    else if (tumblerParameterResult.PostResultAction == PostResultActionType.ShouldStop)
                    {
                        return(tumblerParameterResult);
                    }
                }

                this.logger.LogDebug($"Attempted connection to {registrations.Count} masternodes and did not find a valid registration");
                return(Result.Fail <ClassicTumblerParameters>("Did not find a valid registration", PostResultActionType.ShouldStop));
            }
            else
            {
                Result <ClassicTumblerParameters> tumblerParameterResult = await TryUseServer();

                Uri tumblerAddressUri = new Uri(this.TumblerAddress);

                RegistrationRecord record = registrations.FirstOrDefault(r => r.Record.Ipv4Addr != null && r.Record.Ipv4Addr.ToString() == tumblerAddressUri.DnsSafeHost);
                if (record != null)
                {
                    //Overwrite tumbler address for regtest to indicate to the user that it it not a legimimate masternode
                    if (record.Record.IsDummyAddress && UseDummyAddress)
                    {
                        this.TumblerDisplayAddress = $"ctb://{record.Record.OnionAddress}.dummy?h={record.Record.ConfigurationHash}";
                    }
                    else
                    {
                        this.TumblerDisplayAddress = this.TumblerAddress;
                    }
                }

                tumblerParameterResult = await TryUseServer();

                if (tumblerParameterResult.Success)
                {
                    return(tumblerParameterResult);
                }

                // Blacklist masternode address which we have just failed to connect to so that
                // we won't attempt to connect to it again in the next call to ConnectToTumblerAsync.
                HashSet <string> blacklistedMasternodes = new HashSet <string>()
                {
                    this.TumblerAddress
                };

                // The masternode that was being used in a previous run is now unreachable.
                // Restart the connection process and try to find a working server.
                this.TumblerAddress = null;
                return(await ConnectToTumblerAsync(blacklistedMasternodes));
            }
        }