public bool VerifyCollateral(BreezeConfiguration config, out Money missingFunds) { Network network = Network.ImpleumMain; if (config.TumblerNetwork == Network.TestNet || config.TumblerNetwork == Network.RegTest) { network = Network.ImpleumTest; } var stratisHelper = new RPCHelper(network); var stratisRpc = stratisHelper.GetClient(config.RpcUser, config.RpcPassword, config.RpcUrl); RPCResponse listAddressGroupings = stratisRpc.SendCommand("listaddressgroupings"); var t = listAddressGroupings.Result; decimal collateralBalance = (from p in t from e in p where e.First.Value <String>() == config.TumblerEcdsaKeyAddress select e.ElementAt(1).Value <decimal>()).First(); missingFunds = RegistrationParameters.MASTERNODE_COLLATERAL_THRESHOLD - new Money(collateralBalance, MoneyUnit.BTC); if (missingFunds <= 0) { missingFunds = new Money(0m, MoneyUnit.BTC); return(true); } return(false); }
public Transaction PerformBreezeRegistration(BreezeConfiguration config, DBUtils db) { var network = Network.StratisMain; if (config.IsTestNet) { // TODO: Change to StratisTest when support is added to NStratis network = Network.TestNet; } 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 wallet unlocked?"); Console.WriteLine(e); Environment.Exit(0); } // Retrieve tumbler's parameters so that the registration details can be constructed //var tumblerApi = new TumblerApiAccess(config.TumblerApiBaseUrl); //string json = tumblerApi.GetParameters().Result; //var tumblerParameters = JsonConvert.DeserializeObject<TumblerParameters>(json); var registrationToken = new RegistrationToken(255, config.Ipv4Address, config.Ipv6Address, config.OnionAddress, config.Port, config.TumblerRsaKeyPath); var msgBytes = registrationToken.GetRegistrationTokenBytes(privateKeyEcdsa); // Create the registration transaction using the bytes generated above var rawTx = CreateBreezeRegistrationTx(network, msgBytes, config.TxOutputValueSetting); var txUtils = new TransactionUtils(); try { // Replace fundrawtransaction with C# implementation. The legacy wallet // software does not support the RPC call. var fundedTx = txUtils.FundRawTx(stratisRpc, rawTx, config.TxFeeValueSetting, BitcoinAddress.Create(config.TumblerEcdsaKeyAddress)); var signedTx = stratisRpc.SendCommand("signrawtransaction", fundedTx.ToHex()); var txToSend = new Transaction(((JObject)signedTx.Result)["hex"].Value <string>()); db.UpdateOrInsert <string>("RegistrationTransactions", DateTime.Now.ToString("yyyyMMddHHmmss"), txToSend.ToHex(), (o, n) => n); stratisRpc.SendRawTransaction(txToSend); return(txToSend); } catch (Exception e) { Console.WriteLine("ERROR: Unable to broadcast registration transaction"); Console.WriteLine(e); } return(null); }
public static void Main(string[] args) { var serviceProvider = new ServiceCollection() .AddLogging() .AddSingleton <ITumblerService, TumblerService>() .BuildServiceProvider(); serviceProvider .GetService <ILoggerFactory>() .AddConsole(LogLevel.Debug); // TODO: It is messy having both a BreezeServer logger and an NTumbleBit logger var logger = serviceProvider.GetService <ILoggerFactory>() .CreateLogger <Program>(); logger.LogInformation("{Time} Reading Breeze server configuration", DateTime.Now); // Check OS-specific default config path for the config file. Create default file if it does not exist var configDir = BreezeConfiguration.GetDefaultDataDir("BreezeServer"); var configPath = Path.Combine(configDir, "breeze.conf"); logger.LogInformation("{Time} Configuration file path {Path}", DateTime.Now, configPath); var config = new BreezeConfiguration(configPath); var dbPath = Path.Combine(configDir, "db"); logger.LogInformation("{Time} Database path {Path}", DateTime.Now, dbPath); var db = new DBUtils(dbPath); logger.LogInformation("{Time} Checking node registration on the blockchain", DateTime.Now); var registration = new BreezeRegistration(); if (!registration.CheckBreezeRegistration(config, db)) { logger.LogInformation("{Time} Creating or updating node registration", DateTime.Now); var regTx = registration.PerformBreezeRegistration(config, db); if (regTx != null) { logger.LogInformation("{Time} Submitted transaction {TxId} via RPC for broadcast", DateTime.Now, regTx.GetHash().ToString()); } else { logger.LogInformation("{Time} Unable to broadcast transaction via RPC", DateTime.Now); Environment.Exit(0); } } else { logger.LogInformation("{Time} Node registration has already been performed", DateTime.Now); } logger.LogInformation("{Time} Starting Tumblebit server", DateTime.Now); db.UpdateOrInsert <string>("TumblerStartupLog", DateTime.Now.ToString("yyyyMMddHHmmss"), "Tumbler starting", (o, n) => n); var tumbler = serviceProvider.GetService <ITumblerService>(); tumbler.StartTumbler(config.IsTestNet); }
public bool CheckBreezeRegistration(BreezeConfiguration config, DBUtils db) { var network = Network.StratisMain; if (config.IsTestNet) { // TODO: Change to StratisTest when it is added to NStratis network = Network.TestNet; } // 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. var transactions = db.GetDictionary <string, string>("RegistrationTransactions"); // If no transactions exist, the registration definitely needs to be done if (transactions == null || transactions.Count == 0) { return(false); } string highestKey = null; foreach (var txn in transactions) { // Find most recent transaction. Assume that the rowKeys are ordered // lexicographically. if (highestKey == null) { highestKey = txn.Key; } if (String.Compare(txn.Key, highestKey) == 1) { highestKey = txn.Key; } } var mostRecentTxn = new Transaction(transactions[highestKey]); // Decode transaction and check if the decoded bitstream matches the // current configuration // TODO: Check if transaction is actually confirmed on the blockchain? var registrationToken = new RegistrationToken(); registrationToken.ParseTransaction(mostRecentTxn, network); if (!config.Ipv4Address.Equals(registrationToken.Ipv4Addr)) { return(false); } if (!config.Ipv6Address.Equals(registrationToken.Ipv6Addr)) { return(false); } if (config.OnionAddress != registrationToken.OnionAddress) { return(false); } if (config.Port != registrationToken.Port) { return(false); } return(true); }
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); }
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); }
public static void Main(string[] args) { var serviceProvider = new ServiceCollection() .AddLogging() .AddSingleton <ITumblerService, TumblerService>() .BuildServiceProvider(); serviceProvider .GetService <ILoggerFactory>() .AddConsole(LogLevel.Debug); // TODO: It is messy having both a BreezeServer logger and an NTumbleBit logger var logger = serviceProvider.GetService <ILoggerFactory>() .CreateLogger <Program>(); logger.LogInformation("{Time} Reading Breeze server configuration", DateTime.Now); // Check OS-specific default config path for the config file. Create default file if it does not exist string configDir = BreezeConfiguration.GetDefaultDataDir("BreezeServer"); string configPath = Path.Combine(configDir, "breeze.conf"); logger.LogInformation("{Time} Configuration file path {Path}", DateTime.Now, configPath); BreezeConfiguration config = new BreezeConfiguration(configPath); logger.LogInformation("{Time} Pre-initialising server to obtain parameters for configuration", DateTime.Now); var preTumblerConfig = serviceProvider.GetService <ITumblerService>(); preTumblerConfig.StartTumbler(config.IsTestNet, true); string configurationHash = preTumblerConfig.runtime.ClassicTumblerParameters.GetHash().ToString(); string onionAddress = preTumblerConfig.runtime.TorUri.Host.Substring(0, 16); NTumbleBit.RsaKey tumblerKey = preTumblerConfig.runtime.TumblerKey; // Mustn't be occupying hidden service URL when the TumblerService is reinitialised preTumblerConfig.runtime.TorConnection.Dispose(); // No longer need this instance of the class preTumblerConfig = null; string regStorePath = Path.Combine(configDir, "registrationHistory.json"); logger.LogInformation("{Time} Registration history path {Path}", DateTime.Now, regStorePath); logger.LogInformation("{Time} Checking node registration", DateTime.Now); BreezeRegistration registration = new BreezeRegistration(); if (!registration.CheckBreezeRegistration(config, regStorePath, configurationHash, onionAddress, tumblerKey)) { logger.LogInformation("{Time} Creating or updating node registration", DateTime.Now); var regTx = registration.PerformBreezeRegistration(config, regStorePath, configurationHash, onionAddress, tumblerKey); if (regTx != null) { logger.LogInformation("{Time} Submitted transaction {TxId} via RPC for broadcast", DateTime.Now, regTx.GetHash().ToString()); } else { logger.LogInformation("{Time} Unable to broadcast transaction via RPC", DateTime.Now); Environment.Exit(0); } } else { logger.LogInformation("{Time} Node registration has already been performed", DateTime.Now); } logger.LogInformation("{Time} Starting Tumblebit server", DateTime.Now); var tumbler = serviceProvider.GetService <ITumblerService>(); tumbler.StartTumbler(config.IsTestNet, false); }
public static void Main(string[] args) { var comparer = new CommandlineArgumentComparer(); var isRegTest = args.Contains("regtest", comparer); var isTestNet = args.Contains("testnet", comparer); var forceRegistration = args.Contains("forceRegistration", comparer); var useTor = !args.Contains("noTor", comparer); TumblerProtocolType?tumblerProtocol = null; try { string tumblerProtocolString = args.Where(a => a.StartsWith("-tumblerProtocol=")).Select(a => a.Substring("-tumblerProtocol=".Length).Replace("\"", "")).FirstOrDefault(); if (!isRegTest && (tumblerProtocolString != null || !useTor)) { Console.WriteLine("Options -TumblerProtocol and -NoTor can only be used in combination with -RegTest switch."); return; } if (tumblerProtocolString != null) { tumblerProtocol = Enum.Parse <TumblerProtocolType>(tumblerProtocolString, true); } if (useTor && tumblerProtocol.HasValue && tumblerProtocol.Value == TumblerProtocolType.Http) { Console.WriteLine("TumblerProtocol can only be changed to Http when Tor is disabled. Please use -NoTor switch to disable Tor."); return; } } catch { Console.WriteLine($"Incorrect tumbling prococol specified; the valid values are {TumblerProtocolType.Tcp} and {TumblerProtocolType.Http}"); return; } var serviceProvider = new ServiceCollection() .AddLogging() .AddSingleton <ITumblerService, TumblerService>() .BuildServiceProvider(); serviceProvider .GetService <ILoggerFactory>() .AddConsole(LogLevel.Debug); // TODO: It is messy having both a BreezeServer logger and an NTumbleBit logger var logger = serviceProvider.GetService <ILoggerFactory>() .CreateLogger <Program>(); logger.LogInformation("{Time} Reading Breeze server configuration", DateTime.Now); // Check OS-specific default config path for the config file. Create default file if it does not exist string configDir = BreezeConfiguration.GetDefaultDataDir("BreezeServer"); if (isRegTest) { configDir = Path.Combine(configDir, "ImpleumRegTest"); } else if (isTestNet) { configDir = Path.Combine(configDir, "ImpleumTest"); } else { configDir = Path.Combine(configDir, "ImpleumMain"); } string configPath = Path.Combine(configDir, "breeze.conf"); logger.LogInformation("{Time} Configuration file path {Path}", DateTime.Now, configPath); BreezeConfiguration config = new BreezeConfiguration(configPath); if (!useTor) { config.UseTor = false; } logger.LogInformation("{Time} Pre-initialising server to obtain parameters for configuration", DateTime.Now); var preTumblerConfig = serviceProvider.GetService <ITumblerService>(); preTumblerConfig.StartTumbler(config, true, torMandatory: !isRegTest, tumblerProtocol: tumblerProtocol); string configurationHash = preTumblerConfig.runtime.ClassicTumblerParameters.GetHash().ToString(); string onionAddress = preTumblerConfig.runtime.TorUri.Host.Substring(0, 16); NTumbleBit.RsaKey tumblerKey = preTumblerConfig.runtime.TumblerKey; // No longer need this instance of the class if (config.UseTor) { preTumblerConfig.runtime.TorConnection.Dispose(); } preTumblerConfig = null; string regStorePath = Path.Combine(configDir, "registrationHistory.json"); logger.LogInformation("{Time} Registration history path {Path}", DateTime.Now, regStorePath); logger.LogInformation("{Time} Checking node registration", DateTime.Now); BreezeRegistration registration = new BreezeRegistration(); if (forceRegistration || !registration.CheckBreezeRegistration(config, regStorePath, configurationHash, onionAddress, tumblerKey)) { logger.LogInformation("{Time} Creating or updating node registration", DateTime.Now); var regTx = registration.PerformBreezeRegistration(config, regStorePath, configurationHash, onionAddress, tumblerKey); if (regTx != null) { logger.LogInformation("{Time} Submitted transaction {TxId} via RPC for broadcast", DateTime.Now, regTx.GetHash().ToString()); } else { logger.LogInformation("{Time} Unable to broadcast transaction via RPC", DateTime.Now); Environment.Exit(0); } } else { logger.LogInformation("{Time} Node registration has already been performed", DateTime.Now); } // Perform collateral balance check and report the result Money collateralShortfall; if (registration.VerifyCollateral(config, out collateralShortfall)) { logger.LogInformation($"{{Time}} The collateral address {config.TumblerEcdsaKeyAddress} has sufficient funds.", DateTime.Now); } else { logger.LogWarning($"{{Time}} The collateral address {config.TumblerEcdsaKeyAddress} doesn't have enough funds. Collateral requirement is {RegistrationParameters.MASTERNODE_COLLATERAL_THRESHOLD} but only {collateralShortfall} is available at the collateral address. This is expected if you have only just run the masternode for the first time. Please send funds to the collateral address no later than {RegistrationParameters.WINDOW_PERIOD_BLOCK_COUNT} blocks after the registration transaction.", DateTime.Now); } logger.LogInformation("{Time} Starting Tumblebit server", DateTime.Now); // The TimeStamp and BlockSignature flags could be set to true when the Stratis network is instantiated. // We need to set it to false here to ensure compatibility with the Bitcoin protocol. Transaction.TimeStamp = false; Block.BlockSignature = false; var tumbler = serviceProvider.GetService <ITumblerService>(); tumbler.StartTumbler(config, false, torMandatory: !isRegTest, tumblerProtocol: tumblerProtocol); }