Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
MarketplaceProcessor.cs
1using System;
2using System.Collections.Generic;
3using System.Text;
4using System.Threading.Tasks;
5using System.Xml;
7using Waher.Events;
17using Waher.Script;
27
29{
33 public static class MarketplaceProcessor
34 {
35 private static Scheduler scheduler;
36
40 public const string MarketplaceNamespace = "https://paiwise.tagroot.io/Schema/Marketplace.xsd";
41
45 public const string AuctioneerRole = "Auctioneer";
46
50 public const string SellerRole = "Seller";
51
55 public const string BuyerRole = "Buyer";
56
57 private static readonly Dictionary<CaseInsensitiveString, AuctionItem> activeItems = new Dictionary<CaseInsensitiveString, AuctionItem>();
58 private static decimal? minCommission = null;
59
71 internal static async Task ContractSigned(Contract Contract, bool ContractIsLocked,
72 Dictionary<CaseInsensitiveString, Parameter> TransientParameters,
74 {
75 if (Contract?.ForMachines is null || Contract.ForMachinesNamespace != MarketplaceNamespace)
76 return;
77
78 if ((TransientParameters?.Count ?? 0) > 0)
79 {
80 await NeuroFeaturesProcessor.RejectContract(Contract,ContractIsLocked,
81 "Transient parameters not permitted in auctions.", true, Legal, EDaler);
82 return;
83 }
84
85 switch (Contract.ForMachinesLocalName)
86 {
87 case "ForSale":
88 if (!OnlyMissingAuctioneer(Contract, SellerRole, AuctioneerRole, out string PaymentLegalId, out string PaymentJid))
89 return;
90
91 XmlDocument Doc = new XmlDocument()
92 {
93 PreserveWhitespace = true
94 };
95 Doc.LoadXml(Contract.ForMachines);
96 await AddItemForSale(Doc.DocumentElement, Contract, ContractIsLocked, PaymentLegalId, PaymentJid, Legal, EDaler, PubSub);
97 break;
98
99 case "ToBuy":
100 if (!OnlyMissingAuctioneer(Contract, BuyerRole, AuctioneerRole, out PaymentLegalId, out PaymentJid))
101 return;
102
103 Doc = new XmlDocument()
104 {
105 PreserveWhitespace = true
106 };
107 Doc.LoadXml(Contract.ForMachines);
108 await AddItemToBuy(Doc.DocumentElement, Contract, ContractIsLocked, PaymentLegalId, PaymentJid, Legal, EDaler, PubSub);
109 break;
110
111 case "Offer":
112 if (!OnlyMissingAuctioneer(Contract, null, AuctioneerRole, out _, out _))
113 return;
114
115 Doc = new XmlDocument()
116 {
117 PreserveWhitespace = true
118 };
119 Doc.LoadXml(Contract.ForMachines);
120 await AddOffer(Doc.DocumentElement, Contract, ContractIsLocked, Legal, EDaler, PubSub);
121 break;
122
123 default:
124 Log.Error("Rejected marketplace contract: Unrecognized element: " + Contract.ForMachinesLocalName, Contract.ContractId);
125 break;
126 }
127 }
128
129 internal static bool OnlyMissingAuctioneer(Contract Contract, string PaymentPartyRole,
130 string TrustProviderRole, out string PaymentLegalId, out string PaymentJid)
131 {
132 PaymentLegalId = null;
133 PaymentJid = null;
134
135 if (Contract.State != ContractState.BeingSigned || Contract.ClientSignatures is null)
136 return false;
137
138 if ((Contract.Roles?.Length ?? 0) < 2)
139 return false;
140
141 bool HasAuctioneer = false;
142 bool HasPaymentParty = false;
143 Dictionary<string, SignatureStat> Signatures = new Dictionary<string, SignatureStat>();
144
145 foreach (Role Role in Contract.Roles)
146 {
147 if (Role.CanRevoke)
148 return false;
149
150 if (Role.Name == TrustProviderRole)
151 {
152 if (Role.MinCount != 1 || Role.MaxCount != 1)
153 return false;
154
155 HasAuctioneer = true;
156 }
157 else
158 {
159 if (!(PaymentPartyRole is null) && Role.Name == PaymentPartyRole)
160 {
161 if (Role.MinCount != 1 || Role.MaxCount != 1)
162 return false;
163
164 HasPaymentParty = true;
165 }
166
167 Signatures[Role.Name] = new SignatureStat()
168 {
169 Min = Role.MinCount,
170 Max = Role.MaxCount,
171 Count = 0
172 };
173 }
174 }
175
176 if (!HasAuctioneer || (!HasPaymentParty && !(PaymentPartyRole is null)))
177 return false;
178
179 foreach (ClientSignature Signature in Contract.ClientSignatures)
180 {
181 if (Signatures.TryGetValue(Signature.Role, out SignatureStat Stat))
182 Stat.Count++;
183 else
184 return false;
185
186 if (!(PaymentPartyRole is null) && Signature.Role == PaymentPartyRole)
187 {
188 PaymentLegalId = Signature.LegalId;
189 PaymentJid = Signature.BareJid;
190 }
191 }
192
193 foreach (SignatureStat Stat in Signatures.Values)
194 {
195 if (Stat.Count < Stat.Min || Stat.Count > Stat.Max)
196 return false;
197 }
198
199 if (!(Contract.Parts is null))
200 {
201 foreach (Part Part in Contract.Parts)
202 {
203 if (Part.Role == TrustProviderRole && !LegalIdentityConfiguration.IsMeApproved(Part.LegalId))
204 {
205 // TODO: Forward contract in message to intended auctioneer
206 return false;
207 }
208 }
209 }
210
211 return true;
212 }
213
214 private class SignatureStat
215 {
216 public int Min;
217 public int Max;
218 public int Count;
219 }
220
221 private static async Task<bool> AddItemForSale(XmlElement ForSale, Contract Contract, bool ContractIsLocked, string PaymentLegalId,
222 string PaymentJid, LegalComponent Legal, EDalerComponent EDaler, PubSubComponent PubSub)
223 {
224 AuctionItem Item = await ParseAuctionItem(ForSale, PaymentLegalId, PaymentJid, Contract);
225 if (Item is null)
226 return false;
227
228 await AddItem(Item, Contract, ContractIsLocked, OptimizeDirection.Up, Legal, EDaler, PubSub);
229
230 return true;
231 }
232
233 private static async Task<bool> AddItemToBuy(XmlElement ToBuy, Contract Contract, bool ContractIsLocked, string PaymentLegalId,
234 string PaymentJid, LegalComponent Legal, EDalerComponent EDaler, PubSubComponent PubSub)
235 {
236 AuctionItem Item = await ParseAuctionItem(ToBuy, PaymentLegalId, PaymentJid, Contract);
237 if (Item is null)
238 return false;
239
240 await AddItem(Item, Contract, ContractIsLocked, OptimizeDirection.Down, Legal, EDaler, PubSub);
241
242 return true;
243 }
244
245 private static async Task AddItem(AuctionItem Item, Contract Contract, bool ContractIsLocked, OptimizeDirection Direction, LegalComponent Legal,
247 {
248 Item.Direction = Direction;
249 await Database.Insert(Item);
250
251 Item.ScheduledExpiry = AddItem(Item, EDaler);
252
253 SignatureReference Ref = await SignContract(Contract, ContractIsLocked, Legal, PubSub, Item.Node, AuctioneerRole);
254 if (Ref is null)
255 {
256 lock (activeItems)
257 {
258 activeItems.Remove(Item.ContractId);
259 }
260
261 Scheduler.Remove(Item.ScheduledExpiry);
262 Item.ScheduledExpiry = DateTime.MinValue;
263
264 await Database.Delete(Item);
265 return;
266 }
267
268 Item.TrustProviderJid = Ref.BareJid;
269 Item.TrustProviderLegalId = Ref.LegalId;
270
271 await Database.Update(Item);
272 }
273
274 internal class SignatureReference
275 {
276 public string LegalId;
277 public string BareJid;
278 }
279
280 internal static Task<SignatureReference> SignContract(Contract Contract, bool ContractIsLocked, LegalComponent Legal, string Role)
281 {
282 return SignContract(Contract, ContractIsLocked, Legal, null, null, Role);
283 }
284
285 private static async Task<SignatureReference> SignContract(Contract Contract, bool ContractIsLocked, LegalComponent Legal,
286 PubSubComponent PubSub, string Node, string Role)
287 {
288 try
289 {
290 if (Gateway.ContractsClient is null)
291 return null;
292
293 StringBuilder sb = new StringBuilder();
294
295 if (Legal.IsComponentDomain(Networking.XMPP.XmppClient.GetDomain(Contract.ContractId), true))
296 {
298
299 if (ContractIsLocked)
300 Semaphore = null;
301 else
303
304 try
305 {
306 Contract.Serialize(sb, false, false, false, false, false, false, false, null, Legal);
307 byte[] Data = Encoding.UTF8.GetBytes(sb.ToString());
308 byte[] Signature = await Gateway.ContractsClient.SignAsync(Data, Networking.XMPP.Contracts.SignWith.LatestApprovedId);
309
310 Contract = await Legal.SignContract(Contract, true, Role, false, Signature, new XmppAddress(Gateway.ContractsClient.Client.BareJID));
311
312 if (!string.IsNullOrEmpty(Node))
313 {
314 string ContractXml = null;
315
316 try
317 {
318 sb.Clear();
319 Contract.Serialize(sb, true, true, false, false, false, false, false, null, Legal);
320 ContractXml = sb.ToString();
321
322 XmlDocument Xml = new XmlDocument()
323 {
324 PreserveWhitespace = true
325 };
326 Xml.LoadXml(ContractXml);
327
329 new XmppAddress(Contract.Account + "@" + Legal.MainDomain.Address),
331 }
332 catch (XmlException ex)
333 {
335 }
336 catch (Exception ex)
337 {
339 }
340 }
341
342 SignatureReference Result = new SignatureReference();
343
344 if (!(Contract.ClientSignatures is null))
345 {
346 foreach (ClientSignature Signature2 in Contract.ClientSignatures)
347 {
348 if (Signature2.Role == Role)
349 {
350 Result.LegalId = Signature2.Role;
351 Result.BareJid = Signature2.BareJid;
352 }
353 }
354 }
355
356 return Result;
357 }
358 finally
359 {
361 }
362 }
363 else
364 {
365 Contract.Serialize(sb, true, true, false, false, false, false, false, null, Legal);
366
367 string ContractXml = sb.ToString();
368 XmlDocument Xml = new XmlDocument()
369 {
370 PreserveWhitespace = true
371 };
372 Xml.LoadXml(ContractXml);
373
374 Networking.XMPP.Contracts.ParsedContract Parsed = await Networking.XMPP.Contracts.Contract.Parse(Xml, Gateway.ContractsClient);
376
377 Contract2 = await Gateway.ContractsClient.SignContractAsync(Contract2, Role, false);
378
379 if (!string.IsNullOrEmpty(Node))
380 {
381 ContractXml = null;
382
383 try
384 {
385 sb.Clear();
386 Contract2.Serialize(sb, true, true, false, false, false, false, false);
387 ContractXml = sb.ToString();
388 Xml = new XmlDocument()
389 {
390 PreserveWhitespace = true
391 };
392 Xml.LoadXml(ContractXml);
393
395 new XmppAddress(Contract.Account + "@" + Legal.MainDomain.Address),
396 PubSub.Subdomain, Contract.ContractId, Xml, Contract2.DefaultLanguage);
397 }
398 catch (XmlException ex)
399 {
401 }
402 catch (Exception ex)
403 {
405 }
406 }
407
408 SignatureReference Result = new SignatureReference();
409
410 if (!(Contract2.ClientSignatures is null))
411 {
412 foreach (Networking.XMPP.Contracts.ClientSignature Signature in
413 Contract2.ClientSignatures)
414 {
415 if (Signature.Role == Role)
416 {
417 Result.LegalId = Signature.Role;
418 Result.BareJid = Signature.BareJid;
419 }
420 }
421 }
422
423 return Result;
424 }
425 }
426 catch (XmlException ex)
427 {
428 ex = XML.AnnotateException(ex);
429 Log.Error("Unable to sign contract: " + ex.Message, Contract.ContractId);
430 return null;
431 }
432 catch (Exception ex)
433 {
434 Log.Error("Unable to sign contract: " + ex.Message, Contract.ContractId);
435 return null;
436 }
437 }
438
439 private static DateTime AddItem(AuctionItem Item, EDalerComponent EDaler)
440 {
441 if (Item.ContractId is null)
442 return DateTime.MaxValue;
443
444 lock (activeItems)
445 {
446 activeItems[Item.ContractId] = Item;
447 }
448
449 Item.ScheduledExpiry = Scheduler.Add(Item.Expires.ToLocalTime(), ItemExpires, new ExpiryRecord()
450 {
451 Item = Item,
452 EDaler = EDaler
453 });
454
455 return Item.ScheduledExpiry;
456 }
457
458 private class ExpiryRecord
459 {
460 public AuctionItem Item;
461 public EDalerComponent EDaler;
462 }
463
464 private static Scheduler Scheduler
465 {
466 get
467 {
468 if (scheduler is null)
469 {
470 if (Types.TryGetModuleParameter("Scheduler", out object Obj) && Obj is Scheduler Scheduler)
471 scheduler = Scheduler;
472 else
473 {
474 scheduler = new Scheduler();
475 Log.Terminating += Log_Terminating;
476 }
477 }
478
479 return scheduler;
480 }
481 }
482
483 private static void Log_Terminating(object Sender, EventArgs e)
484 {
485 scheduler?.Dispose();
486 scheduler = null;
487
488 Log.Terminating -= Log_Terminating;
489 }
490
491 internal static async Task LoadActiveItems(EDalerComponent EDaler)
492 {
493 foreach (AuctionItem Item in await Database.Find<AuctionItem>(new FilterFieldEqualTo("Processed", null), "Created"))
494 AddItem(Item, EDaler);
495 }
496
497 private static async Task<AuctionItem> ParseAuctionItem(XmlElement Item, string PaymentLegalId,
498 string PaymentJid, Contract Contract)
499 {
500 List<AuctionItemTag> Tags = new List<AuctionItemTag>();
501 List<ExpectedTag> ExpectedTags = null;
502 string Type = null;
503 string Class = null;
504 string Node = null;
505 string Currency = null;
506 string ScoreFunction = null;
507 string TokenId = null;
508 decimal? AskingPrice = null;
509 decimal? RejectPrice = null;
510 decimal? AcceptPrice = null;
511 decimal? CommissionPercent = null;
512 decimal? RejectScore = null;
513 decimal? AcceptScore = null;
514 decimal? AvailableDays = null;
515
516 foreach (XmlNode N in Item.ChildNodes)
517 {
518 if (!(N is XmlElement E) || E.NamespaceURI != MarketplaceNamespace)
519 continue;
520
521 switch (E.LocalName)
522 {
523 case "Description":
524 foreach (XmlNode N2 in E.ChildNodes)
525 {
526 if (!(N2 is XmlElement E2))
527 continue;
528
529 switch (E2.LocalName)
530 {
531 case "Type":
532 if (!(await PaiwiseProcessor.GetParameterValue(E2, Contract) is string s))
533 {
534 Log.Error("Rejected marketplace contract: Invalid type.", Contract.ContractId);
535 return null;
536 }
537
538 Type = s;
539 break;
540
541 case "Class":
542 if (!(await PaiwiseProcessor.GetParameterValue(E2, Contract) is string s2))
543 {
544 Log.Error("Rejected marketplace contract: Invalid class.", Contract.ContractId);
545 return null;
546 }
547
548 Class = s2;
549 break;
550
551 case "Node":
552 if (!(await PaiwiseProcessor.GetParameterValue(E2, Contract) is string s3))
553 {
554 Log.Error("Rejected marketplace contract: Invalid node.", Contract.ContractId);
555 return null;
556 }
557
558 Node = s3;
559 break;
560
561 case "Tag":
562 AuctionItemTag Tag = new AuctionItemTag()
563 {
564 Name = XML.Attribute(E2, "name"),
565 Value = await PaiwiseProcessor.GetParameterValue(E2, Contract)
566 };
567
568 Tags.Add(Tag);
569
570 if (Tag.Name == "TokenID")
571 TokenId = Tag.Value as string;
572 break;
573 }
574 }
575 break;
576
577 case "Price":
578 foreach (XmlNode N2 in E.ChildNodes)
579 {
580 if (!(N2 is XmlElement E2))
581 continue;
582
583 switch (E2.LocalName)
584 {
585 case "AskingPrice":
586 if (!(await PaiwiseProcessor.GetParameterValue(E2, Contract) is decimal d))
587 {
588 Log.Error("Rejected marketplace contract: Invalid asking price.", Contract.ContractId);
589 return null;
590 }
591
592 AskingPrice = d;
593 break;
594
595 case "RejectPrice":
596 if (!(await PaiwiseProcessor.GetParameterValue(E2, Contract) is decimal d2))
597 {
598 Log.Error("Rejected marketplace contract: Invalid rejection price.", Contract.ContractId);
599 return null;
600 }
601
602 RejectPrice = d2;
603 break;
604
605 case "AcceptPrice":
606 if (!(await PaiwiseProcessor.GetParameterValue(E2, Contract) is decimal d3))
607 {
608 Log.Error("Rejected marketplace contract: Invalid accept price.", Contract.ContractId);
609 return null;
610 }
611
612 AcceptPrice = d3;
613 break;
614
615 case "CommissionPercent":
616 if (!(await PaiwiseProcessor.GetParameterValue(E2, Contract) is decimal d4) || d4 < 0 || d4 > 100)
617 {
618 Log.Error("Rejected marketplace contract: Invalid commission (%).", Contract.ContractId);
619 return null;
620 }
621
622 CommissionPercent = d4;
623 break;
624
625 case "Currency":
626 if (!(await PaiwiseProcessor.GetParameterValue(E2, Contract) is string s))
627 {
628 Log.Error("Rejected marketplace contract: Invalid currency.", Contract.ContractId);
629 return null;
630 }
631
632 Currency = s;
633 break;
634 }
635 }
636 break;
637
638 case "Score":
639 foreach (XmlNode N2 in E.ChildNodes)
640 {
641 if (!(N2 is XmlElement E2))
642 continue;
643
644 switch (E2.LocalName)
645 {
646 case "ExpectsTag":
647 if (ExpectedTags is null)
648 ExpectedTags = new List<ExpectedTag>();
649
650 string Name = E2.InnerText;
651 string Where = null;
652
653 if (E2.HasAttribute("where"))
654 {
655 Where = E2.GetAttribute("where");
656 if (!IsValidExpression(Where, out _))
657 {
658 Log.Error("Rejected marketplace contract: Invalid where expression.", Contract.ContractId);
659 return null;
660 }
661 }
662
663 ExpectedTags.Add(new ExpectedTag()
664 {
665 Name = Name,
666 Where = Where
667 });
668 break;
669
670 case "ScoreFunction":
671 ScoreFunction = E2.InnerText;
672 if (!IsValidExpression(ScoreFunction, out _))
673 {
674 Log.Error("Rejected marketplace contract: Invalid scoring function.", Contract.ContractId);
675 return null;
676 }
677 break;
678
679 case "RejectScore":
680 if (!(await PaiwiseProcessor.GetParameterValue(E2, Contract) is decimal d))
681 {
682 Log.Error("Rejected marketplace contract: Invalid rejection score.", Contract.ContractId);
683 return null;
684 }
685
686 RejectScore = d;
687 break;
688
689 case "AcceptScore":
690 if (!(await PaiwiseProcessor.GetParameterValue(E2, Contract) is decimal d2))
691 {
692 Log.Error("Rejected marketplace contract: Invalid accept score.", Contract.ContractId);
693 return null;
694 }
695
696 AcceptScore = d2;
697 break;
698 }
699 }
700 break;
701
702 case "AvailableDays":
703 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is decimal d5) || d5 <= 0)
704 {
705 Log.Error("Rejected marketplace contract: Invalid number of available days.", Contract.ContractId);
706 return null;
707 }
708
709 AvailableDays = d5;
710 break;
711 }
712 }
713
714 if (Type is null || Class is null || Currency is null || !AskingPrice.HasValue || !CommissionPercent.HasValue || !AvailableDays.HasValue)
715 {
716 Log.Error("Rejected marketplace contract: Incomplete.", Contract.ContractId);
717 return null;
718 }
719
720 if (CommissionPercent.Value < await GetMinCommission())
721 {
722 Log.Error("Rejected marketplace contract: Commission too low.", Contract.ContractId);
723 return null;
724 }
725
727 {
728 if (string.IsNullOrEmpty(TokenId))
729 {
730 Log.Error("Rejected marketplace contract: TokenID Tag expected.", Contract.ContractId);
731 return null;
732 }
733
734 Token Token = await NeuroFeaturesProcessor.GetToken(TokenId, true);
735 if (Token is null)
736 {
737 // TODO: Allow remote tokens in marketplace.
738
739 Log.Error("Rejected marketplace contract: Token not hosted by Trust Provider.", Contract.ContractId);
740 return null;
741 }
742
743 if (Token.OwnerJid != PaymentJid)
744 {
745 Log.Error("Rejected marketplace contract: Not the current owner of the token.", Contract.ContractId);
746 return null;
747 }
748
749 if (Token.Expires.ToUniversalTime() < DateTime.UtcNow)
750 {
751 Log.Error("Rejected marketplace contract: Token has expired.", Contract.ContractId);
752 return null;
753 }
754
755 if (Token.Expires.ToUniversalTime() < DateTime.UtcNow.AddDays((double)AvailableDays.Value))
756 {
757 Log.Error("Rejected marketplace contract: Token will expire during the auction.", Contract.ContractId);
758 return null;
759 }
760
761 AuctionItem Item2 = await Database.FindFirstIgnoreRest<AuctionItem>(new FilterAnd(
762 new FilterFieldEqualTo("TokenId", TokenId),
763 new FilterFieldEqualTo("Processed", null)));
764
765 if (!(Item2 is null))
766 {
767 Log.Error("Rejected marketplace contract: Another auction for the token is already in process.", Contract.ContractId);
768 return null;
769 }
770 }
771
772 DateTime Created = DateTime.UtcNow;
773
774 return new AuctionItem()
775 {
776 ContractId = Contract.ContractId,
777 InitiatorLegalId = PaymentLegalId,
778 InitiatorJid = PaymentJid,
780 Expires = Created.AddDays((double)AvailableDays.Value),
781 Class = Class,
782 Type = Type,
783 TokenId = TokenId,
784 Node = Node,
785 Tags = Tags.ToArray(),
786 ExpectedTags = ExpectedTags?.ToArray(),
787 AskingPrice = AskingPrice.Value,
788 AcceptPrice = AcceptPrice.Value,
789 RejectPrice = RejectPrice.Value,
790 CommissionPercent = CommissionPercent.Value,
791 Currency = Currency,
792 ScoreFunction = ScoreFunction,
793 AcceptScore = AcceptScore,
794 RejectScore = RejectScore,
795 };
796 }
797
798 internal static async Task<decimal> GetMinCommission()
799 {
800 if (!minCommission.HasValue)
801 minCommission = (decimal)await RuntimeSettings.GetAsync("Commission.Min", 10.0);
802
803 return (decimal)minCommission.Value;
804 }
805
806 private static bool IsValidExpression(string Script, out ScriptNode Prohibited)
807 {
808 if (string.IsNullOrEmpty(Script))
809 {
810 Prohibited = null;
811 return false;
812 }
813
814 try
815 {
816 Expression Exp = new Expression(Script);
817 return XmppServer.CheckExpressionSafe(Exp, true, true, false, out Prohibited);
818 }
819 catch (Exception)
820 {
821 Prohibited = null;
822 return false;
823 }
824 }
825
826 private static async Task<bool> AddOffer(XmlElement Offer, Contract Contract, bool ContractIsLocked, LegalComponent Legal, EDalerComponent EDaler,
827 PubSubComponent PubSub)
828 {
829 Variables v = new Variables();
830 List<AuctionItemTag> Tags = new List<AuctionItemTag>();
831 CaseInsensitiveString ItemReference = null;
832 string Currency = null;
833 decimal? Price = null;
834
835 foreach (XmlNode N in Offer.ChildNodes)
836 {
837 if (!(N is XmlElement E) || E.NamespaceURI != MarketplaceNamespace)
838 continue;
839
840 switch (E.LocalName)
841 {
842 case "Tag":
843 AuctionItemTag Tag = new AuctionItemTag()
844 {
845 Name = XML.Attribute(E, "name"),
846 Value = await PaiwiseProcessor.GetParameterValue(E, Contract)
847 };
848
849 Tags.Add(Tag);
850 v[Tag.Name] = Tag.Value;
851 break;
852
853 case "Price":
854 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is decimal d))
855 {
856 Log.Error("Rejected marketplace offer: Invalid price.", Contract.ContractId);
857 return false;
858 }
859
860 Price = d;
861 break;
862
863 case "Currency":
864 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is string s))
865 {
866 Log.Error("Rejected marketplace offer: Invalid currency.", Contract.ContractId);
867 return false;
868 }
869
870 Currency = s;
871 break;
872
873 case "ItemReference":
874 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is string s2))
875 {
876 Log.Error("Rejected marketplace offer: Invalid item reference.", Contract.ContractId);
877 return false;
878 }
879
880 int i = s2.IndexOf('@');
881 if (i < 0 || !Guid.TryParse(s2.Substring(0, i), out Guid _))
882 {
883 Log.Error("Rejected marketplace offer: Invalid item reference.", Contract.ContractId);
884 return false;
885 }
886
887 ItemReference = s2;
888
889 s2 = s2.Substring(i + 1);
890 if (!Legal.IsComponentDomain(s2, true))
891 {
892 // TODO: If contract reference on other domain, send proposal message to that neuron
893 return false;
894 }
895 break;
896 }
897 }
898
899 if (ItemReference is null || Currency is null || !Price.HasValue)
900 {
901 Log.Error("Rejected marketplace offer: Incomplete.", Contract.ContractId);
902 return false;
903 }
904
905 AuctionItem Item;
906
907 lock (activeItems)
908 {
909 if (!activeItems.TryGetValue(ItemReference, out Item))
910 Item = null;
911 }
912
913 if (Item is null)
914 {
915 Log.Error("Rejected marketplace offer: Not active.", ItemReference, Contract.ContractId);
916 return false;
917 }
918
919 if (!(Item.Tags is null))
920 {
921 foreach (AuctionItemTag Tag in Item.Tags)
922 {
923 if (!v.ContainsVariable(Tag.Name))
924 v[Tag.Name] = Tag.Value;
925 }
926 }
927
928 v["Price"] = Price.Value;
929
930 if (!(Item.ExpectedTags is null))
931 {
932 foreach (ExpectedTag ExpectedTag in Item.ExpectedTags)
933 {
934 if (!v.ContainsVariable(ExpectedTag.Name))
935 {
936 Log.Error("Rejected marketplace offer: Tag '" + ExpectedTag
937 + "' used for scoring offers is missing.", ItemReference, Contract.ContractId);
938 return false;
939 }
940
941 if (!string.IsNullOrEmpty(ExpectedTag.Where))
942 {
943 try
944 {
945 object Result = await ExpectedTag.Parsed.EvaluateAsync(v);
946
947 if (Result is bool b)
948 {
949 if (!b)
950 {
951 Log.Error("Rejected marketplace offer: Tag '" + ExpectedTag
952 + "' not valid in accordance with where expression.", ItemReference, Contract.ContractId);
953 return false;
954 }
955 }
956 else
957 {
958 Log.Error("Rejected marketplace offer: Where expression for '" + ExpectedTag
959 + "' did not return a boolean value.", ItemReference, Contract.ContractId);
960 return false;
961 }
962 }
963 catch (Exception ex)
964 {
965 Log.Error("Rejected marketplace offer: Where expression for '" + ExpectedTag
966 + "' returned an exception: " + ex.Message, ItemReference, Contract.ContractId);
967 return false;
968 }
969 }
970 }
971 }
972
973 bool Accept = false;
974 bool NewBest = false;
975 decimal? Score = null;
976 string BestBidContractIdBak = Item.BestBidContractId;
977 string BestBidLegalIdBak = Item.BestBidLegalId;
978 string BestBidLegalJidBak = Item.BestBidJid;
979 decimal? BestBidPriceBak = Item.BestBidPrice;
980 decimal? BestBidScoreBak = Item.BestBidScore;
981 string PaymentLegalId;
982 string PaymentJid;
983
984 switch (Item.Direction)
985 {
986 case OptimizeDirection.Up: // Initiator sells. Offerers buy.
987 if (!OnlyMissingAuctioneer(Contract, BuyerRole, AuctioneerRole, out PaymentLegalId, out PaymentJid))
988 {
989 Log.Error("Rejected marketplace offer: No buyer defined.", ItemReference, Contract.ContractId);
990 return false;
991 }
992 break;
993
994 case OptimizeDirection.Down: // Initiator buys. Offerers sell.
995 if (!OnlyMissingAuctioneer(Contract, SellerRole, AuctioneerRole, out PaymentLegalId, out PaymentJid))
996 {
997 Log.Error("Rejected marketplace offer: No seller defined.", ItemReference, Contract.ContractId);
998 return false;
999 }
1000 break;
1001
1002 default:
1003 return false;
1004 }
1005
1006 using (Semaphore Lock = await Semaphores.BeginWrite(Item.ObjectId.ToString()))
1007 {
1008 if (Item.Processed.HasValue)
1009 {
1010 Log.Error("Rejected marketplace offer: Not active.", ItemReference, Contract.ContractId);
1011 return false;
1012 }
1013
1014 if (Currency != Item.Currency)
1015 {
1016 Log.Error("Rejected marketplace offer: Currency mismatch.", ItemReference, Contract.ContractId);
1017 return false;
1018 }
1019
1020 if (Item.RejectPrice.HasValue)
1021 {
1022 switch (Item.Direction)
1023 {
1024 case OptimizeDirection.Up:
1025 if (Price.Value < Item.RejectPrice.Value)
1026 {
1027 Log.Notice("Rejected marketplace offer: Below rejection price threshold.", ItemReference, Contract.ContractId);
1028 return false;
1029 }
1030 break;
1031
1032 case OptimizeDirection.Down:
1033 if (Price.Value > Item.RejectPrice.Value)
1034 {
1035 Log.Notice("Rejected marketplace offer: Above rejection price threshold.", ItemReference, Contract.ContractId);
1036 return false;
1037 }
1038 break;
1039 }
1040 }
1041
1042 if (!(Item.ScoreFunction is null))
1043 {
1044 try
1045 {
1046 if (Item.ScoreExpression is null)
1047 {
1048 Expression Exp = new Expression(Item.ScoreFunction);
1049
1050 if (!XmppServer.CheckExpressionSafe(Exp, true, true, false, out ScriptNode Prohibited))
1051 throw new UnauthorizedAccessException("Expression not permitted: " + Prohibited?.SubExpression);
1052
1053 Item.ScoreExpression = Exp;
1054 }
1055
1056 object Result = await Item.ScoreExpression.EvaluateAsync(v);
1057 Score = Expression.ToDecimal(Result);
1058 }
1059 catch (Exception ex)
1060 {
1061 Log.Notice("Rejected marketplace offer: Scoring error: " + ex.Message, ItemReference, Contract.ContractId);
1062 return false;
1063 }
1064
1065 if (Item.RejectScore.HasValue)
1066 {
1067 switch (Item.Direction)
1068 {
1069 case OptimizeDirection.Up:
1070 if (Score.Value < Item.RejectScore.Value)
1071 {
1072 Log.Notice("Rejected marketplace offer: Below rejection score threshold.", ItemReference, Contract.ContractId);
1073 return false;
1074 }
1075 break;
1076
1077 case OptimizeDirection.Down:
1078 if (Score.Value > Item.RejectScore.Value)
1079 {
1080 Log.Notice("Rejected marketplace offer: Above rejection score threshold.", ItemReference, Contract.ContractId);
1081 return false;
1082 }
1083 break;
1084 }
1085 }
1086
1087 if (Item.AcceptScore.HasValue)
1088 {
1089 switch (Item.Direction)
1090 {
1091 case OptimizeDirection.Up:
1092 if (Score.Value >= Item.AcceptScore.Value)
1093 {
1094 Accept = NewBest = true;
1095 Item.Processed = DateTime.UtcNow;
1096 }
1097 break;
1098
1099 case OptimizeDirection.Down:
1100 if (Score.Value <= Item.AcceptScore.Value)
1101 {
1102 Accept = NewBest = true;
1103 Item.Processed = DateTime.UtcNow;
1104 }
1105 break;
1106 }
1107 }
1108 }
1109
1110 if (Item.AcceptPrice.HasValue && !Accept)
1111 {
1112 switch (Item.Direction)
1113 {
1114 case OptimizeDirection.Up:
1115 if (Price.Value >= Item.AcceptPrice.Value)
1116 {
1117 Accept = NewBest = true;
1118 Item.Processed = DateTime.UtcNow;
1119 }
1120 break;
1121
1122 case OptimizeDirection.Down:
1123 if (Price.Value <= Item.AcceptPrice.Value)
1124 {
1125 Accept = NewBest = true;
1126 Item.Processed = DateTime.UtcNow;
1127 }
1128 break;
1129 }
1130 }
1131
1132 if (!NewBest)
1133 {
1134 if (Score.HasValue)
1135 {
1136 switch (Item.Direction)
1137 {
1138 case OptimizeDirection.Up:
1139 if (!Item.BestBidScore.HasValue || Score.Value > Item.BestBidScore.Value)
1140 NewBest = true;
1141 break;
1142
1143 case OptimizeDirection.Down:
1144 if (!Item.BestBidScore.HasValue || Score.Value < Item.BestBidScore.Value)
1145 NewBest = true;
1146 break;
1147 }
1148 }
1149 else
1150 {
1151 switch (Item.Direction)
1152 {
1153 case OptimizeDirection.Up:
1154 if (!Item.BestBidPrice.HasValue || Price.Value > Item.BestBidPrice.Value)
1155 NewBest = true;
1156 break;
1157
1158 case OptimizeDirection.Down:
1159 if (!Item.BestBidPrice.HasValue || Price.Value < Item.BestBidPrice.Value)
1160 NewBest = true;
1161 break;
1162 }
1163 }
1164 }
1165
1166 if (NewBest)
1167 {
1168 Item.BestBidContractId = Contract.ContractId;
1169 Item.BestBidLegalId = PaymentLegalId;
1170 Item.BestBidJid = PaymentJid;
1171 Item.BestBidPrice = Price;
1172 Item.BestBidScore = Score;
1173 }
1174 }
1175
1176 if (await SignContract(Contract, ContractIsLocked, Legal, PubSub, Item.Node, AuctioneerRole) is null)
1177 {
1178 if (Accept)
1179 Item.Processed = null;
1180
1181 if (NewBest)
1182 {
1183 Item.BestBidContractId = BestBidContractIdBak;
1184 Item.BestBidLegalId = BestBidLegalIdBak;
1185 Item.BestBidJid = BestBidLegalJidBak;
1186 Item.BestBidPrice = BestBidPriceBak;
1187 Item.BestBidScore = BestBidScoreBak;
1188 }
1189
1190 return false;
1191 }
1192
1193 if (!Accept && NewBest)
1194 await Database.Update(Item);
1195
1196 await Database.Insert(new AuctionOffer()
1197 {
1198 OfferContractId = Contract.ContractId,
1199 ContractId = ItemReference,
1200 OfferLegalId = PaymentLegalId,
1201 OfferJid = PaymentJid,
1202 Created = DateTime.UtcNow,
1203 Price = Price.Value,
1204 Currency = Currency,
1205 Score = Score ?? Price,
1206 Tags = Tags.ToArray()
1207 });
1208
1209 if (Accept)
1210 {
1211 if (await ResolvePayments(Item, EDaler, false)) // Will update Item in the database
1212 {
1213 lock (activeItems)
1214 {
1215 activeItems.Remove(Item.ContractId);
1216 }
1217
1218 Scheduler.Remove(Item.ScheduledExpiry);
1219 Item.ScheduledExpiry = DateTime.MinValue;
1220 }
1221 else
1222 {
1223 Item.Processed = null;
1224 Item.BestBidContractId = BestBidContractIdBak;
1225 Item.BestBidLegalId = BestBidLegalIdBak;
1226 Item.BestBidJid = BestBidLegalJidBak;
1227 Item.BestBidPrice = BestBidPriceBak;
1228 Item.BestBidScore = BestBidScoreBak;
1229
1230 await Database.Update(Item);
1231 }
1232 }
1233
1234 return true;
1235 }
1236
1237 private static async Task ItemExpires(object P)
1238 {
1239 ExpiryRecord Rec = (ExpiryRecord)P;
1240 AuctionItem Item = Rec.Item;
1241 if (Item.Processed.HasValue)
1242 return;
1243
1244 lock (activeItems)
1245 {
1246 activeItems.Remove(Item.ContractId);
1247 }
1248
1249 Item.Processed = DateTime.UtcNow;
1250
1251 if (!await ResolvePayments(Item, Rec.EDaler, true)) // Will update Item in the database
1252 Log.Error("Auctioned item expired, but payments offered were not possible to process.", Item.ContractId);
1253 }
1254
1255 private static async Task<bool> ResolvePayments(AuctionItem Item, EDalerComponent EDaler,
1256 bool BackTrackIfPaymentFails)
1257 {
1258 bool Ok = true;
1259
1260 if (Item.BestBidPrice.HasValue)
1261 {
1262 CaseInsensitiveString BidderId = Item.BestBidLegalId;
1263 CaseInsensitiveString BidderJid = Item.BestBidJid;
1264 CaseInsensitiveString BidId = Item.BestBidContractId;
1265 decimal Amount = Item.BestBidPrice.Value;
1266
1267 if (!(Ok = await ResolvePayments(BidderId, BidderJid, Amount, BidId, Item, EDaler)))
1268 {
1269 if (BackTrackIfPaymentFails)
1270 {
1271 string Order = Item.Direction == OptimizeDirection.Up ? "-Score" : "Score";
1272 IEnumerable<AuctionOffer> Bids = await Database.Find<AuctionOffer>(
1273 new FilterAnd(
1274 new FilterFieldEqualTo("ContractId", Item.ContractId),
1275 new FilterFieldNotEqualTo("OfferContractId", Item.BestBidContractId)),
1276 Order);
1277
1278 foreach (AuctionOffer Bid in Bids)
1279 {
1280 BidderId = Bid.OfferLegalId;
1281 BidderJid = Bid.OfferJid;
1282 BidId = Bid.OfferContractId;
1283 Amount = Bid.Price;
1284
1285 if (Ok = await ResolvePayments(BidderId, BidderJid, Amount, BidId, Item, EDaler))
1286 break;
1287 }
1288 }
1289 }
1290 }
1291
1292 await Database.Update(Item);
1293
1294 return Ok;
1295 }
1296
1297 private static async Task<bool> ResolvePayments(CaseInsensitiveString Bidder,
1298 CaseInsensitiveString BidderJid, decimal Amount, CaseInsensitiveString BidId,
1299 AuctionItem Item, EDalerComponent EDaler)
1300 {
1301 decimal Commission = Amount * Item.CommissionPercent * 0.01m;
1302 string Ref = "iotsc:" + Item.ContractId;
1303 Token Token;
1304 int i = Item.ContractId.IndexOf('@');
1305
1306 if (i <= 0 || !Guid.TryParse(Item.ContractId.Substring(0, i), out Guid TransactionId))
1307 TransactionId = Guid.NewGuid();
1308
1310 {
1311 string TokenId = Item.TokenId;
1312 if (string.IsNullOrEmpty(TokenId))
1313 return false;
1314
1315 Token = await NeuroFeaturesProcessor.GetToken(TokenId, true);
1316 if (Token is null)
1317 return false;
1318
1319 // TODO: Allow remote tokens in marketplace.
1320 }
1321 else
1322 Token = null;
1323
1324 switch (Item.Direction)
1325 {
1326 case OptimizeDirection.Up: // Creator of item sells item to generator of best offer
1327
1328 if (!(Token is null) && Item.InitiatorJid != Token.OwnerJid)
1329 return false;
1330
1331 Item.SalePaymentUri = PaiwiseProcessor.GenerateContractualPaymentUri(TransactionId, Bidder, true,
1332 Gateway.ContractsClient.Client.BareJID, false, Item.Currency, Amount, null, Ref,
1333 BidId, null, 1, out _, out _);
1334
1335 Item.CommissionPaymentUri = PaiwiseProcessor.GenerateContractualPaymentUri(Guid.NewGuid(),
1336 Gateway.ContractsClient.Client.BareJID, false, Item.InitiatorLegalId, true, Item.Currency,
1337 Amount - Commission, null, Ref, Item.ContractId, null, 1, out _, out _);
1338
1339 break;
1340
1341 case OptimizeDirection.Down: // Generator of best offer sells item to generator of item
1342
1343 if (!(Token is null) && BidderJid != Token.OwnerJid)
1344 return false;
1345
1346 Item.SalePaymentUri = PaiwiseProcessor.GenerateContractualPaymentUri(TransactionId, Item.InitiatorLegalId, true,
1347 Gateway.ContractsClient.Client.BareJID, false, Item.Currency, Amount + Commission,
1348 null, Ref, Item.ContractId, null, 1, out _, out _);
1349
1350 Item.CommissionPaymentUri = PaiwiseProcessor.GenerateContractualPaymentUri(Guid.NewGuid(),
1351 Gateway.ContractsClient.Client.BareJID, false, Bidder, true, Item.Currency, Amount, null,
1352 Ref, Item.ContractId, null, 1, out _, out _);
1353
1354 break;
1355
1356 default:
1357 return false;
1358 }
1359
1360 if (!string.IsNullOrEmpty(await PaiwiseProcessor.ProcessPayment(Item.SalePaymentUri, EDaler)))
1361 {
1362 Log.Error("Unable to process payment for consigned auction item.",
1363 Item.ContractId, string.Empty, "AuctionPayment",
1364 new KeyValuePair<string, object>("Bidder", Bidder),
1365 new KeyValuePair<string, object>("BidId", BidId),
1366 new KeyValuePair<string, object>("Amount", Amount),
1367 new KeyValuePair<string, object>("Currency", Item.Currency));
1368
1369 return false;
1370 }
1371
1372 if (Commission > 0)
1373 {
1374 string Msg = await PaiwiseProcessor.ProcessPayment(Item.CommissionPaymentUri, EDaler); // Funds covered by first transaction
1375 if (!string.IsNullOrEmpty(Msg))
1376 {
1377 Log.Error("Auction payment error: Sales payment went through to Auctioneer, but secondary payment failed: " + Msg,
1378 Item.ContractId, string.Empty, "AuctionPayment",
1379 new KeyValuePair<string, object>("Bidder", Bidder),
1380 new KeyValuePair<string, object>("BidId", BidId),
1381 new KeyValuePair<string, object>("Amount", Amount),
1382 new KeyValuePair<string, object>("Currency", Item.Currency),
1383 new KeyValuePair<string, object>("URI", Item.CommissionPaymentUri));
1384 }
1385 }
1386
1387 if (!(Token is null))
1388 {
1389 Transferred TransferToAuctioneer;
1390 Transferred TransferToBuyer;
1391 string SellerJid;
1392 string BuyerJid;
1393
1394 switch (Item.Direction)
1395 {
1396 case OptimizeDirection.Up: // Creator of item sells item to generator of best offer
1397
1398 SellerJid = Item.InitiatorJid;
1399 BuyerJid = BidderJid;
1400
1401 Token.Value = Amount;
1402 Token.Currency = Item.Currency;
1403 Token.Owner = Bidder;
1404 Token.OwnerJid = BidderJid;
1405 Token.OwnershipContract = BidId;
1406 Token.Sign();
1407
1408 TransferToAuctioneer = new Transferred()
1409 {
1410 ArchiveOptional = Token.ArchiveOptional,
1411 ArchiveRequired = Token.ArchiveRequired,
1412 Expires = Token.Expires,
1413 Seller = Item.InitiatorLegalId,
1414 Owner = Item.TrustProviderLegalId,
1415 OwnershipContract = Item.ContractId,
1416 Value = Amount,
1417 Commission = 0,
1418 Currency = Item.Currency,
1419 TokenId = Token.TokenId,
1420 Timestamp = DateTime.UtcNow.AddSeconds(-1),
1421 Personal = false
1422 };
1423
1424 TransferToBuyer = new Transferred()
1425 {
1426 ArchiveOptional = Token.ArchiveOptional,
1427 ArchiveRequired = Token.ArchiveRequired,
1428 Expires = Token.Expires,
1429 Seller = Item.TrustProviderLegalId,
1430 Owner = Bidder,
1431 OwnershipContract = BidId,
1432 Value = Amount,
1433 Commission = Commission,
1434 Currency = Item.Currency,
1435 TokenId = Token.TokenId,
1436 Timestamp = DateTime.UtcNow,
1437 Personal = false
1438 };
1439 break;
1440
1441 case OptimizeDirection.Down: // Generator of best offer sells item to generator of item
1442
1443 SellerJid = BidderJid;
1444 BuyerJid = Item.InitiatorJid;
1445
1446 Token.Value = Amount;
1447 Token.Currency = Item.Currency;
1448 Token.Owner = Item.InitiatorLegalId;
1449 Token.OwnerJid = Item.InitiatorJid;
1450 Token.OwnershipContract = Item.ContractId;
1451 Token.Sign();
1452
1453 TransferToAuctioneer = new Transferred()
1454 {
1455 ArchiveOptional = Token.ArchiveOptional,
1456 ArchiveRequired = Token.ArchiveRequired,
1457 Expires = Token.Expires,
1458 Seller = Bidder,
1459 Owner = Item.TrustProviderLegalId,
1460 OwnershipContract = BidId,
1461 Value = Amount,
1462 Commission = 0,
1463 Currency = Item.Currency,
1464 TokenId = Token.TokenId,
1465 Timestamp = DateTime.UtcNow.AddSeconds(-1),
1466 Personal = false
1467 };
1468
1469 TransferToBuyer = new Transferred()
1470 {
1471 ArchiveOptional = Token.ArchiveOptional,
1472 ArchiveRequired = Token.ArchiveRequired,
1473 Expires = Token.Expires,
1474 Seller = Item.TrustProviderLegalId,
1475 Owner = Item.InitiatorLegalId,
1476 OwnershipContract = Item.ContractId,
1477 Value = Amount,
1478 Commission = Commission,
1479 Currency = Item.Currency,
1480 TokenId = Token.TokenId,
1481 Timestamp = DateTime.UtcNow,
1482 Personal = false
1483 };
1484
1485 break;
1486
1487 default:
1488 return false;
1489 }
1490
1491 await Database.StartBulk();
1492 try
1493 {
1494 await NeuroFeaturesProcessor.DeletePersonalEvents(new string[] { Token.TokenId });
1495 await Database.Update(Token);
1496
1497 try
1498 {
1499 await Database.Insert(TransferToAuctioneer, TransferToBuyer);
1500 }
1501 catch (Exception ex)
1502 {
1503 Log.Exception(ex);
1504 }
1505
1506 // TODO: Protect integrity with transaction, commit, rollback, etc.
1507 }
1508 finally
1509 {
1510 await Database.EndBulk();
1511 }
1512
1513 try
1514 {
1515 await StateMachineProcessor.EventGenerated(Token, TransferToAuctioneer);
1516 }
1517 catch (Exception ex)
1518 {
1520 }
1521
1522 try
1523 {
1524 await StateMachineProcessor.EventGenerated(Token, TransferToBuyer);
1525 }
1526 catch (Exception ex)
1527 {
1529 }
1530
1531 StringBuilder Xml = new StringBuilder();
1532
1533 Xml.Append("<tokenRemoved xmlns='");
1535 Xml.Append("'>");
1536 Token.Serialize(Xml, false, true);
1537 Xml.Append("</tokenRemoved>");
1538
1539 if (await NeuroFeaturesProcessor.SendTokenMessage(EDaler, Xml.ToString(), SellerJid, true, true))
1540 await NeuroFeaturesProcessor.TokenRemoved(Token, SellerJid);
1541
1542 Xml.Clear();
1543
1544 Xml.Append("<tokenAdded xmlns='");
1546 Xml.Append("'>");
1547 Token.Serialize(Xml, false, true);
1548 Xml.Append("</tokenAdded>");
1549
1550 if (await NeuroFeaturesProcessor.SendTokenMessage(EDaler, Xml.ToString(), BuyerJid, true, true))
1551 await NeuroFeaturesProcessor.TokenAdded(Token, BuyerJid);
1552 }
1553
1554 return true;
1555 }
1556
1557 }
1558}
Helps with common XML-related tasks.
Definition: XML.cs:19
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:914
static XmlException AnnotateException(XmlException ex)
Creates a new XML Exception object, with reference to the source XML file, for information.
Definition: XML.cs:1588
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
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:126
static ContractsClient ContractsClient
XMPP Contracts Client, if such a compoent is available on the XMPP broker.
Definition: Gateway.cs:4375
Contains the definition of a contract
Definition: Contract.cs:22
static Task< ParsedContract > Parse(XmlDocument Xml)
Validates a contract XML Document, and returns the contract definition in it.
Definition: Contract.cs:397
ClientSignature[] ClientSignatures
Client signatures of the contract.
Definition: Contract.cs:309
void Serialize(StringBuilder Xml, bool IncludeNamespace, bool IncludeIdAttribute, bool IncludeClientSignatures, bool IncludeAttachments, bool IncludeStatus, bool IncludeServerSignature, bool IncludeAttachmentReferences)
Serializes the Contract, in normalized form.
Definition: Contract.cs:1542
string DefaultLanguage
Default language for contract.
Definition: Contract.cs:1884
Contains information about a parsed contract.
CaseInsensitiveString Subdomain
Subdomain name.
Definition: Component.cs:76
XmppAddress MainDomain
Main/principal domain address
Definition: Component.cs:86
bool IsComponentDomain(CaseInsensitiveString Domain, bool IncludeAlternativeDomains)
Checks if a domain is the component domain, or optionally, an alternative component domain.
Definition: Component.cs:123
Contains information about one XMPP address.
Definition: XmppAddress.cs:9
CaseInsensitiveString Address
XMPP Address
Definition: XmppAddress.cs:37
static bool CheckExpressionSafe(Expression Expression, out ScriptNode Prohibited)
Checks if an expression is safe to execute (if it comes from an external source).
Definition: XmppServer.cs:6733
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
string LowerCase
Lower-case representation of the case-insensitive string.
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 Task EndBulk()
Ends bulk-processing of data. Must be called once for every call to StartBulk.
Definition: Database.cs:1494
static Task StartBulk()
Starts bulk-proccessing of data. Must be followed by a call to EndBulk.
Definition: Database.cs:1486
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 Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:247
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:95
This filter selects objects that conform to all child-filters provided.
Definition: FilterAnd.cs:10
This filter selects objects that have a named field equal to a given value.
This filter selects objects that have a named field not equal to a given value.
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:14
static bool TryGetModuleParameter(string Name, out object Value)
Tries to get a module parameter value.
Definition: Types.cs:583
Static class managing persistent settings.
static async Task< string > GetAsync(string Key, string DefaultValue)
Gets a string-valued setting.
Represents a named semaphore, i.e. an object, identified by a name, that allows single concurrent wri...
Definition: Semaphore.cs:19
async void Dispose()
Disposes of the named semaphore, and releases any locks the object manages.
Definition: Semaphore.cs:161
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
Class that can be used to schedule events in time. It uses a timer to execute tasks at the appointed ...
Definition: Scheduler.cs:26
bool Remove(DateTime When)
Removes an event scheduled for a given point in time.
Definition: Scheduler.cs:182
void Dispose()
IDisposable.Dispose
Definition: Scheduler.cs:46
DateTime Add(DateTime When, ScheduledEventCallback Callback, object State)
Adds an event.
Definition: Scheduler.cs:66
Class managing a script expression.
Definition: Expression.cs:39
async Task< object > EvaluateAsync(Variables Variables)
Evaluates the expression, using the variables provided in the Variables collection....
Definition: Expression.cs:4275
static decimal ToDecimal(object Object)
Converts an object to a double value.
Definition: Expression.cs:4883
Base class for all nodes in a parsed script tree.
Definition: ScriptNode.cs:69
Collection of variables.
Definition: Variables.cs:25
virtual bool ContainsVariable(string Name)
If the collection contains a variable with a given name.
Definition: Variables.cs:76
Manages eDaler on accounts connected to the broker.
Marketplace processor, brokering sales of items via tenders and offers defined in smart contracts.
const string MarketplaceNamespace
https://paiwise.tagroot.io/Schema/Marketplace.xsd
Event raised when a token has been created.
Definition: Created.cs:10
Event raised when a token has been transferred.
Definition: Transferred.cs:11
Marketplace processor, brokering sales of items via tenders and offers defined in smart contracts.
const string NeuroFeaturesNamespace
https://paiwise.tagroot.io/Schema/NeuroFeatures.xsd
Duration? ArchiveOptional
Duration after which token expires, and the required archiving time, the token can optionally be arch...
Definition: Token.cs:382
void Serialize(StringBuilder Xml, bool IncludeNamespace, bool IncludeServerSignature)
Serializes the Token, in normalized form.
Definition: Token.cs:609
CaseInsensitiveString OwnerJid
JID of Current owner of token
Definition: Token.cs:203
Duration? ArchiveRequired
Duration after which token expires, the token is required to be archived.
Definition: Token.cs:372
DateTime Expires
Expiry date of token.
Definition: Token.cs:362
CaseInsensitiveString TokenId
Token ID
Definition: Token.cs:139
Paiwise processor, processing payment instructions defined in smart contracts.
PubSub component, as defined in XEP-0060. https://xmpp.org/extensions/xep-0060.html
Task< string > PublishItem(string Service, string NodeName, string AutoCreateAccess, string From, string Domain, string ItemId, string XmlContent, string Language)
Call this method for automated publishing of pubsub items from hosted services.
OptimizeDirection
Direction in which to optimize prices and/or scores.
Definition: AuctionItem.cs:12
NodeAccessModel
Node access model.
Definition: Enumerations.cs:9