Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
LocalSender.cs
1using Paiwise;
2using System;
3using System.Collections.Generic;
4using System.Threading.Tasks;
5using Waher.Events;
14
16{
21 {
22 private Wallet wallet;
23 private AccountEvent @event;
24 private decimal amount = 0;
25 private bool transferred = false;
26 private DateTime prevTP = DateTime.MinValue;
27 private ICurrencyConverterQuote quote = null;
28 private readonly bool validateSenderSignature;
29
35 public LocalSender(EDalerUri Uri, bool ValidateSenderSignature)
36 : base(Uri)
37 {
38 this.validateSenderSignature = ValidateSenderSignature;
39 }
40
45 protected override async Task<bool> DoPrepare()
46 {
47 if (this.validateSenderSignature)
48 {
49 LegalIdentity Signer = null;
50
51 switch (this.Uri.FromType)
52 {
53 case EntityType.NetworkJid:
54 (Signer, _) = await this.Uri.EDaler.Legal.ValidateSenderSignature(
55 this.Uri.From, this.Uri.State, this.Uri.Created,
56 this.Uri.PreSign, this.Uri.Signature, null);
57 break;
58
59 case EntityType.LegalId:
60 LegalIdentity Temp = await LegalComponent.GetLocalLegalIdentity(this.Uri.From.BareJid);
61 if (Temp is null)
62 break;
63
64 XmppAddress TempAddress = new XmppAddress(Temp.Id);
65 int i = TempAddress.Domain.IndexOf('.');
66 string JidDomain = i < 0 ? this.Uri.EDaler.Server.Domain : TempAddress.Domain.Substring(i + 1);
67
68 (Signer, _) = await this.Uri.EDaler.Legal.ValidateSenderSignature(
69 new XmppAddress(Temp.Account + "@" + JidDomain),
70 this.Uri.State, this.Uri.Created, this.Uri.PreSign, this.Uri.Signature, null);
71
72 if (Signer is null)
73 return false;
74
75 if (Signer.Id != Temp.Id)
76 Signer = null;
77 break;
78 }
79
80 if (Signer is null)
81 {
82 this.Uri.State.Error(Uris.States.EDalerUriErrorType.BadRequest, "Invalid sender signature.", false);
83 return false;
84 }
85 }
86
87 if (!await base.DoPrepare())
88 return false;
89
90 this.wallet = await this.GetWallet(this.Uri.From, this.Uri.FromType);
91
92 if (this.wallet is null)
93 {
94 this.Uri.State.Error(Uris.States.EDalerUriErrorType.ServiceUnavailable, "Sender wallet not found.", false);
95 return false;
96 }
97
98 this.amount = this.Uri.TotalAmount;
99
100 if (this.Uri.Currency != this.wallet.Currency)
101 {
103 new CurrencyPair(this.wallet.Currency, this.Uri.Currency));
104
105 if (Converter is null)
106 {
107 this.Uri.State.Error(Uris.States.EDalerUriErrorType.Forbidden, "Sender cannot send eDaler in " + this.Uri.Currency + ". No currency conversion service available.", false);
108 return false;
109 }
110
111 try
112 {
113 this.quote = await Converter.GetCurrencyConversionQuote(this.wallet.Currency, this.Uri.Currency);
114 if (this.quote is null)
115 {
116 this.Uri.State.Error(Uris.States.EDalerUriErrorType.Forbidden, "Sender cannot send eDaler in " + this.Uri.Currency + ". Conversion not supported by service.", false);
117 return false;
118 }
119 }
120 catch (Exception ex)
121 {
122 this.Uri.State.Error(Uris.States.EDalerUriErrorType.Forbidden, "Sender cannot send eDaler in " + this.Uri.Currency + ". Currency converter reports: " + ex.Message, false);
123 return false;
124 }
125
126 this.amount *= this.quote.Rate;
127
128 // TODO: Commission
129 }
130
131 if (this.wallet.Balance < this.amount)
132 {
133 this.Uri.State.Error(Uris.States.EDalerUriErrorType.Forbidden, "Sender lacks funds to perform transaction.", false);
134 return false;
135 }
136
137 return true;
138 }
139
144 protected override async Task<bool> DoExecute()
145 {
146 using (Semaphore Semaphore = await Semaphores.BeginWrite("wallet:" + this.wallet.Account))
147 {
148 if (this.wallet.Balance < this.amount)
149 {
150 this.Uri.State.Error(Uris.States.EDalerUriErrorType.Forbidden, "Sender lacks funds to perform transaction.", false);
151 return false;
152 }
153
154 if (!await base.DoExecute())
155 return false;
156
157 AccountEvent Ref = null;
158 DateTime BakTP = this.wallet.BalanceTimestamp;
159 decimal Bak = this.wallet.Balance;
160 bool RefAdded = false;
161
162 try
163 {
164 Ref = new AccountEvent()
165 {
166 TransactionId = this.Uri.Id,
167 Timestamp = DateTime.UtcNow,
168 Account = this.wallet.Account,
169 Remote = this.Uri.To.Address,
170 Change = -this.amount,
171 Balance = this.wallet.Balance - this.amount,
172 Reserved = this.wallet.Reserved,
173 EncryptedMessage = this.Uri.EncryptedMessage,
174 EncryptionPublicKey = this.Uri.EncryptionPublicKey
175 };
176
177 await Database.Insert(Ref);
178 RefAdded = true;
179
180 this.prevTP = this.wallet.BalanceTimestamp;
181
182 this.wallet.Balance -= this.amount;
183 this.wallet.BalanceTimestamp = Ref.Timestamp;
184
185 await Database.Update(this.wallet);
186
187 this.transferred = true;
188 }
189 catch (Exception ex)
190 {
191 Log.Exception(ex);
192
193 this.Uri.State.Error(Uris.States.EDalerUriErrorType.ServiceUnavailable, "eDaler transaction could not be processed due to internal failure.", false);
194
195 this.wallet.Balance = Bak;
196 this.wallet.BalanceTimestamp = BakTP;
197 this.transferred = false;
198
199 if (RefAdded)
200 {
201 try
202 {
203 await Database.Delete(Ref);
204 }
205 catch (Exception ex2)
206 {
207 Log.Exception(ex2);
208 }
209 }
210
211 return false;
212 }
213
214 this.@event = Ref;
215 }
216
217 return true;
218 }
219
224 protected override async Task<bool> DoCommit()
225 {
226 if (!await base.DoCommit())
227 return false;
228
229 CaseInsensitiveString Domain = this.wallet.Domain ?? this.Uri.EDaler.Server.Domain;
230 XmppAddress To = new XmppAddress(this.wallet.Account + "@" + Domain);
231
232 try
233 {
234 await StateMachineProcessor.EDalerSent(To, this.Uri);
235 }
236 catch (Exception ex)
237 {
238 Log.Exception(ex);
239 }
240
241 string Xml = await this.Uri.EDaler.GetWalletBalanceXml(this.wallet.Account, Domain, this.@event);
242
243 await this.Uri.EDaler.Server.SendMessage(string.Empty, string.Empty,
244 new XmppAddress(this.Uri.EDaler.Subdomain + "." + Domain), To, string.Empty, Xml);
245
246 return true;
247 }
248
255 protected override void LogEvent(string To, string From, decimal Amount, string Currency,
256 KeyValuePair<string, object>[] Tags)
257 {
258 Append(ref Tags, this.quote);
259
260 Log.Notice(Amount.ToString() + " " + Currency + " eDaler transmitted, and removed from wallet.",
261 To, From, "eDalerTransmitted", EventLevel.Major, Tags);
262 }
263
264 internal static void Append(ref KeyValuePair<string, object>[] Tags, ICurrencyConverterQuote Quote)
265 {
266 if (!(Quote is null))
267 {
268 int c = Tags.Length;
269 Array.Resize(ref Tags, c + 5);
270 Tags[c] = new KeyValuePair<string, object>("ExchangeFrom", Quote.FromCurrency);
271 Tags[c + 1] = new KeyValuePair<string, object>("ExchangeTo", Quote.ToCurrency);
272 Tags[c + 2] = new KeyValuePair<string, object>("ExchangeRate", Quote.Rate);
273 Tags[c + 3] = new KeyValuePair<string, object>("Timestamp", Quote.Timestamp);
274 Tags[c + 4] = new KeyValuePair<string, object>("Source", Quote.Source);
275 }
276 }
277
282 protected override async Task<bool> DoRollback()
283 {
284 bool Result = true;
285
286 using (Semaphore Semaphore = await Semaphores.BeginWrite("wallet:" + this.wallet.Account))
287 {
288 if (this.amount != 0 && this.transferred)
289 {
290 DateTime BakTP = this.wallet.BalanceTimestamp;
291 decimal Bak = this.wallet.Balance;
292
293 try
294 {
295 this.wallet.Balance += this.amount;
296
297 if (this.wallet.BalanceTimestamp == this.@event.Timestamp)
298 this.wallet.BalanceTimestamp = this.prevTP;
299
300 await Database.Update(this.wallet);
301 this.amount = 0;
302 this.transferred = false;
303 }
304 catch (Exception ex)
305 {
306 Result = false;
307
308 this.wallet.Balance = Bak;
309 this.wallet.BalanceTimestamp = BakTP;
310
311 KeyValuePair<string, object>[] Tags = new KeyValuePair<string, object>[]
312 {
313 new KeyValuePair<string, object>("Amount", -this.amount),
314 new KeyValuePair<string, object>("Currency", this.Uri.Currency),
315 new KeyValuePair<string, object>("RefId", this.Uri.Id.ToString()),
316 new KeyValuePair<string, object>("Sender", this.Uri.State.Sender.Value),
317 new KeyValuePair<string, object>("From", this.Uri.From.Address.Value),
318 new KeyValuePair<string, object>("To", this.Uri.To.Address.Value),
319 new KeyValuePair<string, object>("Uri", this.Uri.UriString)
320 };
321
322 Append(ref Tags, this.quote);
323
324 Log.Error("Unable to rollback wallet change of " + (-this.amount).ToString() +
325 " for " + this.wallet.Account + ". Error reported:\r\n\r\n" + ex.Message,
326 this.wallet.Account, string.Empty, "eDalerError", Tags);
327 }
328 }
329
330 if (!(this.@event is null))
331 {
332 try
333 {
334 await Database.Delete(this.@event);
335 this.@event = null;
336 }
337 catch (Exception ex)
338 {
339 Result = false;
340
341 KeyValuePair<string, object>[] Tags = new KeyValuePair<string, object>[]
342 {
343 new KeyValuePair<string, object>("Amount", -this.amount),
344 new KeyValuePair<string, object>("Currency", this.Uri.Currency),
345 new KeyValuePair<string, object>("RefId", this.Uri.Id.ToString()),
346 new KeyValuePair<string, object>("Sender", this.Uri.State.Sender.Value),
347 new KeyValuePair<string, object>("From", this.Uri.From.Address.Value),
348 new KeyValuePair<string, object>("To", this.Uri.To.Address.Value),
349 new KeyValuePair<string, object>("Uri", this.Uri.UriString)
350 };
351
352 Append(ref Tags, this.quote);
353
354 Log.Error("Unable to rollback wallet event of " + (-this.amount).ToString() +
355 " for " + this.wallet.Account + ". Error reported:\r\n\r\n" + ex.Message,
356 this.wallet.Account, string.Empty, "eDalerError", Tags);
357 }
358 }
359
360 if (!await base.DoRollback())
361 Result = false;
362 }
363
364 return Result;
365 }
366 }
367}
Contains a pair of currencies, for currency conversion.
Definition: CurrencyPair.cs:9
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1647
static void Error(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an error event.
Definition: Log.cs:682
static void Notice(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs a notice event.
Definition: Log.cs:450
CaseInsensitiveString Subdomain
Subdomain name.
Definition: Component.cs:76
XmppServer Server
XMPP Server.
Definition: Component.cs:96
Contains information about one XMPP address.
Definition: XmppAddress.cs:9
CaseInsensitiveString Domain
Domain
Definition: XmppAddress.cs:97
CaseInsensitiveString Address
XMPP Address
Definition: XmppAddress.cs:37
CaseInsensitiveString BareJid
Bare JID
Definition: XmppAddress.cs:45
static readonly XmppAddress Empty
Empty address.
Definition: XmppAddress.cs:31
Task< bool > SendMessage(string Type, string Id, string From, string To, string Language, string ContentXml)
Sends a Message stanza to a recipient.
Definition: XmppServer.cs:3412
CaseInsensitiveString Domain
Domain name.
Definition: XmppServer.cs:882
Represents a case-insensitive string.
string Value
String-representation of the case-insensitive string. (Representation is case sensitive....
static readonly CaseInsensitiveString Empty
Empty case-insensitive string
int IndexOf(CaseInsensitiveString value, StringComparison comparisonType)
Reports the zero-based index of the first occurrence of the specified string in the current System....
CaseInsensitiveString Substring(int startIndex, int length)
Retrieves a substring from this instance. The substring starts at a specified character position and ...
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:19
static async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:626
static async Task Delete(object Object)
Deletes an object in the database.
Definition: Database.cs:717
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:95
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:14
Represents a named semaphore, i.e. an object, identified by a name, that allows single concurrent wri...
Definition: Semaphore.cs:19
Static class of application-wide semaphores that can be used to order access to editable objects.
Definition: Semaphores.cs:16
static async Task< Semaphore > BeginWrite(string Key)
Waits until the semaphore identified by Key is ready for writing. Each call to BeginWrite must be fo...
Definition: Semaphores.cs:90
Abstract base class for primary transaction objects (i.e. objects that control the transaction).
async Task< Wallet > GetWallet(XmppAddress For, EntityType ForType)
Gets the wallet of an entity in an eDaler transaction.
override void LogEvent(string To, string From, decimal Amount, string Currency, KeyValuePair< string, object >[] Tags)
Logs an event corresponding to the transaction.
Definition: LocalSender.cs:255
override async Task< bool > DoExecute()
Performs actual execution.
Definition: LocalSender.cs:144
override async Task< bool > DoRollback()
Performs actual rollback.
Definition: LocalSender.cs:282
override async Task< bool > DoPrepare()
Performs actual preparation.
Definition: LocalSender.cs:45
LocalSender(EDalerUri Uri, bool ValidateSenderSignature)
Handles the local sender of eDaler.
Definition: LocalSender.cs:35
override async Task< bool > DoCommit()
Performs actual commit.
Definition: LocalSender.cs:224
Abstract base class for eDaler URIs
Definition: EDalerUri.cs:20
string UriString
Original URI String.
Definition: EDalerUri.cs:101
EDalerUriState State
URI State object.
Definition: EDalerUri.cs:173
EDalerComponent EDaler
eDaler component reference
Definition: EDalerUri.cs:167
byte[] PreSign
Binary representation of URI, before appending signature.
Definition: EDalerUri.cs:162
DateTime Created
When URI was created
Definition: EDalerUri.cs:136
decimal TotalAmount
Total amount: Amount+AmountExtra
Definition: EDalerUri.cs:121
byte[] EncryptedMessage
Encrypted message for recipient. If EncryptionPublicKey is null, the message is just UTF-8 encoded.
Definition: EDalerUri.cs:197
Retains the current balance of an account.
Definition: Wallet.cs:16
Interface for currency conversion quotes.
DateTime Timestamp
Timestamp of quote.
CaseInsensitiveString FromCurrency
Conversion from this currency.
CaseInsensitiveString ToCurrency
Conversion to this currency.
Interface for currency converter services
Task< ICurrencyConverterQuote > GetCurrencyConversionQuote(CaseInsensitiveString FromCurrency, CaseInsensitiveString ToCurrency)
Gets a Currency Exchange Rate from one currency to another.
EventLevel
Event level.
Definition: EventLevel.cs:7
EntityType
Type of entity referred to in transaction.
Definition: Transaction.cs:15