public static AjaxCallResult SetBitcoinPayoutAddress(string bitcoinAddress) { AuthenticationData authData = GetAuthenticationDataAndCulture(); if (authData == null) { throw new UnauthorizedAccessException(); } // Remove whitespace from submitted address (whitespace will probably be entered in some cases) bitcoinAddress = bitcoinAddress.Replace(" ", string.Empty); // Remove a possible start of "bitcoincash:" if (bitcoinAddress.StartsWith("bitcoincash:")) { bitcoinAddress = bitcoinAddress.Substring("bitcoincash:".Length); } if (string.IsNullOrEmpty(authData.CurrentUser.BitcoinPayoutAddress)) { if (!BitcoinUtility.IsValidBitcoinAddress(bitcoinAddress)) { return(new AjaxCallResult { Success = false, DisplayMessage = "Invalid address" }); } authData.CurrentUser.BitcoinPayoutAddress = bitcoinAddress; authData.CurrentUser.BitcoinPayoutAddressTimeSet = DateTime.UtcNow.ToString(CultureInfo.InvariantCulture); // TODO: Create notifications for CEO and for user NotificationCustomStrings strings = new NotificationCustomStrings(); strings["BitcoinAddress"] = bitcoinAddress; OutboundComm userNotify = OutboundComm.CreateNotification(authData.CurrentOrganization, "BitcoinPayoutAddress_Set", strings, People.FromSingle(authData.CurrentUser)); strings["ConcernedPersonName"] = authData.CurrentUser.Canonical; OutboundComm adminNotify = OutboundComm.CreateNotification(authData.CurrentOrganization, "BitcoinPayoutAddress_Set_OfficerNotify", strings); // will send to admins of org as no people specified return(new AjaxCallResult { Success = true }); } else { // If the address is already set return(new AjaxCallResult { Success = false, DisplayMessage = "Address already set" }); } }
public static void VerifyBitcoinHotWallet() { // This must only be run from the backend if (HttpContext.Current != null) { throw new InvalidOperationException("Checking root keys cannot be done from the frontend"); } // Make sure there's always a private hotwallet root, regardless of whether it's used or not if (!File.Exists(SystemSettings.EtcFolder + Path.DirectorySeparatorChar + "hotwallet")) { ExtKey privateRoot = new ExtKey(); File.WriteAllText(SystemSettings.EtcFolder + Path.DirectorySeparatorChar + "hotwallet", privateRoot.GetWif(Network.Main).ToWif(), Encoding.ASCII); File.WriteAllText( SystemSettings.EtcFolder + Path.DirectorySeparatorChar + "hotwallet-created-" + DateTime.UtcNow.ToString("yyyy-MM-dd--HH-mm-ss--fff.backup"), privateRoot.GetWif(Network.Main).ToWif(), Encoding.ASCII); // an extra backup if (String.IsNullOrEmpty(Persistence.Key["BitcoinHotPublicRoot"])) { Persistence.Key["BitcoinHotPublicRoot"] = privateRoot.Neuter().GetWif(Network.Main).ToWif(); } } else { // The file exists. Does the database have the hotwallet public root? if (Persistence.Key["BitcoinHotPublicRoot"].Length < 3) { // No, it has disappeared, which can happen for a few bad reasons Persistence.Key["BitcoinHotPublicRoot"] = BitcoinHotPrivateRoot.Neuter().GetWif(Network.Main).ToWif(); if (!PilotInstallationIds.IsPilot(PilotInstallationIds.DevelopmentSandbox)) { // TODO: Log some sort of exception (the sandbox db is reset every night, so it's ok to lose the public key from there) } } // Is the hotwallet public root equal to the private root, while in production environment? // ReSharper disable once RedundantCheckBeforeAssignment if (Persistence.Key["BitcoinHotPublicRoot"] != BitcoinHotPrivateRoot.Neuter().GetWif(Network.Main).ToWif() && !Debugger.IsAttached) { // SERIOUS CONDITION - the public root key did not match the private root key. This needs to be logged somewhere. OutboundComm.CreateNotification(NotificationResource.System_PublicRootReset); // Reset it Persistence.Key["BitcoinHotPublicRoot"] = BitcoinHotPrivateRoot.Neuter().GetWif(Network.Main).ToWif(); } } }
public static void UpgradeSchemata() { int currentDbVersion = SwarmDb.DbVersion; int expectedDbVersion = SwarmDb.DbVersionExpected; string sql; bool upgraded = false; while (currentDbVersion < expectedDbVersion) { currentDbVersion++; string fileName = String.Format("http://packages.swarmops.com/schemata/upgrade-{0:D4}.sql", currentDbVersion); using (WebClient client = new WebClient()) { sql = client.DownloadString(fileName); } string[] sqlCommands = sql.Split('#'); // in the file, the commands are split by a single # sign. (Semicolons are an integral part of storedprocs, so they can't be used.) foreach (string sqlCommand in sqlCommands) { try { SwarmDb.GetDatabaseForAdmin().ExecuteAdminCommand(sqlCommand.Trim()); } catch (MySqlException exception) { SwarmDb.GetDatabaseForWriting() .CreateExceptionLogEntry(DateTime.UtcNow, "DatabaseUpgrade", exception); // Continue processing after logging error. // TODO: Throw and abort? Tricky decision } } upgraded = true; SwarmDb.GetDatabaseForWriting() .SetKeyValue("DbVersion", currentDbVersion.ToString(CultureInfo.InvariantCulture)); // Increment after each successful run } if (upgraded) { try { OutboundComm.CreateNotification(null, NotificationResource.System_DatabaseSchemaUpgraded); } catch (ArgumentException) { // this is ok - if we're in the install process, person 1 or other notification targets won't exist yet } } }
public static void UpgradeSchemata() { int currentDbVersion = SwarmDb.DbVersion; int expectedDbVersion = SwarmDb.DbVersionExpected; string sql; bool upgraded = false; if (currentDbVersion < expectedDbVersion) { Console.WriteLine("Swarmops: Current DB version is {0}, but expected is {1}. A schema upgrade will take place.", currentDbVersion, expectedDbVersion); } while (currentDbVersion < expectedDbVersion) { currentDbVersion++; Console.Write("Schema {0} diff: Fetching...", currentDbVersion); string fileName = String.Format("http://packages.swarmops.com/schemata/upgrade-{0:D4}.sql", currentDbVersion); try { using (WebClient client = new WebClient()) { sql = client.DownloadString(fileName); } } catch (Exception outerException) { Console.WriteLine(" trying fallback..."); // Because Mono installs with an insufficient certificate store, we must disable certificate checking before accessing github SupportFunctions.DisableSslCertificateChecks(); fileName = String.Format("https://raw.githubusercontent.com/Swarmops/Swarmops/master/Database/Schemata/upgrade-{0:D4}.sql", currentDbVersion); try { using (WebClient client = new WebClient()) { sql = client.DownloadString(fileName); } } catch (Exception middleException) { try { OutboundComm.CreateNotification(null, NotificationResource.System_DatabaseUpgradeFailed); } catch (ArgumentException) { // if this happens during setup: throw new Exception("Failed fetching upgrade packages:\r\n" + middleException.ToString() + "\r\n" + outerException.ToString()); } Console.WriteLine(" FAILED! Aborting."); return; } } string[] sqlCommands = sql.Split('#'); // in the file, the commands are split by a single # sign. (Semicolons are an integral part of storedprocs, so they can't be used.) Console.Write(" applying..."); foreach (string sqlCommand in sqlCommands) { try { string trimmedCommand = sqlCommand.Trim().TrimEnd(';').Trim(); // removes whitespace first, then any ; at the end (if left in by mistake) if (!String.IsNullOrWhiteSpace(trimmedCommand)) { SwarmDb.GetDatabaseForAdmin().ExecuteAdminCommand(trimmedCommand); } } catch (MySqlException exception) { SwarmDb.GetDatabaseForWriting() .CreateExceptionLogEntry(DateTime.UtcNow, "DatabaseUpgrade", new Exception(string.Format("Exception upgrading to Db{0:D4}", currentDbVersion), exception)); Console.Write(" EXCEPTION (see log)!"); // Continue processing after logging error. // TODO: Throw and abort? Tricky decision } } upgraded = true; SwarmDb.GetDatabaseForWriting() .SetKeyValue("DbVersion", currentDbVersion.ToString(CultureInfo.InvariantCulture)); // Increment after each successful run Console.WriteLine(" done."); } if (upgraded) { Console.WriteLine("Swarmops database schema upgrade completed.\r\n"); try { OutboundComm.CreateNotification(null, NotificationResource.System_DatabaseSchemaUpgraded); } catch (ArgumentException) { // this is ok - if we're in the install process, person 1 or other notification targets won't exist yet } } }
public static void PerformAutomated(BitcoinChain chain) { // Perform all waiting hot payouts for all orgs in the installation throw new NotImplementedException("Waiting for rewrite for Bitcoin Cash"); // TODO DateTime utcNow = DateTime.UtcNow; foreach (Organization organization in Organizations.GetAll()) { // If this org doesn't do hotwallet, continue if (organization.FinancialAccounts.AssetsBitcoinHot == null) { continue; } Payouts orgPayouts = Payouts.Construct(organization); Payouts bitcoinPayouts = new Payouts(); Dictionary <string, Int64> satoshiPayoutLookup = new Dictionary <string, long>(); Dictionary <string, Int64> nativeCentsPayoutLookup = new Dictionary <string, long>(); Dictionary <int, Int64> satoshiPersonLookup = new Dictionary <int, long>(); Dictionary <int, Int64> nativeCentsPersonLookup = new Dictionary <int, long>(); Int64 satoshisTotal = 0; string currencyCode = organization.Currency.Code; // For each ready payout that can automate, add an output to a constructed transaction TransactionBuilder txBuilder = null; // TODO TODO TODO TODO new TransactionBuilder(); foreach (Payout payout in orgPayouts) { if (payout.ExpectedTransactionDate > utcNow) { continue; // payout is not due yet } if (payout.RecipientPerson != null && payout.RecipientPerson.BitcoinPayoutAddress.Length > 2 && payout.Account.Length < 4) { // If the payout address is still in quarantine, don't pay out yet string addressSetTime = payout.RecipientPerson.BitcoinPayoutAddressTimeSet; if (addressSetTime.Length > 4 && DateTime.Parse(addressSetTime, CultureInfo.InvariantCulture).AddHours(48) > utcNow) { continue; // still in quarantine } // Test the payout address - is it valid and can we handle it? if (!BitcoinUtility.IsValidBitcoinAddress(payout.RecipientPerson.BitcoinPayoutAddress)) { // Notify person that address is invalid, then clear it NotificationStrings primaryStrings = new NotificationStrings(); NotificationCustomStrings secondaryStrings = new NotificationCustomStrings(); primaryStrings[NotificationString.OrganizationName] = organization.Name; secondaryStrings["BitcoinAddress"] = payout.RecipientPerson.BitcoinPayoutAddress; OutboundComm.CreateNotification(organization, NotificationResource.BitcoinPayoutAddress_Bad, primaryStrings, secondaryStrings, People.FromSingle(payout.RecipientPerson)); payout.RecipientPerson.BitcoinPayoutAddress = string.Empty; continue; // do not add this payout } // Ok, so it seems we're making this payout at this time. bitcoinPayouts.Add(payout); int recipientPersonId = payout.RecipientPerson.Identity; if (!satoshiPersonLookup.ContainsKey(recipientPersonId)) { satoshiPersonLookup[recipientPersonId] = 0; nativeCentsPersonLookup[recipientPersonId] = 0; } nativeCentsPersonLookup[recipientPersonId] += payout.AmountCents; // Find the amount of satoshis for this payout if (organization.Currency.IsBitcoinCore) { satoshiPayoutLookup[payout.ProtoIdentity] = payout.AmountCents; nativeCentsPayoutLookup[payout.ProtoIdentity] = payout.AmountCents; satoshisTotal += payout.AmountCents; satoshiPersonLookup[recipientPersonId] += payout.AmountCents; } else { // Convert currency Money payoutAmount = new Money(payout.AmountCents, organization.Currency); Int64 satoshis = payoutAmount.ToCurrency(Currency.BitcoinCore).Cents; satoshiPayoutLookup[payout.ProtoIdentity] = satoshis; nativeCentsPayoutLookup[payout.ProtoIdentity] = payout.AmountCents; satoshisTotal += satoshis; satoshiPersonLookup[recipientPersonId] += satoshis; } } else if (payout.RecipientPerson != null && payout.RecipientPerson.BitcoinPayoutAddress.Length < 3 && payout.Account.Length < 4) { // There is a payout for this person, but they don't have a bitcoin payout address set. Send notification to this effect once a day. if (utcNow.Minute != 0) { continue; } if (utcNow.Hour != 12) { continue; } NotificationStrings primaryStrings = new NotificationStrings(); primaryStrings[NotificationString.OrganizationName] = organization.Name; OutboundComm.CreateNotification(organization, NotificationResource.BitcoinPayoutAddress_PleaseSet, primaryStrings, People.FromSingle(payout.RecipientPerson)); } else if (payout.Account.StartsWith("bitcoin:")) { } } if (bitcoinPayouts.Count == 0) { // no automated payments pending for this organization, nothing more to do continue; } // We now have our desired payments. The next step is to find enough inputs to reach the required amount (plus fees; we're working a little blind here still). BitcoinTransactionInputs inputs = null; Int64 satoshisMaximumAnticipatedFees = BitcoinUtility.GetRecommendedFeePerThousandBytesSatoshis(chain) * 20; // assume max 20k transaction size try { inputs = BitcoinUtility.GetInputsForAmount(organization, satoshisTotal + satoshisMaximumAnticipatedFees); } catch (NotEnoughFundsException) { // If we're at the whole hour, send a notification to people responsible for refilling the hotwallet if (utcNow.Minute != 0) { continue; // we're not at the whole hour, so continue with next org instead } // Send urgent notification to top up the damn wallet so we can make payments NotificationStrings primaryStrings = new NotificationStrings(); primaryStrings[NotificationString.CurrencyCode] = organization.Currency.Code; primaryStrings[NotificationString.OrganizationName] = organization.Name; NotificationCustomStrings secondaryStrings = new NotificationCustomStrings(); Int64 satoshisAvailable = HotBitcoinAddresses.ForOrganization(organization).BalanceSatoshisTotal; secondaryStrings["AmountMissingMicrocoinsFloat"] = ((satoshisTotal - satoshisAvailable + satoshisMaximumAnticipatedFees) / 100.0).ToString("N2"); if (organization.Currency.IsBitcoinCore) { secondaryStrings["AmountNeededFloat"] = ((satoshisTotal + satoshisMaximumAnticipatedFees) / 100.0).ToString("N2"); secondaryStrings["AmountWalletFloat"] = (satoshisAvailable / 100.0).ToString("N2"); } else { // convert to org native currency secondaryStrings["AmountNeededFloat"] = (new Money(satoshisTotal, Currency.BitcoinCore).ToCurrency(organization.Currency).Cents / 100.0).ToString("N2"); secondaryStrings["AmountWalletFloat"] = (new Money(satoshisAvailable, Currency.BitcoinCore).ToCurrency(organization.Currency).Cents / 100.0).ToString("N2"); } OutboundComm.CreateNotification(organization, NotificationResource.Bitcoin_Shortage_Critical, primaryStrings, secondaryStrings, People.FromSingle(Person.FromIdentity(1))); continue; // with next organization } // If we arrive at this point, the previous function didn't throw, and we have enough money. // Ensure the existence of a cost account for bitcoin miner fees. organization.EnsureMinerFeeAccountExists(); // Add the inputs to the transaction. txBuilder = txBuilder.AddCoins(inputs.Coins); txBuilder = txBuilder.AddKeys(inputs.PrivateKeys); Int64 satoshisInput = inputs.AmountSatoshisTotal; // Add outputs and prepare notifications Int64 satoshisUsed = 0; Dictionary <int, List <string> > notificationSpecLookup = new Dictionary <int, List <string> >(); Dictionary <int, List <Int64> > notificationAmountLookup = new Dictionary <int, List <Int64> >(); Payout masterPayoutPrototype = Payout.Empty; HotBitcoinAddress changeAddress = HotBitcoinAddress.OrganizationWalletZero(organization, BitcoinChain.Core); // TODO: CHAIN! foreach (Payout payout in bitcoinPayouts) { int recipientPersonId = payout.RecipientPerson.Identity; if (!notificationSpecLookup.ContainsKey(recipientPersonId)) { notificationSpecLookup[recipientPersonId] = new List <string>(); notificationAmountLookup[recipientPersonId] = new List <Int64>(); } notificationSpecLookup[recipientPersonId].Add(payout.Specification); notificationAmountLookup[recipientPersonId].Add(payout.AmountCents); if (payout.RecipientPerson.BitcoinPayoutAddress.StartsWith("1")) // regular address { txBuilder = txBuilder.Send(new BitcoinPubKeyAddress(payout.RecipientPerson.BitcoinPayoutAddress), new Satoshis(satoshiPayoutLookup[payout.ProtoIdentity])); } else if (payout.RecipientPerson.BitcoinPayoutAddress.StartsWith("3")) // multisig { txBuilder = txBuilder.Send(new BitcoinScriptAddress(payout.RecipientPerson.BitcoinPayoutAddress, Network.Main), new Satoshis(satoshiPayoutLookup[payout.ProtoIdentity])); } else { throw new InvalidOperationException("Unhandled bitcoin address type in Payouts.PerformAutomated(): " + payout.RecipientPerson.BitcoinPayoutAddress); } satoshisUsed += satoshiPayoutLookup[payout.ProtoIdentity]; payout.MigrateDependenciesTo(masterPayoutPrototype); } // Set change address to wallet slush txBuilder.SetChange(new BitcoinPubKeyAddress(changeAddress.Address)); // Add fee int transactionSizeBytes = txBuilder.EstimateSize(txBuilder.BuildTransaction(false)) + inputs.Count; // +inputs.Count for size variability Int64 feeSatoshis = (transactionSizeBytes / 1000 + 1) * BitcoinUtility.GetRecommendedFeePerThousandBytesSatoshis(chain); txBuilder = txBuilder.SendFees(new Satoshis(feeSatoshis)); satoshisUsed += feeSatoshis; // Sign transaction - ready to execute Transaction txReady = txBuilder.BuildTransaction(true); // Verify that transaction is ready if (!txBuilder.Verify(txReady)) { // Transaction was not signed with the correct keys. This is a serious condition. NotificationStrings primaryStrings = new NotificationStrings(); primaryStrings[NotificationString.OrganizationName] = organization.Name; OutboundComm.CreateNotification(organization, NotificationResource.Bitcoin_PrivateKeyError, primaryStrings); throw new InvalidOperationException("Transaction is not signed enough"); } // Broadcast transaction BitcoinUtility.BroadcastTransaction(txReady, BitcoinChain.Cash); // Note the transaction hash string transactionHash = txReady.GetHash().ToString(); // Delete all old inputs, adjust balance for addresses (re-register unused inputs) inputs.AsUnspents.DeleteAll(); // Log the new unspent created by change (if there is any) if (satoshisInput - satoshisUsed > 0) { SwarmDb.GetDatabaseForWriting() .CreateHotBitcoinAddressUnspentConditional(changeAddress.Identity, transactionHash, +/* the change address seems to always get index 0? is this a safe assumption? */ 0, satoshisInput - satoshisUsed, /* confirmation count*/ 0); } // Register new balance of change address, should have increased by (satoshisInput-satoshisUsed) // TODO // Send notifications foreach (int personId in notificationSpecLookup.Keys) { Person person = Person.FromIdentity(personId); string spec = string.Empty; for (int index = 0; index < notificationSpecLookup[personId].Count; index++) { spec += String.Format(" * {0,-40} {1,14:N2} {2,-4}\r\n", notificationSpecLookup[personId][index], notificationAmountLookup[personId][index] / 100.0, currencyCode); } spec = spec.TrimEnd(); NotificationStrings primaryStrings = new NotificationStrings(); NotificationCustomStrings secondaryStrings = new NotificationCustomStrings(); primaryStrings[NotificationString.OrganizationName] = organization.Name; primaryStrings[NotificationString.CurrencyCode] = organization.Currency.DisplayCode; primaryStrings[NotificationString.EmbeddedPreformattedText] = spec; secondaryStrings["AmountFloat"] = (nativeCentsPersonLookup[personId] / 100.0).ToString("N2"); secondaryStrings["BitcoinAmountFloat"] = (satoshiPersonLookup[personId] / 100.0).ToString("N2"); secondaryStrings["BitcoinAddress"] = person.BitcoinPayoutAddress; // warn: potential rare race condition here OutboundComm.CreateNotification(organization, NotificationResource.Bitcoin_PaidOut, primaryStrings, secondaryStrings, People.FromSingle(person)); } // Create the master payout from its prototype Payout masterPayout = Payout.CreateBitcoinPayoutFromPrototype(organization, masterPayoutPrototype, txReady.GetHash().ToString()); // Finally, create ledger entries and notify NotificationStrings masterPrimaryStrings = new NotificationStrings(); NotificationCustomStrings masterSecondaryStrings = new NotificationCustomStrings(); masterPrimaryStrings[NotificationString.OrganizationName] = organization.Name; masterPrimaryStrings[NotificationString.CurrencyCode] = organization.Currency.DisplayCode; masterSecondaryStrings["AmountFloat"] = (new Swarmops.Logic.Financial.Money(satoshisUsed, Currency.BitcoinCore).ToCurrency( organization.Currency).Cents / 100.0).ToString("N2", CultureInfo.InvariantCulture); masterSecondaryStrings["BitcoinAmountFloat"] = (satoshisUsed / 100.0).ToString("N2", CultureInfo.InvariantCulture); masterSecondaryStrings["PaymentCount"] = bitcoinPayouts.Count.ToString("N0", CultureInfo.InvariantCulture); OutboundComm.CreateNotification(organization, NotificationResource.Bitcoin_Hotwallet_Outflow, masterPrimaryStrings, masterSecondaryStrings); // TODO: special case for native-bitcoin organizations vs. fiat-currency organizations FinancialTransaction ledgerTransaction = FinancialTransaction.Create(organization, utcNow, "Bitcoin automated payout"); if (organization.Currency.IsBitcoinCore) { ledgerTransaction.AddRow(organization.FinancialAccounts.AssetsBitcoinHot, -(masterPayoutPrototype.AmountCents + feeSatoshis), null); ledgerTransaction.AddRow(organization.FinancialAccounts.CostsBitcoinFees, feeSatoshis, null); } else { // If the ledger isn't using bitcoin natively, we need to translate the miner fee paid to ledger cents before entering it into ledger Int64 feeCentsLedger = new Money(feeSatoshis, Currency.BitcoinCore).ToCurrency(organization.Currency).Cents; ledgerTransaction.AddRow(organization.FinancialAccounts.AssetsBitcoinHot, -(masterPayoutPrototype.AmountCents + feeCentsLedger), null).AmountForeignCents = new Money(satoshisUsed, Currency.BitcoinCore); ledgerTransaction.AddRow(organization.FinancialAccounts.CostsBitcoinFees, feeCentsLedger, null); } ledgerTransaction.BlockchainHash = transactionHash; masterPayout.BindToTransactionAndClose(ledgerTransaction, null); } }
internal static void Run() { OutboundComms comms = OutboundComms.GetOpen(); foreach (OutboundComm comm in comms) { BotLog.Write(1, "CommsTx", "OutboundComm #" + comm.Identity.ToString("N0")); if (!comm.Resolved) { BotLog.Write(2, "CommsTx", "--resolving"); ICommsResolver resolver = FindResolver(comm); resolver.Resolve(comm); comm.Resolved = true; int recipientCount = comm.Recipients.Count; BotLog.Write(2, "CommsTx", "--resolved to " + recipientCount.ToString("N0") + " recipients"); if (recipientCount > 1 && comm.SenderPersonId != 0) { // "Your message has been queued for delivery and the recipients have been resolved. // Your mail will be sent to, or be attempted to sent to, [RecipientCount] people in [Geography] in [OrganizationName]." NotificationStrings notifyStrings = new NotificationStrings(); NotificationCustomStrings customStrings = new NotificationCustomStrings(); notifyStrings[NotificationString.OrganizationName] = Organization.FromIdentity(comm.OrganizationId).Name; customStrings["RecipientCount"] = comm.Recipients.Count.ToString("N0"); if (resolver is IHasGeography) { customStrings["GeographyName"] = ((IHasGeography)resolver).Geography.Localized; } OutboundComm.CreateNotification(Organization.FromIdentity(comm.OrganizationId), NotificationResource.OutboundComm_Resolved, notifyStrings, customStrings, People.FromSingle(Person.FromIdentity(comm.SenderPersonId))); } comm.StartTransmission(); continue; // continue is not strictly necessary; could continue processing the same OutboundComm after resolution } if (comm.TransmitterClass != "Swarmops.Utility.Communications.CommsTransmitterMail") { throw new NotImplementedException(); } ICommsTransmitter transmitter = new CommsTransmitterMail(); const int batchSize = 1000; OutboundCommRecipients recipients = comm.GetRecipientBatch(batchSize); PayloadEnvelope envelope = PayloadEnvelope.FromXml(comm.PayloadXml); BotLog.Write(2, "CommsTx", "--transmitting to " + recipients.Count.ToString("N0") + " recipients"); foreach (OutboundCommRecipient recipient in recipients) { try { transmitter.Transmit(envelope, recipient.Person); recipient.CloseSuccess(); } catch (OutboundCommTransmitException e) { recipient.CloseFailed(e.Description); } } if (recipients.Count < batchSize) // Was this the last batch? { comm.Open = false; BotLog.Write(2, "CommsTx", "--closing"); OutboundComm reloadedComm = OutboundComm.FromIdentity(comm.Identity); // active object doesn't update as we get results, so need to load // from database to get final counts of successes and fails if (comm.RecipientCount > 1 && comm.SenderPersonId != 0) { BotLog.Write(2, "CommsTx", "--notifying"); ICommsResolver resolver = FindResolver(comm); // "Your message to [GeographyName] has been sent to all scheduled recipients. Of the [RecipientCount] planned recipients, // [RecipientsSuccess] succeeded from Swarmops' horizon. (These can fail later for a number of reasons, from broken // computers to hospitalized recipients.) Time spent transmitting: [TransmissionTime]." NotificationStrings notifyStrings = new NotificationStrings(); NotificationCustomStrings customStrings = new NotificationCustomStrings(); notifyStrings[NotificationString.OrganizationName] = Organization.FromIdentity(comm.OrganizationId).Name; customStrings["RecipientCount"] = reloadedComm.RecipientCount.ToString("N0"); customStrings["RecipientsSuccess"] = reloadedComm.RecipientsSuccess.ToString("N0"); TimeSpan resolveTime = comm.StartTransmitDateTime - comm.CreatedDateTime; TimeSpan transmitTime = comm.ClosedDateTime - comm.StartTransmitDateTime; TimeSpan totalTime = resolveTime + transmitTime; customStrings["TransmissionTime"] = FormatTimespan(transmitTime); customStrings["ResolvingTime"] = FormatTimespan(resolveTime); customStrings["TotalTime"] = FormatTimespan(totalTime); if (resolver is IHasGeography) { customStrings["GeographyName"] = ((IHasGeography)resolver).Geography.Localized; } OutboundComm.CreateNotification(Organization.FromIdentity(comm.OrganizationId), NotificationResource.OutboundComm_Sent, notifyStrings, customStrings, People.FromSingle(Person.FromIdentity(comm.SenderPersonId))); } } } }
public static void TestMultisigPayout() { throw new InvalidOperationException("This function is only for testing purposes. It pays real money. Don't use except for dev/test."); // disable "code unreachable" warning for this code // ReSharper disable once CSharpWarnings::CS0162 #pragma warning disable 162,219 Organization organization = Organization.Sandbox; // a few testing cents here in test environment string bitcoinTestAddress = "3KS6AuQbZARSvqnaHoHfL1Xhm3bTLFAzoK"; // Make a small test payment to a multisig address TransactionBuilder txBuilder = null; // TODO TODO TODO TODO new TransactionBuilder(); Int64 satoshis = new Money(100, Currency.FromCode("SEK")).ToCurrency(Currency.BitcoinCore).Cents; BitcoinTransactionInputs inputs = null; Int64 satoshisMaximumAnticipatedFees = BitcoinUtility.GetRecommendedFeePerThousandBytesSatoshis(BitcoinChain.Cash) * 20; // assume max 20k transaction size try { inputs = BitcoinUtility.GetInputsForAmount(organization, satoshis + satoshisMaximumAnticipatedFees); } catch (NotEnoughFundsException) { Debugger.Break(); } // If we arrive at this point, the previous function didn't throw, and we have enough money. Add the inputs to the transaction. txBuilder = txBuilder.AddCoins(inputs.Coins); txBuilder = txBuilder.AddKeys(inputs.PrivateKeys); Int64 satoshisInput = inputs.AmountSatoshisTotal; // Add outputs and prepare notifications Int64 satoshisUsed = 0; //Dictionary<int, List<string>> notificationSpecLookup = new Dictionary<int, List<string>>(); //Dictionary<int, List<Int64>> notificationAmountLookup = new Dictionary<int, List<Int64>>(); Payout masterPayoutPrototype = Payout.Empty; HotBitcoinAddress changeAddress = HotBitcoinAddress.OrganizationWalletZero(organization, BitcoinChain.Core); // Add the test payment if (bitcoinTestAddress.StartsWith("1")) // regular address { txBuilder = txBuilder.Send(new BitcoinPubKeyAddress(bitcoinTestAddress), new Satoshis(satoshis)); } else if (bitcoinTestAddress.StartsWith("3")) // multisig { txBuilder = txBuilder.Send(new BitcoinScriptAddress(bitcoinTestAddress, Network.Main), new Satoshis(satoshis)); } else { throw new InvalidOperationException("Unhandled address case"); } satoshisUsed += satoshis; // Set change address to wallet slush txBuilder.SetChange(new BitcoinPubKeyAddress(changeAddress.Address)); // Add fee int transactionSizeBytes = txBuilder.EstimateSize(txBuilder.BuildTransaction(false)) + inputs.Count; // +inputs.Count for size variability Int64 feeSatoshis = (transactionSizeBytes / 1000 + 1) * BitcoinUtility.GetRecommendedFeePerThousandBytesSatoshis(BitcoinChain.Cash); txBuilder = txBuilder.SendFees(new Satoshis(feeSatoshis)); satoshisUsed += feeSatoshis; // Sign transaction - ready to execute Transaction txReady = txBuilder.BuildTransaction(true); // Verify that transaction is ready if (!txBuilder.Verify(txReady)) { // Transaction was not signed with the correct keys. This is a serious condition. NotificationStrings primaryStrings = new NotificationStrings(); primaryStrings[NotificationString.OrganizationName] = organization.Name; OutboundComm.CreateNotification(organization, NotificationResource.Bitcoin_PrivateKeyError, primaryStrings); throw new InvalidOperationException("Transaction is not signed enough"); } // Broadcast transaction BitcoinUtility.BroadcastTransaction(txReady, BitcoinChain.Cash); // Note the transaction hash string transactionHash = txReady.GetHash().ToString(); // Delete all old inputs, adjust balance for addresses (re-register unused inputs) inputs.AsUnspents.DeleteAll(); // Log the new unspent created by change (if there is any) if (satoshisInput - satoshisUsed > 0) { SwarmDb.GetDatabaseForWriting() .CreateHotBitcoinAddressUnspentConditional(changeAddress.Identity, transactionHash, +/* the change address seems to always get index 0? is this a safe assumption? */ 0, satoshisInput - satoshisUsed, /* confirmation count*/ 0); } // Restore "code unreachable", "unsued var" warnings #pragma warning restore 162,219 // This puts the ledger out of sync, so only do this on Sandbox for various small-change (cents) testing }
public static void CreateAnnualStatements(int year) { // This function is supposed to be running once a year, and summarize the past year of salaries. // Get all organizations. Organizations organizations = Organizations.GetAll(); foreach (Organization organization in organizations) { // Get all salaries for this organization (filter year in logic - this function is allowed to be expensive, rather than // adding a custom database query for this particular operation) Salaries allSalaries = ForOrganization(organization, true); Salaries yearSalaries = new Salaries(); yearSalaries.AddRange(allSalaries.Where(salary => salary.PayoutDate.Year == year)); Dictionary <int, Dictionary <int, Salary> > personSalaryLookup = new Dictionary <int, Dictionary <int, Salary> >(); // Go through the salaries and store them in the dictionary, so we can process the statement by person later foreach (Salary salary in yearSalaries) { int personId = salary.PayrollItem.PersonId; if (!personSalaryLookup.ContainsKey(personId)) { personSalaryLookup[personId] = new Dictionary <int, Salary>(); } personSalaryLookup[personId][salary.PayoutDate.Month] = salary; } // Once here, salaries are arranged by person and month. Iterate over people, create statements. foreach (int personId in personSalaryLookup.Keys) { Person person = Person.FromIdentity(personId); Int64 grossTotal = 0; Int64 subTaxTotal = 0; Int64 addTaxTotal = 0; Int64 netTotal = 0; string preFormattedStatement = string.Empty; for (int monthNumber = 1; monthNumber <= 12; monthNumber++) { string monthName = new DateTime(year, monthNumber, 1).ToString("MMM", new CultureInfo(person.PreferredCulture)); monthName = monthName.Substring(0, 3); // believe it or not some .Net localizations don't respect the three letter limit string lineItem = " " + monthName; if (personSalaryLookup[personId].ContainsKey(monthNumber) && personSalaryLookup[personId][monthNumber].GrossSalaryCents > 0) { Salary monthSalary = personSalaryLookup[personId][monthNumber]; // TODO: replace "bitcoin" with actual payout method - this is just for show at the moment lineItem += String.Format(" {0,10:N2} {1,11:N2} {2,10:N2} bitcoin", monthSalary.GrossSalaryCents / 100.0, -monthSalary.SubtractiveTaxCents / 100.0, monthSalary.NetSalaryCents / 100.0); grossTotal += monthSalary.GrossSalaryCents; addTaxTotal += monthSalary.AdditiveTaxCents; subTaxTotal += monthSalary.SubtractiveTaxCents; netTotal += monthSalary.NetSalaryCents; } preFormattedStatement += lineItem + "\r\n"; } NotificationStrings notificationStrings = new NotificationStrings(); NotificationCustomStrings customStrings = new NotificationCustomStrings(); notificationStrings[NotificationString.CurrencyCode] = organization.Currency.DisplayCode; notificationStrings[NotificationString.EmbeddedPreformattedText] = preFormattedStatement; customStrings["LastYear"] = year.ToString(CultureInfo.InvariantCulture); customStrings["GrossSalaryTotal"] = String.Format("{0,10:N2}", grossTotal / 100.0); customStrings["TaxDeductedTotal"] = String.Format("{0,11:N2}", -subTaxTotal / 100.0); customStrings["NetSalaryTotal"] = String.Format("{0,10:N2}", netTotal / 100.0); customStrings["TaxAdditiveTotalUnpadded"] = String.Format("{0:N2}", addTaxTotal / 100.0); // Send notification if gross is nonzero if (grossTotal > 0) { OutboundComm.CreateNotification(organization, NotificationResource.Salary_LastYearSummary, notificationStrings, customStrings, People.FromSingle(person)); } } } }
static public AjaxInputCallResult StoreCallback(string newValue, string cookie) { AjaxInputCallResult result = new AjaxInputCallResult(); AuthenticationData authData = GetAuthenticationDataAndCulture(); if (!authData.Authority.HasAccess(new Access(AccessAspect.Administration, AccessType.Write))) { result.Success = false; // this is default on init, but let's be explicit about it result.FailReason = AjaxInputCallResult.ErrorAccessDenied; return(result); // fail silently } switch (cookie) { case "Smtp": Match match = Regex.Match(newValue, "((?<user>[a-z0-9_]+)(:(?<pass>[^@]+))?@)?(?<host>[a-z0-9_\\-\\.]+)(:(?<port>[0-9]+))?", RegexOptions.IgnoreCase); if (match.Success) { string user = match.Groups["user"].Value; string pass = match.Groups["pass"].Value; string host = match.Groups["host"].Value; string portString = match.Groups["port"].Value; int port = 25; if (!string.IsNullOrEmpty(portString)) { try { port = Int32.Parse(portString); } catch (FormatException) { result.DisplayMessage = Resources.Pages.Admin.SystemSettings_Error_SmtpHostPort; result.FailReason = AjaxInputCallResult.ErrorInvalidFormat; result.Success = false; return(result); // return early } } SystemSettings.SmtpUser = user ?? string.Empty; SystemSettings.SmtpPassword = pass ?? string.Empty; SystemSettings.SmtpHost = host; SystemSettings.SmtpPort = port; OutboundComm.CreateNotification(Organization.Sandbox, Logic.Communications.Payload.NotificationResource.System_MailServerTest); result.Success = true; result.NewValue = FormatSmtpAccessString(user, pass, host, port); result.DisplayMessage = Resources.Pages.Admin.SystemSettings_TestMailSent; } else { result.Success = false; result.FailReason = AjaxInputCallResult.ErrorInvalidFormat; result.DisplayMessage = Resources.Pages.Admin.SystemSettings_Error_SmtpSyntax; } break; case "ExtUrl": if (!newValue.EndsWith("/")) { newValue = newValue + "/"; } if (!newValue.StartsWith("http://") && !newValue.StartsWith("https://")) { newValue = "https://" + newValue; } SystemSettings.ExternalUrl = newValue; result.NewValue = newValue; result.Success = true; break; case "InstallationName": result.NewValue = newValue.Trim(); result.Success = true; SystemSettings.InstallationName = result.NewValue; break; case "AdminSender": result.NewValue = newValue.Trim(); result.Success = true; SystemSettings.InstallationName = result.NewValue; break; case "AdminAddress": result.NewValue = newValue.Trim(); result.Success = true; SystemSettings.AdminNotificationAddress = result.NewValue; break; case "BackendHost": result.NewValue = newValue.Trim(); result.Success = true; SystemSettings.BackendHostnameOverride = result.NewValue; break; case "WebsocketFrontend": try { int newPort = Int32.Parse(newValue); if (newPort < 1 || newPort > 32767) { throw new ArgumentException(); } result.NewValue = newValue.Trim(); result.Success = true; SystemSettings.WebsocketPortFrontend = newPort; } catch (Exception) { result.Success = false; result.FailReason = AjaxInputCallResult.ErrorInvalidFormat; result.NewValue = SystemSettings.WebsocketPortFrontend.ToString(CultureInfo.InvariantCulture); } break; case "WebsocketBackend": try { int newPort = Int32.Parse(newValue); if (newPort < 1 || newPort > 32767) { throw new ArgumentException(); } result.NewValue = newValue.Trim(); result.Success = true; SystemSettings.WebsocketPortBackend = newPort; } catch (Exception) { result.Success = false; result.FailReason = AjaxInputCallResult.ErrorInvalidFormat; result.NewValue = SystemSettings.WebsocketPortBackend.ToString(CultureInfo.InvariantCulture); } break; default: throw new NotImplementedException("Unknown cookie in StoreCallback"); } return(result); }
private static void Main(string[] args) { testMode = false; UnixSignal[] killSignals = { new UnixSignal(Signum.SIGINT), new UnixSignal(Signum.SIGTERM) }; BotLog.Write(0, "MainCycle", string.Empty); BotLog.Write(0, "MainCycle", "-----------------------------------------------"); BotLog.Write(0, "MainCycle", string.Empty); if (args.Length > 0) { if (args[0].ToLower() == "test") { /* * BotLog.Write(0, "MainCycle", "Running self-tests"); * HeartBeater.Instance.Beat(heartbeatFile); // Otherwise Heartbeater.Beat() will fail in various places * * testMode = true; * Console.WriteLine("Testing All Maintenance Processes (except membership-changing ones)."); * PWLog.Write(PWLogItem.None, 0, PWLogAction.SystemTest, string.Empty, string.Empty); * * Console.WriteLine("\r\n10-second intervals:"); * OnEveryTenSeconds(); * Console.WriteLine("\r\nEvery minute:"); * OnEveryMinute(); * Console.WriteLine("\r\nEvery five minutes:"); * OnEveryFiveMinutes(); * Console.WriteLine("\r\nEvery hour:"); * OnEveryHour(); * Console.WriteLine("\r\nNoon:"); * OnNoon(); * Console.WriteLine("\r\nMidnight:"); * OnMidnight(); */ Console.WriteLine("Testing database access..."); Console.WriteLine(SwarmDb.GetDatabaseForReading().GetPerson(1).Name); Console.WriteLine(SwarmDb.GetDatabaseForReading().GetPerson(1).PasswordHash); Console.WriteLine("Creating OutboundComm..."); OutboundComm.CreateNotification(null, NotificationResource.System_Startup); Console.WriteLine("Transmitting..."); OutboundComms comms = OutboundComms.GetOpen(); Console.WriteLine("{0} open items in outbound comms.", comms.Count); foreach (OutboundComm comm in comms) { if (comm.TransmitterClass != "Swarmops.Utility.Communications.CommsTransmitterMail") { throw new NotImplementedException(); } ICommsTransmitter transmitter = new CommsTransmitterMail(); OutboundCommRecipients recipients = comm.Recipients; PayloadEnvelope envelope = PayloadEnvelope.FromXml(comm.PayloadXml); foreach (OutboundCommRecipient recipient in recipients) { transmitter.Transmit(envelope, recipient.Person); } } Console.Write("\r\nAll tests run. Waiting for mail queue to flush... "); while (!MailTransmitter.CanExit) { Thread.Sleep(50); } Console.WriteLine("done."); BotLog.Write(0, "MainCycle", "Exiting self-tests"); return; } if (args[0].ToLower() == "console") { Console.WriteLine("\r\nRunning Swarmops-Backend in CONSOLE mode.\r\n"); // ------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------- // ----------------------- INSERT ANY ONE-OFF ACTIONS HERE ------------------------- Console.Write("\r\nWaiting for mail queue to flush... "); while (!MailTransmitter.CanExit) { Thread.Sleep(50); } Console.WriteLine("done."); return; } if (args[0].ToLower() == "rsm") { Console.WriteLine("Testing character encoding: räksmörgås RÄKSMÖRGÅS"); return; } } /* * MailMessage message = new MailMessage(); * message.From = new MailAddress(Strings.MailSenderAddress, Strings.MailSenderName); * message.To.Add (new MailAddress ("*****@*****.**", "Rick Falkvinge (Piratpartiet)")); * message.Subject = "Räksmörgåsarnas ékÖNÖMÏåvdëlnïng"; * message.Body = "Hejsan hoppsan Räksmörgåsar."; * message.BodyEncoding = Encoding.Default; * message.SubjectEncoding = Encoding.Default; * * SmtpClient smtpClient = new SmtpClient ("localhost"); * smtpClient.Credentials = null; // mono bug * smtpClient.Send (message);*/ Console.WriteLine(" * Swarmops Backend starting"); BotLog.Write(0, "MainCycle", "Backend STARTING"); OutboundComm.CreateNotification(null, NotificationResource.System_Startup); // Checking for schemata upgrade DatabaseMaintenance.UpgradeSchemata(); // Check for existence of installation ID. If not, create one. Warning: has privacy implications when communicated. if (Persistence.Key["SwarmopsInstallationId"] == string.Empty) { Persistence.Key["SwarmopsInstallationId"] = Guid.NewGuid().ToString(); } DateTime cycleStartTime = DateTime.Now; int lastSecond = cycleStartTime.Second; int lastMinute = cycleStartTime.Minute; int lastHour = cycleStartTime.Hour; bool exitFlag = false; while (!exitFlag) // exit is handled by signals handling at end of loop { BotLog.Write(0, "MainCycle", "Cycle Start"); cycleStartTime = DateTime.Now; try { OnEveryTenSeconds(); if (cycleStartTime.Second < lastSecond) { OnEveryMinute(); if (cycleStartTime.Minute % 5 == 0) { OnEveryFiveMinutes(); } } if (cycleStartTime.Minute < lastMinute) { OnEveryHour(); if (DateTime.Now.Hour == 10 && DateTime.Today.DayOfWeek == DayOfWeek.Tuesday) { OnTuesdayMorning(); } } if (cycleStartTime.Hour >= 12 && lastHour < 12) { OnNoon(); } if (cycleStartTime.Hour < lastHour) { OnMidnight(); } } catch (Exception e) { // Note each "OnEvery..." catches its own errors and sends Exception mails, // so that failure in one should not stop the others from running. This particular // code should never run. ExceptionMail.Send(new Exception("Failed in swarmops-backend main loop", e), true); } lastSecond = cycleStartTime.Second; lastMinute = cycleStartTime.Minute; lastHour = cycleStartTime.Hour; // Wait for a maximum of ten seconds while (DateTime.Now < cycleStartTime.AddSeconds(10) && !exitFlag) { // block until a SIGINT or SIGTERM signal is generated, or one second has passed. int signalIndex = UnixSignal.WaitAny(killSignals, 1000); if (signalIndex < 1000) { exitFlag = true; Console.WriteLine("Caught signal " + killSignals[signalIndex].Signum + ", exiting"); BotLog.Write(0, "MainCycle", "EXIT SIGNAL (" + killSignals[signalIndex].Signum + "), terminating backend"); } } } Console.WriteLine(" * Swarmops Backend stopping"); BotLog.Write(0, "MainCycle", "BACKEND EXITING, sending backend-termination notices"); /* * if (HeartBeater.Instance.WasKilled) * { * // removed unconditional delete, cron job that restarts bot uses it to know that it is intentionally down. * ExceptionMail.Send(new Exception("HeartBeater triggered restart of Swarmops Backend. Will commence after 800 seconds."), false); * }*/ BotLog.Write(0, "MainCycle", "...done"); /* * while (!MailTransmitter.CanExit) * { * System.Threading.Thread.Sleep(50); * }*/ Thread.Sleep(2000); }
private static void Main(string[] args) { // Are we running yet? if (!SystemSettings.DatabaseInitialized) { // will restart the service every 15s until db initialized on OOBE // also, the read of DatabaseInitialized can and will fail if // we're not initalized enough to even have a database throw new InvalidOperationException(); } // Checking for schemata upgrade first of all, after seeing that db exists int startupDbVersion = Database.SwarmDb.DbVersion; DatabaseMaintenance.UpgradeSchemata(); testMode = false; SystemSettings.BackendHostname = Dns.GetHostName(); // Other one-time initializations FinancialTransactions.FixAllUnsequenced(); SupportFunctions.OperatingTopology = OperatingTopology.Backend; // Begin main loop UnixSignal[] killSignals = null; if (!Debugger.IsAttached) { killSignals = new UnixSignal[] { new UnixSignal(Signum.SIGINT), new UnixSignal(Signum.SIGTERM) }; } BotLog.Write(0, "MainCycle", string.Empty); BotLog.Write(0, "MainCycle", "-----------------------------------------------"); BotLog.Write(0, "MainCycle", string.Empty); if (args.Length > 0) { if (args[0].ToLower() == "test") { /* * BotLog.Write(0, "MainCycle", "Running self-tests"); * HeartBeater.Instance.Beat(heartbeatFile); // Otherwise Heartbeater.Beat() will fail in various places * * testMode = true; * Console.WriteLine("Testing All Maintenance Processes (except membership-changing ones)."); * PWLog.Write(PWLogItem.None, 0, PWLogAction.SystemTest, string.Empty, string.Empty); * * Console.WriteLine("\r\n10-second intervals:"); * OnEveryTenSeconds(); * Console.WriteLine("\r\nEvery minute:"); * OnEveryMinute(); * Console.WriteLine("\r\nEvery five minutes:"); * OnEveryFiveMinutes(); * Console.WriteLine("\r\nEvery hour:"); * OnEveryHour(); * Console.WriteLine("\r\nNoon:"); * OnNoon(); * Console.WriteLine("\r\nMidnight:"); * OnMidnight(); */ Console.WriteLine("Testing database access..."); Console.WriteLine(SwarmDb.GetDatabaseForReading().GetPerson(1).Name); Console.WriteLine(SwarmDb.GetDatabaseForReading().GetPerson(1).PasswordHash); Console.WriteLine("Creating OutboundComm..."); OutboundComm.CreateNotification(null, NotificationResource.System_Startup_Backend); Console.WriteLine("Transmitting..."); OutboundComms comms = OutboundComms.GetOpen(); Console.WriteLine("{0} open items in outbound comms.", comms.Count); foreach (OutboundComm comm in comms) { if (comm.TransmitterClass != "Swarmops.Utility.Communications.CommsTransmitterMail") { throw new NotImplementedException(); } ICommsTransmitter transmitter = new CommsTransmitterMail(); OutboundCommRecipients recipients = comm.Recipients; PayloadEnvelope envelope = PayloadEnvelope.FromXml(comm.PayloadXml); foreach (OutboundCommRecipient recipient in recipients) { transmitter.Transmit(envelope, recipient.Person); } } Console.Write("\r\nAll tests run. Waiting for mail queue to flush... "); while (!MailTransmitter.CanExit) { Thread.Sleep(50); } Console.WriteLine("done."); BotLog.Write(0, "MainCycle", "Exiting self-tests"); return; } if (args[0].ToLower() == "console") { Console.WriteLine("\r\nRunning Swarmops-Backend in CONSOLE mode.\r\n"); // ------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------- // ----------------------- INSERT ANY ONE-OFF ACTIONS HERE ------------------------- Console.Write("\r\nWaiting for mail queue to flush... "); while (!MailTransmitter.CanExit) { Thread.Sleep(50); } Console.WriteLine("done."); return; } if (args[0].ToLowerInvariant() == "pdfregen") { if (args.Length > 1) { int docId = Int32.Parse(args[1]); PdfProcessor.Rerasterize(Document.FromIdentity(docId)); } else { Console.WriteLine("Regenerating all bitmaps from PDF uploads."); PdfProcessor.RerasterizeAll(); Console.WriteLine("Done."); } return; } if (args[0].ToLower() == "rsm") { Console.WriteLine("Testing character encoding: räksmörgås RÄKSMÖRGÅS"); return; } } /* * MailMessage message = new MailMessage(); * message.From = new MailAddress(Strings.MailSenderAddress, Strings.MailSenderName); * message.To.Add (new MailAddress ("*****@*****.**", "Rick Falkvinge (Piratpartiet)")); * message.Subject = "Räksmörgåsarnas ékÖNÖMÏåvdëlnïng"; * message.Body = "Hejsan hoppsan Räksmörgåsar."; * message.BodyEncoding = Encoding.Default; * message.SubjectEncoding = Encoding.Default; * * SmtpClient smtpClient = new SmtpClient ("localhost"); * smtpClient.Credentials = null; // mono bug * smtpClient.Send (message);*/ Console.WriteLine(" * Swarmops Backend starting"); BotLog.Write(0, "MainCycle", "Backend STARTING"); // Disable certificate checking due to Mono not installing with a certificate repository - this is UTTERLY broken SupportFunctions.DisableSslCertificateChecks(); // MONO BUG/MISFEATURE: Mono has no root certificates, so can't verify cert // Tell sysop we're starting OutboundComm.CreateNotification(null, NotificationResource.System_Startup_Backend); // Check for existence of installation ID. If not, create one. Warning: has privacy implications when communicated. if (Persistence.Key["SwarmopsInstallationId"] == string.Empty) { Persistence.Key["SwarmopsInstallationId"] = Guid.NewGuid().ToString(); } // Check for existence of bitcoin hotwallet root BitcoinUtility.VerifyBitcoinHotWallet(); // Initialize backend socket server int backendSocketPort = SystemSettings.WebsocketPortBackend; _socketServer = new WebSocketServer(backendSocketPort); _socketServer.AddWebSocketService <BackendServices>("/Backend"); _socketServer.Start(); // Initialize socket client to Blockchain.Info (pending our own services) using ( _blockChainInfoSocket = new WebSocket("wss://ws.blockchain.info/inv?api_code=" + SystemSettings.BlockchainSwarmopsApiKey)) { // Begin maintenance loop DateTime cycleStartTime = DateTime.UtcNow; DateTime cycleEndTime; int lastSecond = cycleStartTime.Second; int lastMinute = cycleStartTime.Minute; int lastHour = cycleStartTime.Hour; bool exitFlag = false; _blockChainInfoSocket.OnOpen += new EventHandler(OnBlockchainOpen); _blockChainInfoSocket.OnError += new EventHandler <ErrorEventArgs>(OnBlockchainError); _blockChainInfoSocket.OnClose += new EventHandler <CloseEventArgs>(OnBlockchainClose); _blockChainInfoSocket.OnMessage += new EventHandler <MessageEventArgs>(OnBlockchainMessage); _blockChainInfoSocket.Connect(); while (!exitFlag) // exit is handled by signals handling at end of loop { BotLog.Write(0, "MainCycle", "Cycle Start"); cycleStartTime = DateTime.UtcNow; cycleEndTime = cycleStartTime.AddSeconds(10); try { OnEveryTenSeconds(); if (cycleStartTime.Second < lastSecond) { OnEveryMinute(); if (cycleStartTime.Minute % 5 == 0) { OnEveryFiveMinutes(); } } if (cycleStartTime.Minute < lastMinute) { OnEveryHour(); if (DateTime.Now.Hour == 10 && DateTime.Today.DayOfWeek == DayOfWeek.Tuesday) { OnTuesdayMorning(); } if (DateTime.Now.Hour == 7 && DateTime.Today.DayOfWeek == DayOfWeek.Monday) { OnMondayMorning(); } } if (cycleStartTime.Hour >= 12 && lastHour < 12) { OnNoon(); } if (cycleStartTime.Hour < lastHour) { OnMidnight(); } } catch (Exception e) { // Note each "OnEvery..." catches its own errors and sends Exception mails, // so that failure in one should not stop the others from running. This particular // code should never run. ExceptionMail.Send(new Exception("Failed in swarmops-backend main loop", e), true); } lastSecond = cycleStartTime.Second; lastMinute = cycleStartTime.Minute; lastHour = cycleStartTime.Hour; // Wait for a maximum of ten seconds (the difference between cycleStartTime and cycleEndTime) int iterationCount = 0; DateTime utcNow = DateTime.UtcNow; while (utcNow < cycleEndTime && !exitFlag) { int signalIndex = 250; // Handle important service orders (those that can't be lost in a random loss // of connection of a socket): BackendServiceOrders backendOrders = BackendServiceOrders.GetNextBatch(5); backendOrders.Execute(); // takes at most 250ms per BSO reqs // Block until a SIGINT or SIGTERM signal is generated, or 1/4 second has passed. // However, we can't do that in a development environment - it won't have the // Mono.Posix assembly, and won't understand UnixSignals. So people running this in // a dev environment will need to stop it manually. if (!Debugger.IsAttached) { signalIndex = UnixSignal.WaitAny(killSignals, 250); } else { TimeSpan timeLeft = (cycleEndTime - utcNow); BotLog.Write(0, "MainCycle Debug", string.Format(CultureInfo.InvariantCulture, "Waiting for {0:F2} more seconds for cycle end", timeLeft.TotalMilliseconds / 1000.0)); Thread.Sleep(250); } if (signalIndex < 250) { exitFlag = true; Console.WriteLine("Caught signal " + killSignals[signalIndex].Signum + ", exiting"); BotLog.Write(0, "MainCycle", "EXIT SIGNAL (" + killSignals[signalIndex].Signum + "), terminating backend"); } utcNow = DateTime.UtcNow; // Every second, send an internal heartbeat if (iterationCount++ % 4 == 0) { InternalHeartbeat(); } } } } Console.WriteLine(" * Swarmops Backend stopping"); BotLog.Write(0, "MainCycle", "BACKEND EXITING, sending backend-termination notices"); /* * if (HeartBeater.Instance.WasKilled) * { * // removed unconditional delete, cron job that restarts bot uses it to know that it is intentionally down. * ExceptionMail.Send(new Exception("HeartBeater triggered restart of Swarmops Backend. Will commence after 800 seconds."), false); * }*/ BotLog.Write(0, "MainCycle", "...done"); /* * while (!MailTransmitter.CanExit) * { * System.Threading.Thread.Sleep(50); * }*/ _socketServer.Stop(); Thread.Sleep(2000); }
static public AjaxTextBox.CallbackResult StoreCallback(string newValue, string cookie) { AjaxTextBox.CallbackResult result = new AjaxTextBox.CallbackResult(); switch (cookie) { case "Smtp": Match match = Regex.Match(newValue, "((?<user>[a-z0-9_]+)(:(?<pass>[^@]+))?@)?(?<host>[a-z0-9_\\-\\.]+)(:(?<port>[0-9]+))?", RegexOptions.IgnoreCase); if (match.Success) { string user = match.Groups["user"].Value; string pass = match.Groups["pass"].Value; string host = match.Groups["host"].Value; string portString = match.Groups["port"].Value; int port = 25; if (!string.IsNullOrEmpty(portString)) { try { port = Int32.Parse(portString); } catch (FormatException) { result.DisplayMessage = Resources.Pages.Admin.SystemSettings_Error_SmtpHostPort; result.ResultCode = AjaxTextBox.CodeInvalid; return(result); // return early } } SystemSettings.SmtpUser = user ?? string.Empty; SystemSettings.SmtpPassword = pass ?? string.Empty; SystemSettings.SmtpHost = host; SystemSettings.SmtpPort = port; OutboundComm.CreateNotification(Organization.Sandbox, Logic.Communications.Transmission.NotificationResource.System_MailServerTest); result.ResultCode = AjaxTextBox.CodeChanged; result.NewData = FormatSmtpAccessString(user, pass, host, port); result.DisplayMessage = Resources.Pages.Admin.SystemSettings_TestMailSent; } else { result.ResultCode = AjaxTextBox.CodeInvalid; result.DisplayMessage = Resources.Pages.Admin.SystemSettings_Error_SmtpSyntax; } break; case "ExtUrl": if (!newValue.EndsWith("/")) { newValue = newValue + "/"; } if (!newValue.StartsWith("http://") && !newValue.StartsWith("https://")) { newValue = "https://" + newValue; } SystemSettings.ExternalUrl = newValue; result.NewData = newValue; result.ResultCode = AjaxTextBox.CodeSuccess; break; case "InstallationName": result.NewData = newValue.Trim(); result.ResultCode = AjaxTextBox.CodeSuccess; SystemSettings.InstallationName = result.NewData; break; case "AdminSender": result.NewData = newValue.Trim(); result.ResultCode = AjaxTextBox.CodeSuccess; SystemSettings.InstallationName = result.NewData; break; case "AdminAddress": result.NewData = newValue.Trim(); result.ResultCode = AjaxTextBox.CodeSuccess; SystemSettings.AdminNotificationAddress = result.NewData; break; default: throw new NotImplementedException("Unknown cookie in StoreCallback"); } return(result); }
public void Transmit(PayloadEnvelope envelope, Person person) { // Create the renderer via reflection of the static FromXml method Assembly assembly = typeof(PayloadEnvelope).Assembly; Type payloadType = assembly.GetType(envelope.PayloadClass); if (payloadType == null) { NotificationCustomStrings customStrings = new NotificationCustomStrings(); customStrings["UnrecognizedPayloadType"] = envelope.PayloadClass; OutboundComm.CreateNotification(null, NotificationResource.System_UnrecognizedPayload, customStrings); throw new OutboundCommTransmitException("Unrecognized or uninstantiable payload type: " + envelope.PayloadClass); } var methodInfo = payloadType.GetMethod("FromXml", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy); ICommsRenderer renderer = (ICommsRenderer)(methodInfo.Invoke(null, new object[] { envelope.PayloadXml })); RenderedComm comm = renderer.RenderComm(person); MailMessage mail = new MailMessage(); // This is a rather simple mail (no images or stuff like that) mail.Subject = (string)comm[CommRenderPart.Subject]; mail.SubjectEncoding = Encoding.UTF8; try { mail.From = new MailAddress((string)comm[CommRenderPart.SenderMail], (string)comm[CommRenderPart.SenderName], Encoding.UTF8); // SPECIAL CASE for sandbox mails -- ugly code but wtf if (person.Identity == 1 && PilotInstallationIds.DevelopmentSandbox == SystemSettings.InstallationId && mail.Subject.Contains("|")) { string[] separated = mail.Subject.Split('|'); mail.Subject = separated[0]; mail.To.Add(new MailAddress(separated[1], "Swarmops Sandbox Administrator")); } else // regular case to be used... like everywhere else except for the sandbox test { if (string.IsNullOrWhiteSpace(person.Mail)) { throw new OutboundCommTransmitException("No valid mail address for " + person.Canonical); } mail.To.Add(new MailAddress(person.Mail, person.Name)); } } catch (ArgumentException e) { // Address failure -- either sender or recipient _cacheReloadTime = Constants.DateTimeLow; throw new OutboundCommTransmitException("Cannot send mail to " + person.Mail, e); } string mailBodyText = (string)comm[CommRenderPart.BodyText]; mailBodyText = mailBodyText.Replace("[Addressee]", person.Canonical); string mailBodyHtml = comm.ContainsKey(CommRenderPart.BodyHtml)? (string)comm[CommRenderPart.BodyHtml]: string.Empty; mailBodyHtml = mailBodyHtml.Replace("[Addressee]", person.Canonical); mail.Body = mailBodyText; mail.BodyEncoding = Encoding.UTF8; string smtpServer = _smtpServerCache; int smtpPort = _smtpPortCache; string smtpUser = _smtpUserCache; string smtpPassword = _smtpPasswordCache; DateTime now = DateTime.Now; if (now > _cacheReloadTime) { smtpServer = _smtpServerCache = SystemSettings.SmtpHost; smtpPort = _smtpPortCache = SystemSettings.SmtpPort; smtpUser = _smtpUserCache = SystemSettings.SmtpUser; smtpPassword = _smtpPasswordCache = SystemSettings.SmtpPassword; _cacheReloadTime = now.AddMinutes(5); } if (string.IsNullOrEmpty(smtpServer)) { smtpServer = "localhost"; smtpPort = 25; // For development use only - invalidate cache instead of this, forcing re-reload _cacheReloadTime = Constants.DateTimeLow; } SmtpClient mailClient = new SmtpClient(smtpServer, smtpPort); if (!string.IsNullOrEmpty(smtpUser)) { mailClient.Credentials = new System.Net.NetworkCredential(smtpUser, smtpPassword); } try { mailClient.Send(mail); } catch (Exception e) { _cacheReloadTime = Constants.DateTimeLow; throw new OutboundCommTransmitException("Cannot send mail to " + person.Mail, e); } }
private static void Main(string[] args) { // Are we running yet? if (!SystemSettings.DatabaseInitialized) { // will restart the service every 15s until db initialized on OOBE // also, the read of DatabaseInitialized can and will fail if // we're not initalized enough to even have a database throw new InvalidOperationException(); } // Check if we're Sandbox if (PilotInstallationIds.IsPilot(PilotInstallationIds.DevelopmentSandbox)) { _isSandbox = true; } // Disable SSL cert checking (because Mono doesn't have a cert repo, dammit) Swarmops.Logic.Support.SupportFunctions.DisableSslCertificateChecks(); // Other one-off initializations SupportFunctions.OperatingTopology = OperatingTopology.FrontendSocket; // Initiate main loop UnixSignal[] killSignals = null; _activeAlarms = new Dictionary <FrontendMalfunctions, bool>(); if (!Debugger.IsAttached) { killSignals = new UnixSignal[] { new UnixSignal(Signum.SIGINT), new UnixSignal(Signum.SIGTERM) }; } Console.WriteLine(" * Swarmops Frontend Socket Server starting up."); OutboundComm.CreateNotification(null, NotificationResource.System_Startup_Frontend); // Initialize socket server and client _socketServer = new WebSocketServer(SystemSettings.WebsocketPortFrontend); _socketServer.AddWebSocketService <FrontendServices>("/Front"); // _socketServer.KeepClean = false; // as per the author's recommendation - this may be bad in the long run _socketServer.Start(); DateTime cycleStartTime = DateTime.UtcNow; DateTime cycleEndTime; int lastSecond = cycleStartTime.Second; int lastMinute = cycleStartTime.Minute; int lastHour = cycleStartTime.Hour; string backendHostname = SystemSettings.BackendHostnameOverride; if (String.IsNullOrEmpty(backendHostname)) { backendHostname = SystemSettings.BackendHostname; } string backendSocketUri = "ws://" + backendHostname + ":" + SystemSettings.WebsocketPortBackend.ToString(CultureInfo.InvariantCulture) + "/Backend?Auth=" + Authority.GetSystemAuthorityToken("Frontend"); bool exitFlag = false; Console.WriteLine("Connecting backend socket " + backendSocketUri); using (_backendSocket = new WebSocket(backendSocketUri)) { _backendSocket.OnMessage += new EventHandler <MessageEventArgs>(OnBackendMessage); _backendSocket.OnOpen += new EventHandler(OnBackendOpen); _backendSocket.OnClose += new EventHandler <CloseEventArgs>(OnBackendClose); _backendSocket.OnError += new EventHandler <ErrorEventArgs>(OnBackendError); _backendSocket.Connect(); while (!exitFlag) // exit is handled by signals handling at end of loop { cycleStartTime = DateTime.UtcNow; cycleEndTime = cycleStartTime.AddSeconds(10); try { OnEveryTenSeconds(); if (cycleStartTime.Second < lastSecond) { OnEveryMinute(); if (cycleStartTime.Minute % 5 == 0) { OnEveryFiveMinutes(); } if (cycleStartTime.Minute % 30 == 0) { OnEveryHalfHour(); } } if (cycleStartTime.Minute < lastMinute) { OnEveryHour(); if (DateTime.Now.Hour == 10 && DateTime.Today.DayOfWeek == DayOfWeek.Tuesday) { // OnTuesdayMorning(); } } if (cycleStartTime.Hour >= 12 && lastHour < 12) { // OnNoon(); } if (cycleStartTime.Hour < lastHour) { // OnMidnight(); } } catch (Exception e) { Console.WriteLine(e.ToString()); // Note each "OnEvery..." catches its own errors and sends Exception mails, // so that failure in one should not stop the others from running. This particular // code should never run. // ExceptionMail.Send (new Exception ("Failed in swarmops-backend main loop", e), true); } lastSecond = cycleStartTime.Second; lastMinute = cycleStartTime.Minute; lastHour = cycleStartTime.Hour; // Wait for a maximum of ten seconds (the difference between cycleStartTime and cycleEndTime) DateTime utcNow = DateTime.UtcNow; while (utcNow < cycleEndTime && !exitFlag) { int signalIndex = 250; // Block until a SIGINT or SIGTERM signal is generated, or 1/4 second has passed. // However, we can't do that in a development environment - it won't have the // Mono.Posix assembly, and won't understand UnixSignals. So people running this in // a dev environment will need to stop it manually. if (!Debugger.IsAttached) { signalIndex = UnixSignal.WaitAny(killSignals, 250); } else { Thread.Sleep(250); } if (signalIndex < 250) { exitFlag = true; Console.WriteLine(" * Swarmops Frontend Socket Server caught signal " + killSignals[signalIndex].Signum + ", exiting"); } utcNow = DateTime.UtcNow; } } _socketServer.Stop(); Thread.Sleep(2000); Console.WriteLine(" * Swarmops Frontend Socket Server exiting"); } }