Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
LocalReservation.cs
1using Paiwise;
2using System;
3using System.Collections.Generic;
4using System.Threading.Tasks;
5using Waher.Events;
13
15{
20 {
21 private Wallet wallet;
22 private AccountEvent @event;
23 private decimal amount = 0;
24 private bool transferred = false;
25 private DateTime prevTP = DateTime.MinValue;
26 private ICurrencyConverterQuote quote = null;
27 private readonly bool validateSenderSignature;
28
34 public LocalReservation(EDalerUri Uri, bool ValidateSenderSignature)
35 : base(Uri)
36 {
37 this.validateSenderSignature = ValidateSenderSignature;
38 }
39
44 protected override async Task<bool> DoPrepare()
45 {
46 if (this.validateSenderSignature)
47 {
48 LegalIdentity Signer = null;
49
50 switch (this.Uri.FromType)
51 {
52 case EntityType.NetworkJid:
53 (Signer, _) = await this.Uri.EDaler.Legal.ValidateSenderSignature(
54 this.Uri.From, this.Uri.State, this.Uri.Created,
55 this.Uri.PreSign, this.Uri.Signature, null);
56 break;
57
58 case EntityType.LegalId:
59 LegalIdentity Temp = await LegalComponent.GetLocalLegalIdentity(this.Uri.From.BareJid);
60 if (Temp is null)
61 break;
62
63 XmppAddress TempAddress = new XmppAddress(Temp.Id);
64 int i = TempAddress.Domain.IndexOf('.');
65 string JidDomain = i < 0 ? this.Uri.EDaler.Server.Domain : TempAddress.Domain.Substring(i + 1);
66
67 (Signer, _) = await this.Uri.EDaler.Legal.ValidateSenderSignature(
68 new XmppAddress(Temp.Account + "@" + JidDomain),
69 this.Uri.State, this.Uri.Created, this.Uri.PreSign, this.Uri.Signature, null);
70
71 if (Signer is null)
72 return false;
73
74 if (Signer.Id != Temp.Id)
75 Signer = null;
76 break;
77 }
78
79 if (Signer is null)
80 {
81 this.Uri.State.Error(Uris.States.EDalerUriErrorType.BadRequest, "Invalid signature.", false);
82 return false;
83 }
84 }
85
86 if (!await base.DoPrepare())
87 return false;
88
89 this.wallet = await this.GetWallet(this.Uri.From, this.Uri.FromType);
90
91 if (this.wallet is null)
92 {
93 this.Uri.State.Error(Uris.States.EDalerUriErrorType.ServiceUnavailable, "Wallet not found.", false);
94 return false;
95 }
96
97 this.amount = this.Uri.TotalAmount;
98
99 if (this.Uri.Currency != this.wallet.Currency)
100 {
102 new CurrencyPair(this.wallet.Currency, this.Uri.Currency));
103
104 if (Converter is null)
105 {
106 this.Uri.State.Error(Uris.States.EDalerUriErrorType.Forbidden, "Cannot reserve eDaler in " + this.Uri.Currency + ". No currency conversion service available.", false);
107 return false;
108 }
109
110 try
111 {
112 this.quote = await Converter.GetCurrencyConversionQuote(this.wallet.Currency, this.Uri.Currency);
113 if (this.quote is null)
114 {
115 this.Uri.State.Error(Uris.States.EDalerUriErrorType.Forbidden, "Cannot reserve eDaler in " + this.Uri.Currency + ". Conversion not supported by service.", false);
116 return false;
117 }
118 }
119 catch (Exception ex)
120 {
121 this.Uri.State.Error(Uris.States.EDalerUriErrorType.Forbidden, "Cannot reserve eDaler in " + this.Uri.Currency + ". Currency converter reports: " + ex.Message, false);
122 return false;
123 }
124
125 this.amount *= this.quote.Rate;
126
127 // TODO: Commission
128 }
129
130 if (this.wallet.Balance < this.amount)
131 {
132 this.Uri.State.Error(Uris.States.EDalerUriErrorType.Forbidden, "Lacks funds to perform reservation.", false);
133 return false;
134 }
135
136 return true;
137 }
138
143 protected override async Task<bool> DoExecute()
144 {
145 using (Semaphore Semaphore = await Semaphores.BeginWrite("wallet:" + this.wallet.Account))
146 {
147 if (this.wallet.Balance < this.amount)
148 {
149 this.Uri.State.Error(Uris.States.EDalerUriErrorType.Forbidden, "Lacks funds to perform reservation.", false);
150 return false;
151 }
152
153 if (!await base.DoExecute())
154 return false;
155
156 AccountEvent Ref = null;
157 DateTime BakTP = this.wallet.BalanceTimestamp;
158 decimal Bak = this.wallet.Balance;
159 decimal ReservedBak = this.wallet.Reserved;
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.From.Address,
170 Change = 0,
171 Balance = this.wallet.Balance - this.amount,
172 Reserved = this.wallet.Reserved + this.amount,
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.Reserved += this.amount;
184 this.wallet.BalanceTimestamp = Ref.Timestamp;
185
186 await Database.Update(this.wallet);
187
188 this.transferred = true;
189 }
190 catch (Exception ex)
191 {
192 Log.Exception(ex);
193
194 this.Uri.State.Error(Uris.States.EDalerUriErrorType.ServiceUnavailable, "eDaler reservation could not be processed due to internal failure.", false);
195
196 this.wallet.Balance = Bak;
197 this.wallet.Reserved = ReservedBak;
198 this.wallet.BalanceTimestamp = BakTP;
199 this.transferred = false;
200
201 if (RefAdded)
202 {
203 try
204 {
205 await Database.Delete(Ref);
206 }
207 catch (Exception ex2)
208 {
209 Log.Exception(ex2);
210 }
211 }
212
213 return false;
214 }
215
216 this.@event = Ref;
217 }
218
219 return true;
220 }
221
226 protected override async Task<bool> DoCommit()
227 {
228 if (!await base.DoCommit())
229 return false;
230
231 CaseInsensitiveString Domain = this.wallet.Domain ?? this.Uri.EDaler.Server.Domain;
232 XmppAddress To = new XmppAddress(this.wallet.Account + "@" + Domain);
233 string Xml = await this.Uri.EDaler.GetWalletBalanceXml(this.wallet.Account, Domain, this.@event);
234
235 await this.Uri.EDaler.Server.SendMessage(string.Empty, string.Empty,
236 new XmppAddress(this.Uri.EDaler.Subdomain + "." + Domain), To, string.Empty, Xml);
237
238 return true;
239 }
240
247 protected override void LogEvent(string To, string From, decimal Amount, string Currency,
248 KeyValuePair<string, object>[] Tags)
249 {
250 Append(ref Tags, this.quote);
251
252 Log.Notice(Amount.ToString() + " " + Currency + " eDaler reserved in wallet.",
253 To, From, "eDalerReserved", EventLevel.Major, Tags);
254 }
255
256 internal static void Append(ref KeyValuePair<string, object>[] Tags, ICurrencyConverterQuote Quote)
257 {
258 if (!(Quote is null))
259 {
260 int c = Tags.Length;
261 Array.Resize(ref Tags, c + 5);
262 Tags[c] = new KeyValuePair<string, object>("ExchangeFrom", Quote.FromCurrency);
263 Tags[c + 1] = new KeyValuePair<string, object>("ExchangeTo", Quote.ToCurrency);
264 Tags[c + 2] = new KeyValuePair<string, object>("ExchangeRate", Quote.Rate);
265 Tags[c + 3] = new KeyValuePair<string, object>("Timestamp", Quote.Timestamp);
266 Tags[c + 4] = new KeyValuePair<string, object>("Source", Quote.Source);
267 }
268 }
269
274 protected override async Task<bool> DoRollback()
275 {
276 bool Result = true;
277
278 using (Semaphore Semaphore = await Semaphores.BeginWrite("wallet:" + this.wallet.Account))
279 {
280 if (this.amount != 0 && this.transferred)
281 {
282 DateTime BakTP = this.wallet.BalanceTimestamp;
283 decimal Bak = this.wallet.Balance;
284 decimal ReservedBak = this.wallet.Reserved;
285
286 try
287 {
288 this.wallet.Balance += this.amount;
289 this.wallet.Reserved -= this.amount;
290
291 if (this.wallet.BalanceTimestamp == this.@event.Timestamp)
292 this.wallet.BalanceTimestamp = this.prevTP;
293
294 await Database.Update(this.wallet);
295 this.amount = 0;
296 this.transferred = false;
297 }
298 catch (Exception ex)
299 {
300 Result = false;
301
302 this.wallet.Balance = Bak;
303 this.wallet.Reserved = ReservedBak;
304 this.wallet.BalanceTimestamp = BakTP;
305
306 KeyValuePair<string, object>[] Tags = new KeyValuePair<string, object>[]
307 {
308 new KeyValuePair<string, object>("Amount", this.amount),
309 new KeyValuePair<string, object>("Currency", this.Uri.Currency),
310 new KeyValuePair<string, object>("RefId", this.Uri.Id.ToString()),
311 new KeyValuePair<string, object>("Sender", this.Uri.State.Sender.Value),
312 new KeyValuePair<string, object>("From", this.Uri.From.Address.Value),
313 new KeyValuePair<string, object>("To", this.Uri.From.Address.Value),
314 new KeyValuePair<string, object>("Uri", this.Uri.UriString)
315 };
316
317 Append(ref Tags, this.quote);
318
319 Log.Error("Unable to rollback wallet reservation of " + this.amount.ToString() +
320 " for " + this.wallet.Account + ". Error reported:\r\n\r\n" + ex.Message,
321 this.wallet.Account, string.Empty, "eDalerError", Tags);
322 }
323 }
324
325 if (!(this.@event is null))
326 {
327 try
328 {
329 await Database.Delete(this.@event);
330 this.@event = null;
331 }
332 catch (Exception ex)
333 {
334 Result = false;
335
336 KeyValuePair<string, object>[] Tags = new KeyValuePair<string, object>[]
337 {
338 new KeyValuePair<string, object>("Amount", this.amount),
339 new KeyValuePair<string, object>("Currency", this.Uri.Currency),
340 new KeyValuePair<string, object>("RefId", this.Uri.Id.ToString()),
341 new KeyValuePair<string, object>("Sender", this.Uri.State.Sender.Value),
342 new KeyValuePair<string, object>("From", this.Uri.From.Address.Value),
343 new KeyValuePair<string, object>("To", this.Uri.From.Address.Value),
344 new KeyValuePair<string, object>("Uri", this.Uri.UriString)
345 };
346
347 Append(ref Tags, this.quote);
348
349 Log.Error("Unable to rollback wallet event of reservation of " + this.amount.ToString() +
350 " for " + this.wallet.Account + ". Error reported:\r\n\r\n" + ex.Message,
351 this.wallet.Account, string.Empty, "eDalerError", Tags);
352 }
353 }
354
355 if (!await base.DoRollback())
356 Result = false;
357 }
358
359 return Result;
360 }
361 }
362}
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....
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 async Task< bool > DoCommit()
Performs actual commit.
override void LogEvent(string To, string From, decimal Amount, string Currency, KeyValuePair< string, object >[] Tags)
Logs an event corresponding to the transaction.
override async Task< bool > DoPrepare()
Performs actual preparation.
LocalReservation(EDalerUri Uri, bool ValidateSenderSignature)
Handles the local reservation of eDaler.
override async Task< bool > DoExecute()
Performs actual execution.
override async Task< bool > DoRollback()
Performs actual rollback.
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