-
Notifications
You must be signed in to change notification settings - Fork 1
/
rns_server_blockchain.py
executable file
·2026 lines (1622 loc) · 81.8 KB
/
rns_server_blockchain.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
##############################################################################################################
#
# Copyright (c) 2024 Sebastian Obele / obele.eu
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# This software uses the following software-parts:
# Reticulum / Copyright (c) 2016-2022 Mark Qvist / unsigned.io / MIT License
#
##############################################################################################################
##############################################################################################################
# Include
#### System ####
import sys
import os
import time
import datetime
import argparse
#### UID ####
import uuid
#### Config ####
import configparser
#### Process ####
import signal
import threading
import subprocess
#### Reticulum ####
# Install: pip3 install rns
# Source: https://markqvist.github.io
import RNS
import RNS.vendor.umsgpack as msgpack
#### UID ####
import string, random
#### Token ####
import importlib
##############################################################################################################
# Globals
#### Global Variables - Configuration ####
NAME = "RNS Server Blockchain"
DESCRIPTION = "Gateway/Bridge for payment/wallet for RNS based apps"
VERSION = "0.0.1 (2024-10-07)"
COPYRIGHT = "(c) 2024 Sebastian Obele / obele.eu"
PATH = os.path.expanduser("~")+"/.config/"+os.path.splitext(os.path.basename(__file__))[0]
PATH_RNS = None
#### Global Variables - System (Not changeable) ####
CONFIG = None
RNS_CONNECTION = None
RNS_SERVER_BLOCKCHAIN = None
MSG_FIELD_EMBEDDED_LXMS = 0x01
MSG_FIELD_TELEMETRY = 0x02
MSG_FIELD_TELEMETRY_STREAM = 0x03
MSG_FIELD_ICON_APPEARANCE = 0x04
MSG_FIELD_FILE_ATTACHMENTS = 0x05
MSG_FIELD_IMAGE = 0x06
MSG_FIELD_AUDIO = 0x07
MSG_FIELD_THREAD = 0x08
MSG_FIELD_COMMANDS = 0x09
MSG_FIELD_RESULTS = 0x0A
MSG_FIELD_GROUP = 0x0B
MSG_FIELD_TICKET = 0x0C
MSG_FIELD_EVENT = 0x0D
MSG_FIELD_RNR_REFS = 0x0E
MSG_FIELD_RENDERER = 0x0F
MSG_FIELD_CUSTOM_TYPE = 0xFB
MSG_FIELD_CUSTOM_DATA = 0xFC
MSG_FIELD_CUSTOM_META = 0xFD
MSG_FIELD_NON_SPECIFIC = 0xFE
MSG_FIELD_DEBUG = 0xFF
MSG_FIELD_ANSWER = 0xA0
MSG_FIELD_ATTACHMENT = 0xA1
MSG_FIELD_COMMANDS_EXECUTE = 0xA2
MSG_FIELD_COMMANDS_RESULT = 0xA3
MSG_FIELD_CONTACT = 0xA4
MSG_FIELD_DATA = 0xA5
MSG_FIELD_DELETE = 0xA6
MSG_FIELD_EDIT = 0xA7
MSG_FIELD_GROUP = 0xA8
MSG_FIELD_HASH = 0xA9
MSG_FIELD_ICON = 0xC1
MSG_FIELD_ICON_MENU = 0xAA
MSG_FIELD_ICON_SRC = 0xAB
MSG_FIELD_KEYBOARD = 0xAC
MSG_FIELD_KEYBOARD_INLINE = 0xAD
MSG_FIELD_LOCATION = 0xAE
MSG_FIELD_OWNER = 0xC0
MSG_FIELD_POLL = 0xAF
MSG_FIELD_POLL_ANSWER = 0xB0
MSG_FIELD_REACTION = 0xB1
MSG_FIELD_RECEIPT = 0xB2
MSG_FIELD_SCHEDULED = 0xB3
MSG_FIELD_SILENT = 0xB4
MSG_FIELD_SRC = 0xB5
MSG_FIELD_STATE = 0xB6
MSG_FIELD_STICKER = 0xB7
MSG_FIELD_TELEMETRY_DB = 0xB8
MSG_FIELD_TELEMETRY_PEER = 0xB9
MSG_FIELD_TELEMETRY_COMMANDS = 0xBA
MSG_FIELD_TEMPLATE = 0xBB
MSG_FIELD_TOPIC = 0xBC
MSG_FIELD_TYPE = 0xBD
MSG_FIELD_TYPE_FIELDS = 0xBE
MSG_FIELD_VOICE = 0xBF
##############################################################################################################
# RateLimiter Class
class RateLimiter:
def __init__(self, calls, size, duration):
self.calls = calls
self.size = size
self.duration = duration
self.ts = time.time()
self.data_calls = {}
self.data_size = {}
self.lock = threading.Lock()
threading.Thread(target=self._jobs, daemon=True).start()
def handle(self, id):
if self.handle_call(id) and self.handle_size(id, 0):
return True
else:
return False
def handle_call(self, id):
with self.lock:
if self.calls == 0:
return True
if id not in self.data_calls:
self.data_calls[id] = []
self.data_calls[id] = [t for t in self.data_calls[id] if t > self.ts - self.duration]
if len(self.data_calls[id]) >= self.calls:
return False
else:
self.data_calls[id].append(self.ts)
return True
def handle_size(self, id, size):
with self.lock:
if self.size == 0:
return True
if id not in self.data_size:
self.data_size[id] = [0, self.ts]
if self.data_size[id][1] <= self.ts - self.duration:
self.data_size[id] = [0, self.ts]
if self.data_size[id][0] >= self.size:
return False
else:
self.data_size[id][0] += size
self.data_size[id][1] = self.ts
return True
def _jobs(self):
while True:
time.sleep(self.duration)
self.ts = time.time()
with self.lock:
if self.calls > 0:
for id in list(self.data_calls.keys()):
self.data_calls[id] = [t for t in self.data_calls[id] if t > self.ts - self.duration]
if not self.data_calls[id]:
del self.data_calls[id]
if self.size > 0:
for id in list(self.data_size.keys()):
if self.data_size[id][1] <= self.ts - self.duration:
del self.data_size[id]
##############################################################################################################
# ServerBlockchain Class
class ServerBlockchain:
ACCOUNT_STATE_FAILED = 0x00 # Failed
ACCOUNT_STATE_SUCCESSFULL = 0x01 # Successfull
ACCOUNT_STATE_WAITING = 0x02 # Waiting in local cache
ACCOUNT_STATE_SYNCING = 0x03 # Syncing/Transfering to server
ACCOUNT_STATE_PROCESSING = 0x04 # Processing/Execution on the blockchain
ACCOUNT_STATE_FAILED_TMP = 0x05 # Temporary failed
CONNECTION_TIMEOUT = 10 # Seconds
DELEGATE_STATE_RESIGNED = 0x00
DELEGATE_STATE_ACTIVE = 0x01
DELEGATE_STATE_STANDBY = 0x02
JOBS_PERIODIC_DELAY = 10 # Seconds
JOBS_PERIODIC_INTERVAL = 60 # Seconds
KEY_RESULT = 0x0A # Result
KEY_RESULT_REASON = 0x0B # Result - Reason
KEY_A = 0x0C # Account
KEY_API = 0x0D # API
KEY_B = 0x0E # Block
KEY_D = 0x0F # Delegate
KEY_E = 0x10 # Explorer
KEY_I = 0x11 # Info
KEY_T = 0x12 # Transaction
KEY_A_BALANCE = 0x00
KEY_A_DATA = 0x01
KEY_A_DELEGATE = 0x02
KEY_A_ID = 0x03
KEY_A_KEY = 0x04
KEY_A_METADATA = 0x05
KEY_A_NAME = 0x06
KEY_A_NONCE = 0x07
KEY_A_STATE = 0x08
KEY_A_STATE_REASON = 0x09
KEY_A_TOKEN = 0x0A
KEY_A_TS = 0x0B
KEY_A_VOTE = 0x0C
KEY_A_MAPPING = {
"balance": KEY_A_BALANCE,
"data": KEY_A_DATA,
"delegate": KEY_A_DELEGATE,
"id": KEY_A_ID,
"key": KEY_A_KEY,
"name": KEY_A_NAME,
"nonce": KEY_A_NONCE,
"state": KEY_A_STATE,
"state_reason": KEY_A_STATE_REASON,
"token": KEY_A_TOKEN,
"ts": KEY_A_TS,
"vote": KEY_A_VOTE,
}
KEY_A_STATE_REASON_None = 0x00
KEY_A_STATE_REASON_WalletIndexAlreadyRegisteredError = 0x01
KEY_A_STATE_REASON_WalletIndexNotFoundError = 0x02
KEY_B_CONFIRMATIONS = 0x00
KEY_B_DATA = 0x01
KEY_B_FORGED_AMOUNT = 0x02
KEY_B_FORGED_FEE = 0x03
KEY_B_FORGED_REWARD = 0x04
KEY_B_FORGED_TOTAL = 0x05
KEY_B_GENERATOR_ID = 0x06
KEY_B_GENERATOR_NAME = 0x07
KEY_B_HEIGHT = 0x08
KEY_B_ID = 0x09
KEY_B_TRANSACTIONS = 0x0A
KEY_B_TS = 0x0B
KEY_B_MAPPING = {
"confirmations": KEY_B_CONFIRMATIONS,
"data": KEY_B_DATA,
"forged_amount": KEY_B_FORGED_AMOUNT,
"forged_fee": KEY_B_FORGED_FEE,
"forged_reward": KEY_B_FORGED_REWARD,
"forged_total": KEY_B_FORGED_TOTAL,
"generator_id": KEY_B_GENERATOR_ID,
"generator_name": KEY_B_GENERATOR_NAME,
"height": KEY_B_HEIGHT,
"id": KEY_B_ID,
"transactions": KEY_B_TRANSACTIONS,
"ts": KEY_B_TS,
}
KEY_D_BLOCK_ID = 0x00
KEY_D_BLOCK_TS = 0x01
KEY_D_DATA = 0x02
KEY_D_DATA_HASH = 0x03
KEY_D_FORGED_AMOUNT = 0x04
KEY_D_FORGED_BLOCKS = 0x05
KEY_D_FORGED_FEE = 0x06
KEY_D_FORGED_REWARD = 0x07
KEY_D_FORGED_TOTAL = 0x08
KEY_D_ID = 0x09
KEY_D_NAME = 0x0A
KEY_D_NAME_HASH = 0x0B
KEY_D_STATE = 0x0C
KEY_D_STATE_HASH = 0x0D
KEY_D_VALUE = 0x0E
KEY_D_VALUE_HASH = 0x0F
KEY_D_VOTES = 0x10
KEY_D_VOTES_PERCENT = 0x11
KEY_D_MAPPING = {
"block_id": KEY_D_BLOCK_ID,
"block_ts": KEY_D_BLOCK_TS,
"data": KEY_D_DATA,
"data_hash": KEY_D_DATA_HASH,
"forged_amount": KEY_D_FORGED_AMOUNT,
"forged_blocks": KEY_D_FORGED_BLOCKS,
"forged_fee": KEY_D_FORGED_FEE,
"forged_reward": KEY_D_FORGED_REWARD,
"forged_total": KEY_D_FORGED_TOTAL,
"id": KEY_D_ID,
"name": KEY_D_NAME,
"name_hash": KEY_D_NAME_HASH,
"state": KEY_D_STATE,
"state_hash": KEY_D_STATE_HASH,
"value": KEY_D_VALUE,
"value_hash": KEY_D_VALUE_HASH,
"votes": KEY_D_VOTES,
"votes_percent": KEY_D_VOTES_PERCENT,
}
KEY_E_FILTER = 0x00
KEY_E_HASH = 0x01
KEY_E_LIMIT = 0x02
KEY_E_LIMIT_START = 0x03
KEY_E_ORDER = 0x04
KEY_E_SEARCH = 0x05
KEY_E_TOKEN = 0x06
KEY_E_MAPPING = {
"filter": KEY_E_FILTER,
"hash": KEY_E_HASH,
"limit": KEY_E_LIMIT,
"limit_start": KEY_E_LIMIT_START,
"order": KEY_E_ORDER,
"search": KEY_E_SEARCH,
"token": KEY_E_TOKEN,
}
KEY_E_ORDER_ASC = 0x00
KEY_E_ORDER_DESC = 0x01
KEY_E_ORDER_MAPPING = {
"asc": KEY_E_ORDER_ASC,
"desc": KEY_E_ORDER_DESC,
}
KEY_I_DATA = 0x00
KEY_I_SUPPLY = 0x01
KEY_I_MAPPING = {
"account": KEY_A,
"block": KEY_B,
"delegate": KEY_D,
"transaction": KEY_T,
"data": KEY_I_DATA,
"supply": KEY_I_SUPPLY,
}
KEY_T_AMOUNT = 0x00
KEY_T_BLOCK_ID = 0x01
KEY_T_COMMENT = 0x02
KEY_T_CONFIRMATIONS = 0x03
KEY_T_DATA = 0x04
KEY_T_DEST = 0x05
KEY_T_DIRECTION = 0x06
KEY_T_FEE = 0x07
KEY_T_ID = 0x08
KEY_T_INDEX = 0x09
KEY_T_METADATA = 0x0A
KEY_T_NONCE = 0x0B
KEY_T_SOURCE = 0x0C
KEY_T_STATE = 0x0D
KEY_T_STATE_REASON = 0x0E
KEY_T_TS = 0x0F
KEY_T_TYPE = 0x10
KEY_T_MAPPING = {
"amount": KEY_T_AMOUNT,
"block_id": KEY_T_BLOCK_ID,
"comment": KEY_T_COMMENT,
"confirmations": KEY_T_CONFIRMATIONS,
"data": KEY_T_DATA,
"dest": KEY_T_DEST,
"direction": KEY_T_DIRECTION,
"fee": KEY_T_FEE,
"id": KEY_T_ID,
"index": KEY_T_INDEX,
"nonce": KEY_T_NONCE,
"source": KEY_T_SOURCE,
"state": KEY_T_STATE,
"state_reason": KEY_T_STATE_REASON,
"ts": KEY_T_TS,
"type": KEY_T_TYPE,
}
KEY_T_STATE_REASON_None = 0x00
KEY_T_STATE_REASON_AlreadyVotedError = 0x01
KEY_T_STATE_REASON_ColdWalletError = 0x02
KEY_T_STATE_REASON_DeactivatedTransactionHandlerError = 0x03
KEY_T_STATE_REASON_HtlcLockExpiredError = 0x04
KEY_T_STATE_REASON_HtlcLockNotExpiredError = 0x05
KEY_T_STATE_REASON_HtlcLockTransactionNotFoundError = 0x06
KEY_T_STATE_REASON_HtlcSecretHashMismatchError = 0x07
KEY_T_STATE_REASON_InsufficientBalanceError = 0x08
KEY_T_STATE_REASON_InvalidMultiSignatureError = 0x09
KEY_T_STATE_REASON_InvalidMultiSignaturesError = 0x0A
KEY_T_STATE_REASON_InvalidSecondSignatureError = 0x0B
KEY_T_STATE_REASON_InvalidTransactionTypeError = 0x0C
KEY_T_STATE_REASON_IpfsHashAlreadyExists = 0x0D
KEY_T_STATE_REASON_LegacyMultiSignatureError = 0x0E
KEY_T_STATE_REASON_LegacyMultiSignatureRegistrationError = 0x0F
KEY_T_STATE_REASON_MissingMultiSignatureOnSenderError = 0x10
KEY_T_STATE_REASON_MultiSignatureAlreadyRegisteredError = 0x11
KEY_T_STATE_REASON_MultiSignatureKeyCountMismatchError = 0x12
KEY_T_STATE_REASON_MultiSignatureMinimumKeysError = 0x13
KEY_T_STATE_REASON_NotEnoughDelegatesError = 0x14
KEY_T_STATE_REASON_NotImplementedError = 0x15
KEY_T_STATE_REASON_NotSupportedForMultiSignatureWalletError = 0x16
KEY_T_STATE_REASON_NoVoteError = 0x17
KEY_T_STATE_REASON_SecondSignatureAlreadyRegisteredError = 0x18
KEY_T_STATE_REASON_SenderWalletMismatchError = 0x19
KEY_T_STATE_REASON_UnexpectedNonceError = 0x1A
KEY_T_STATE_REASON_UnexpectedSecondSignatureError = 0x1B
KEY_T_STATE_REASON_UnsupportedMultiSignatureTransactionError = 0x1C
KEY_T_STATE_REASON_UnvoteMismatchError = 0x1D
KEY_T_STATE_REASON_VotedForNonDelegateError = 0x1E
KEY_T_STATE_REASON_VotedForResignedDelegateError = 0x1F
KEY_T_STATE_REASON_WalletAlreadyResignedError = 0x20
KEY_T_STATE_REASON_WalletIsAlreadyDelegateError = 0x21
KEY_T_STATE_REASON_WalletNotADelegateError = 0x22
KEY_T_STATE_REASON_WalletNoUsernameError = 0x23
KEY_T_STATE_REASON_WalletUsernameAlreadyRegisteredError = 0x24
RESULT_ERROR = 0x00
RESULT_OK = 0x01
RESULT_SYNCRONIZE = 0x02
RESULT_NO_IDENTITY = 0x03
RESULT_NO_USER = 0x04
RESULT_NO_RIGHT = 0x05
RESULT_NO_DATA = 0x06
RESULT_LIMIT_ALL = 0x07
RESULT_LIMIT_PEER = 0x08
RESULT_PARTIAL = 0x09
RESULT_DISABLED = 0xFE
RESULT_BLOCKED = 0xFF
STATE_NO_PATH = 0x00
STATE_PATH_REQUESTED = 0x01
STATE_ESTABLISHING_LINK = 0x02
STATE_LINK_TIMEOUT = 0x03
STATE_LINK_ESTABLISHED = 0x04
STATE_REQUESTING = 0x05
STATE_REQUEST_SENT = 0x06
STATE_REQUEST_FAILED = 0x07
STATE_REQUEST_TIMEOUT = 0x08
STATE_RECEIVING_RESPONSE = 0x09
STATE_TRANSFERRING = 0x0A
STATE_DISCONECTED = 0xFD
STATE_DONE = 0xFF
TRANSACTION_STATE_FAILED = 0x00 # Failed
TRANSACTION_STATE_SUCCESSFULL = 0x01 # Successfull
TRANSACTION_STATE_WAITING = 0x02 # Waiting in local cache
TRANSACTION_STATE_SYNCING = 0x03 # Syncing/Transfering to server
TRANSACTION_STATE_PROCESSING = 0x04 # Processing/Execution on the blockchain
TRANSACTION_STATE_FAILED_TMP = 0x05 # Temporary failed
TRANSACTION_TYPE_TRANSFER = 0x00
TRANSACTION_TYPE_SWAP = 0x01
TRANSACTION_TYPE_VOTE = 0x02
TRANSACTION_TYPE_UNVOTE = 0x03
TRANSACTION_TYPE_DELEGATE_REGISTRATION = 0x04
TRANSACTION_TYPE_DELEGATE_RESIGNATION = 0x05
TRANSACTION_TYPE_SECOND_SIGNATURE = 0x06
TRANSACTION_TYPE_MULTI_SIGNATURE = 0x07
TRANSACTION_TYPE_MULTI_PAYMENT = 0x08
TRANSACTION_TYPE_IPFS = 0x09
TRANSACTION_TYPE_TIMELOCK_TRANSFER = 0x0A
TRANSACTION_TYPE_TIMELOCK_CLAIM = 0x0B
TRANSACTION_TYPE_TIMELOCK_REFUND = 0x0C
TRANSACTION_TYPE_BUSINESS_REGISTRATION = 0x0D
TRANSACTION_TYPE_BUSINESS_RESIGNATION = 0x0E
TRANSACTION_TYPE_BUSINESS_UPDATE = 0x0F
TRANSACTION_TYPE_BRIDGECHAIN_REGISTRATION = 0x10
TRANSACTION_TYPE_BRIDGECHAIN_RESIGNATION = 0x11
TRANSACTION_TYPE_BRIDGECHAIN_UPDATE = 0x12
TRANSACTION_TYPE_SSI_TRANSACTION = 0x13
TRANSACTION_TYPE_DNS_TRANSACTION = 0x14
TYPE_API = 0x00
TYPE_EXPLORER = 0x01
TYPE_WALLET = 0x02
TYPE_UNKNOWN = 0xFF
def __init__(self, storage_path=None, identity_file="identity", identity=None, ratchets=False,
destination_name="nomadnetwork", destination_type="bc",
announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, announce_data="", announce_hidden=False,
register_startup=True, register_startup_delay=0, register_periodic=True, register_periodic_interval=30,
limiter_server_enabled=False, limiter_server_calls=1000, limiter_server_size=0, limiter_server_duration=60,
limiter_peer_enabled=True, limiter_peer_calls=15, limiter_peer_size=500*1024, limiter_peer_duration=60,
limiter_api_enabled=False, limiter_api_calls=15, limiter_api_size=500*1024, limiter_api_duration=60,
limiter_explorer_enabled=False, limiter_explorer_calls=15, limiter_explorer_size=500*1024, limiter_explorer_duration=60,
limiter_wallet_enabled=False, limiter_wallet_calls=15, limiter_wallet_size=500*1024, limiter_wallet_duration=60,
):
self.storage_path = storage_path
self.identity_file = identity_file
self.identity = identity
self.ratchets = ratchets
self.destination_name = destination_name
self.destination_type = destination_type
self.aspect_filter = self.destination_name + "." + self.destination_type
self.announce_startup = announce_startup
self.announce_startup_delay = int(announce_startup_delay)
self.announce_periodic = announce_periodic
self.announce_periodic_interval = int(announce_periodic_interval)
self.announce_data = announce_data
self.announce_hidden = announce_hidden
if self.storage_path:
if not os.path.isdir(self.storage_path):
os.makedirs(self.storage_path)
RNS.log("Server - Storage path was created", RNS.LOG_NOTICE)
RNS.log("Server - Storage path: " + self.storage_path, RNS.LOG_INFO)
if self.identity:
RNS.log("Server - Using existing Primary Identity %s" % (str(self.identity)))
else:
if not self.storage_path:
RNS.log("Server - No storage_path parameter", RNS.LOG_ERROR)
return
if not self.identity_file:
self.identity_file = "identity"
self.identity_path = self.storage_path + "/" + self.identity_file
if os.path.isfile(self.identity_path):
try:
self.identity = RNS.Identity.from_file(self.identity_path)
if self.identity != None:
RNS.log("Server - Loaded Primary Identity %s from %s" % (str(self.identity), self.identity_path))
else:
RNS.log("Server - Could not load the Primary Identity from "+self.identity_path, RNS.LOG_ERROR)
except Exception as e:
RNS.log("Server - Could not load the Primary Identity from "+self.identity_path, RNS.LOG_ERROR)
RNS.log("Server - The contained exception was: %s" % (str(e)), RNS.LOG_ERROR)
else:
try:
RNS.log("Server - No Primary Identity file found, creating new...")
self.identity = RNS.Identity()
self.identity.to_file(self.identity_path)
RNS.log("Server - Created new Primary Identity %s" % (str(self.identity)))
except Exception as e:
RNS.log("Server - Could not create and save a new Primary Identity", RNS.LOG_ERROR)
RNS.log("Server - The contained exception was: %s" % (str(e)), RNS.LOG_ERROR)
self.destination = RNS.Destination(self.identity, RNS.Destination.IN, RNS.Destination.SINGLE, self.destination_name, self.destination_type)
if self.ratchets:
self.destination.enable_ratchets(self.identity_path+"."+RNS.hexrep(self.destination.hash, delimit=False)+".ratchets")
self.destination.set_proof_strategy(RNS.Destination.PROVE_ALL)
self.destination.set_link_established_callback(self.peer_connected)
if self.announce_startup or self.announce_periodic:
self.announce(initial=True)
self.register()
self.token_init()
if limiter_server_enabled:
self.limiter_server = RateLimiter(int(limiter_server_calls), int(limiter_server_size), int(limiter_server_duration))
else:
self.limiter_server = None
if limiter_peer_enabled:
self.limiter_peer = RateLimiter(int(limiter_peer_calls), int(limiter_peer_size), int(limiter_peer_duration))
else:
self.limiter_peer = None
if limiter_api_enabled:
self.limiter_api = RateLimiter(int(limiter_api_calls), int(limiter_api_size), int(limiter_api_duration))
else:
self.limiter_api = None
if limiter_explorer_enabled:
self.limiter_explorer = RateLimiter(int(limiter_explorer_calls), int(limiter_explorer_size), int(limiter_explorer_duration))
else:
self.limiter_explorer = None
if limiter_wallet_enabled:
self.limiter_wallet = RateLimiter(int(limiter_wallet_calls), int(limiter_wallet_size), int(limiter_wallet_duration))
else:
self.limiter_wallet = None
def start(self):
pass
def stop(self):
pass
def register_announce_callback(self, handler_function):
self.announce_callback = handler_function(self.aspect_filter)
RNS.Transport.register_announce_handler(self.announce_callback)
def destination_hash(self):
return self.destination.hash
def destination_hash_str(self):
return RNS.hexrep(self.destination.hash, False)
def destination_check(self, destination):
if type(destination) is not bytes:
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
destination = destination[1:-1]
if len(destination) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
RNS.log("Server - Destination length is invalid", RNS.LOG_ERROR)
return False
try:
destination = bytes.fromhex(destination)
except Exception as e:
RNS.log("Server - Destination is invalid", RNS.LOG_ERROR)
return False
return True
def destination_correct(self, destination):
if type(destination) is not bytes:
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
destination = destination[1:-1]
if len(destination) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
return ""
try:
destination_bytes = bytes.fromhex(destination)
return destination
except Exception as e:
return ""
return ""
def announce(self, app_data=None, attached_interface=None, initial=False):
announce_timer = None
if self.announce_periodic and self.announce_periodic_interval > 0:
announce_timer = threading.Timer(self.announce_periodic_interval*60, self.announce)
announce_timer.daemon = True
announce_timer.start()
if initial:
if self.announce_startup:
if self.announce_startup_delay > 0:
if announce_timer is not None:
announce_timer.cancel()
announce_timer = threading.Timer(self.announce_startup_delay, self.announce)
announce_timer.daemon = True
announce_timer.start()
else:
self.announce_now(app_data=app_data, attached_interface=attached_interface)
return
self.announce_now(app_data=app_data, attached_interface=attached_interface)
def announce_now(self, app_data=None, attached_interface=None):
if self.announce_hidden:
self.destination.announce("".encode("utf-8"), attached_interface=attached_interface)
RNS.log("Server - Announced: " + RNS.prettyhexrep(self.destination_hash()) +" (Hidden)", RNS.LOG_DEBUG)
elif app_data != None:
if isinstance(app_data, str):
self.destination.announce(app_data.encode("utf-8"), attached_interface=attached_interface)
RNS.log("Server - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + app_data, RNS.LOG_DEBUG)
else:
self.destination.announce(app_data, attached_interface=attached_interface)
RNS.log("Server - Announced: " + RNS.prettyhexrep(self.destination_hash()), RNS.LOG_DEBUG)
else:
if isinstance(self.announce_data, str):
self.destination.announce(self.announce_data.encode("utf-8"), attached_interface=attached_interface)
RNS.log("Server - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, RNS.LOG_DEBUG)
else:
self.destination.announce(self.announce_data, attached_interface=attached_interface)
RNS.log("Server - Announced: " + RNS.prettyhexrep(self.destination_hash()), RNS.LOG_DEBUG)
def register(self):
RNS.log("Server - Register", RNS.LOG_DEBUG)
self.destination.register_request_handler("api", response_generator=self.response_api, allow=RNS.Destination.ALLOW_ALL)
self.destination.register_request_handler("explorer", response_generator=self.response_explorer, allow=RNS.Destination.ALLOW_ALL)
self.destination.register_request_handler("wallet", response_generator=self.response_wallet, allow=RNS.Destination.ALLOW_ALL)
def peer_connected(self, link):
RNS.log("Server - Peer connected to "+str(self.destination), RNS.LOG_VERBOSE)
link.set_link_closed_callback(self.peer_disconnected)
link.set_remote_identified_callback(self.peer_identified)
def peer_disconnected(self, link):
RNS.log("Server - Peer disconnected from "+str(self.destination), RNS.LOG_VERBOSE)
def peer_identified(self, link, identity):
if not identity:
link.teardown()
#################################################
# Config #
#################################################
def config_load(self, file, default=""):
try:
file = self.storage_path+"/"+file
config = configparser.ConfigParser(allow_no_value=True, inline_comment_prefixes="#")
config.sections()
if os.path.isfile(file):
config.read(file, encoding="utf-8")
return config
else:
if not os.path.isdir(os.path.dirname(file)):
os.makedirs(os.path.dirname(file))
fh = open(file, "w")
fh.write(default)
fh.close()
config.read(file, encoding="utf-8")
return config
except Exception as e:
return None
def config_save(self, file, config):
try:
file = self.storage_path+"/"+file
if not os.path.isdir(os.path.dirname(file)):
os.makedirs(os.path.dirname(file))
fh = open(file, "w")
config.write(fh)
fh.close()
return True
except Exception as e:
return False
#################################################
# Log #
#################################################
def log(self, text="", level=None):
if level == None:
level = RNS.LOG_ERROR
RNS.log(text, level)
def log_exception(self, e, text="", level=None):
import traceback
if level == None:
level = RNS.LOG_ERROR
RNS.log(text+" - An "+str(type(e))+" occurred: "+str(e), level)
RNS.log("".join(traceback.TracebackException.from_exception(e).format()), level)
#################################################
# Response #
#################################################
def response_api(self, path, data, request_id, link_id, remote_identity, requested_at):
if not remote_identity:
return msgpack.packb({self.KEY_RESULT: self.RESULT_NO_IDENTITY})
if self.limiter_server and not self.limiter_server.handle("server"):
return msgpack.packb({self.KEY_RESULT: self.RESULT_LIMIT_SERVER})
if self.limiter_peer and not self.limiter_peer.handle(str(remote_identity)):
return msgpack.packb({self.KEY_RESULT: self.RESULT_LIMIT_PEER})
if self.limiter_api and not self.limiter_api.handle(str(remote_identity)):
return msgpack.packb({self.KEY_RESULT: self.RESULT_LIMIT_PEER})
if not data:
return msgpack.packb({self.KEY_RESULT: self.RESULT_NO_DATA})
RNS.log("Server - Response - API", RNS.LOG_DEBUG)
RNS.log(data, RNS.LOG_EXTREME)
data_return = {}
data_return[self.KEY_RESULT] = self.RESULT_OK
if self.KEY_API in data:
data_return[self.KEY_API] = {}
for key, value in data[self.KEY_API].items():
try:
value_token = value["token"] if "token" in value else None
value_url = value["url"] if "url" in value else None
value_data = value["data"] if "data" in value else None
value_json = value["json"] if "json" in value else None
value_headers = value["headers"] if "headers" in value else None
value_cookies = value["cookies"] if "cookies" in value else None
value_auth = value["auth"] if "auth" in value else None
value_timeout = value["timeout"] if "timeout" in value else None
if "delete" in value:
response = self.api_delete(token=value_token, url=value_url if value_url else value["delete"], data=value_data, json=value_json, headers=value_headers, cookies=value_cookies, auth=value_auth, timeout=value_timeout)
elif "get" in value:
response = self.api_get(token=value_token, url=value_url if value_url else value["get"], data=value_data, json=value_json, headers=value_headers, cookies=value_cookies, auth=value_auth, timeout=value_timeout)
elif "patch" in value:
response = self.api_patch(token=value_token, url=value_url if value_url else value["patch"], data=value_data, json=value_json, headers=value_headers, cookies=value_cookies, auth=value_auth, timeout=value_timeout)
elif "post" in value:
response = self.api_post(token=value_token, url=value_url if value_url else value["post"], data=value_data, json=value_json, headers=value_headers, cookies=value_cookies, auth=value_auth, timeout=value_timeout)
elif "put" in value:
response = self.api_put(token=value_token, url=value_url if value_url else value["put"], data=value_data, json=value_json, headers=value_headers, cookies=value_cookies, auth=value_auth, timeout=value_timeout)
else:
raise ValueError("Wrong api type")
data_return[self.KEY_API][key] = response
except Exception as e:
self.log_exception(e, "Server - API")
data_return[self.KEY_API][key] = None
data_return[self.KEY_RESULT] = self.RESULT_PARTIAL
if len(data_return[self.KEY_API]) == 0:
del data_return[self.KEY_API]
data_return = msgpack.packb(data_return)
if self.limiter_server:
self.limiter_server.handle_size("server", len(data_return))
if self.limiter_peer:
self.limiter_peer.handle_size(str(remote_identity), len(data_return))
return data_return
def response_explorer(self, path, data, request_id, link_id, remote_identity, requested_at):
if not remote_identity:
return msgpack.packb({self.KEY_RESULT: self.RESULT_NO_IDENTITY})
if self.limiter_server and not self.limiter_server.handle("server"):
return msgpack.packb({self.KEY_RESULT: self.RESULT_LIMIT_SERVER})
if self.limiter_peer and not self.limiter_peer.handle(str(remote_identity)):
return msgpack.packb({self.KEY_RESULT: self.RESULT_LIMIT_PEER})
if self.limiter_explorer and not self.limiter_explorer.handle(str(remote_identity)):
return msgpack.packb({self.KEY_RESULT: self.RESULT_LIMIT_PEER})
if not data:
return msgpack.packb({self.KEY_RESULT: self.RESULT_NO_DATA})
RNS.log("Server - Response - Explorer", RNS.LOG_DEBUG)
RNS.log(data, RNS.LOG_EXTREME)
data_return = {}
data_return[self.KEY_RESULT] = self.RESULT_OK
if self.KEY_E in data:
# explorer
data_explorer = data[self.KEY_E]
filter = data_explorer[self.KEY_E_FILTER] if self.KEY_E_FILTER in data_explorer else None
hash = data_explorer[self.KEY_E_HASH] if self.KEY_E_HASH in data_explorer else None
limit = data_explorer[self.KEY_E_LIMIT] if self.KEY_E_LIMIT in data_explorer else None
limit_start = data_explorer[self.KEY_E_LIMIT_START] if self.KEY_E_LIMIT_START in data_explorer else None
order = data_explorer[self.KEY_E_ORDER] if self.KEY_E_ORDER in data_explorer else None
search = data_explorer[self.KEY_E_SEARCH] if self.KEY_E_SEARCH in data_explorer else None
token = data_explorer[self.KEY_E_TOKEN] if self.KEY_E_TOKEN in data_explorer else None
if self.KEY_A in data:
mapping = {v: k for k, v in self.KEY_A_MAPPING.items()}
elif self.KEY_B in data:
mapping = {v: k for k, v in self.KEY_B_MAPPING.items()}
elif self.KEY_D in data:
mapping = {v: k for k, v in self.KEY_D_MAPPING.items()}
elif self.KEY_I in data:
mapping = {v: k for k, v in self.KEY_I_MAPPING.items()}
elif self.KEY_T in data:
mapping = {v: k for k, v in self.KEY_T_MAPPING.items()}
else:
mapping = {}
# explorer - filter
if filter != None and len(filter) > 0:
filter_result = {}
for filter_key, filter_value in filter.items():
if filter_key in mapping:
filter_result[mapping[filter_key]] = filter_value
if len(filter_result) > 0:
filter = filter_result
else:
filter = None
# explorer - order
if order != None:
order_mapping = {v: k for k, v in self.KEY_E_ORDER_MAPPING.items()}
order[1] = order_mapping[order[1]]
if order[0] in mapping:
order[0] = mapping[order[0]]
else:
order = None
# accounts
if self.KEY_A in data:
try:
count, entrys = self.accounts_list(token=token, filter=filter, search=search, order=order, limit=limit, limit_start=limit_start)
accounts = {}
for account_id, result in entrys.items():
accounts[account_id] = {}
for key, value in result.items():
if key in self.KEY_A_MAPPING:
accounts[account_id][self.KEY_A_MAPPING[key]] = value
hash_new = RNS.Identity.full_hash(msgpack.packb(accounts))
if hash != hash_new:
data_return[self.KEY_A] = [count, accounts]
data_return[self.KEY_E] = {self.KEY_E_HASH: hash_new}
except Exception as e:
self.log_exception(e, "Server - Explorer - Accounts")
data_return[self.KEY_RESULT] = self.RESULT_PARTIAL
# blocks
if self.KEY_B in data:
try:
count, entrys = self.blocks_list(token=token, filter=filter, search=search, order=order, limit=limit, limit_start=limit_start)
blocks = {}
for blocks_id, result in entrys.items():
blocks[blocks_id] = {}
for key, value in result.items():
if key in self.KEY_B_MAPPING:
blocks[blocks_id][self.KEY_B_MAPPING[key]] = value
hash_new = RNS.Identity.full_hash(msgpack.packb(blocks))
if hash != hash_new:
data_return[self.KEY_B] = [count, blocks]
data_return[self.KEY_E] = {self.KEY_E_HASH: hash_new}
except Exception as e:
self.log_exception(e, "Server - Explorer - Blocks")
data_return[self.KEY_RESULT] = self.RESULT_PARTIAL
# delegates
if self.KEY_D in data:
try:
count, entrys = self.delegates_list(token=token, filter=filter, search=search, order=order, limit=limit, limit_start=limit_start)
delegates = {}
for delegate_id, result in entrys.items():
delegates[delegate_id] = {}
for key, value in result.items():
if key in self.KEY_D_MAPPING:
delegates[delegate_id][self.KEY_D_MAPPING[key]] = value
hash_new = RNS.Identity.full_hash(msgpack.packb(delegates))
if hash != hash_new:
data_return[self.KEY_D] = [count, delegates]
data_return[self.KEY_E] = {self.KEY_E_HASH: hash_new}
except Exception as e: