mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-03-26 12:13:08 +00:00
Compare commits
1862 Commits
demo-versi
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb083ce91e | ||
|
|
f9071d492c | ||
|
|
c5950bd08a | ||
|
|
6f77c75b9c | ||
|
|
fe368f2b48 | ||
|
|
3913446cd2 | ||
|
|
7b50c68550 | ||
|
|
05c2c3a56d | ||
|
|
7b20a83379 | ||
|
|
325960d696 | ||
|
|
b631ef02c6 | ||
|
|
9d87e3b046 | ||
|
|
b254ebb185 | ||
|
|
be94e133fa | ||
|
|
bffc711470 | ||
|
|
666353d7df | ||
|
|
c40c62a484 | ||
|
|
83ef789002 | ||
|
|
4fdefd3557 | ||
|
|
ce729f112d | ||
|
|
be4f6c0c78 | ||
|
|
2cecf71c93 | ||
|
|
b81b725bd1 | ||
|
|
5d9980cf63 | ||
|
|
0bcb626adc | ||
|
|
0586e98c61 | ||
|
|
82fb5781fe | ||
|
|
e42c62fc21 | ||
|
|
a83725512d | ||
|
|
821cb891d5 | ||
|
|
de3d17c7f8 | ||
|
|
94faaa6d64 | ||
|
|
b94b58ceb1 | ||
|
|
d71805abb6 | ||
|
|
b3b5337b92 | ||
|
|
82e8e8601d | ||
|
|
bd79e982fd | ||
|
|
ba8fdf4f29 | ||
|
|
1035893fd9 | ||
|
|
a12d1e5912 | ||
|
|
8dd5037e28 | ||
|
|
7481b54cf8 | ||
|
|
2dbacdaff1 | ||
|
|
dbc2e7c327 | ||
|
|
f5ba922b70 | ||
|
|
b544b919a6 | ||
|
|
a304f3ad13 | ||
|
|
66d501e4d7 | ||
|
|
d79ac3f9a8 | ||
|
|
65ce23b9e1 | ||
|
|
252848a145 | ||
|
|
3b0e842a42 | ||
|
|
147fff957a | ||
|
|
6e79ce0941 | ||
|
|
90412f9590 | ||
|
|
5bf4495709 | ||
|
|
3e1cc2e58e | ||
|
|
222e68ac0b | ||
|
|
aa462b66eb | ||
|
|
e3b93b6e9a | ||
|
|
efe8393ba0 | ||
|
|
3998a2562c | ||
|
|
b44e066e84 | ||
|
|
2fb8c2c87f | ||
|
|
8a8c7f722b | ||
|
|
756f2f4135 | ||
|
|
dd64f0b1f8 | ||
|
|
d665540495 | ||
|
|
620dd3ad10 | ||
|
|
6e74cca512 | ||
|
|
d941fd3b8b | ||
|
|
55c930d784 | ||
|
|
e20860ea97 | ||
|
|
546c3d0986 | ||
|
|
339c0dc048 | ||
|
|
5f2d33f90e | ||
|
|
3464d15d89 | ||
|
|
3256e3d470 | ||
|
|
0b66903cb9 | ||
|
|
b5d0d2d0c1 | ||
|
|
b1747548b2 | ||
|
|
d029def7c7 | ||
|
|
a5119dc3f3 | ||
|
|
1cfc710024 | ||
|
|
2d04946a64 | ||
|
|
1e3b60feae | ||
|
|
7f0d41104d | ||
|
|
7a9d02af31 | ||
|
|
39d8c89c17 | ||
|
|
0aec6028f6 | ||
|
|
4950c1277c | ||
|
|
fa406e7a86 | ||
|
|
cf92157728 | ||
|
|
a7607b4525 | ||
|
|
cb57baaa36 | ||
|
|
e3764e1911 | ||
|
|
ec1018deac | ||
|
|
db5d1e5cf7 | ||
|
|
06665160c6 | ||
|
|
44a11bd3f1 | ||
|
|
ddecae8c40 | ||
|
|
9df121704f | ||
|
|
9d7cf58f6f | ||
|
|
7daf32611a | ||
|
|
6000d61bf2 | ||
|
|
181d43e6d2 | ||
|
|
84abe02573 | ||
|
|
ed299932c5 | ||
|
|
71d07ccdf7 | ||
|
|
d863b763d1 | ||
|
|
227f006179 | ||
|
|
416f16bfc6 | ||
|
|
2e5a1d29ad | ||
|
|
ab41bff5cc | ||
|
|
226ebe3e0e | ||
|
|
7d4409fdf9 | ||
|
|
4e112bc520 | ||
|
|
9dc6025537 | ||
|
|
5a0ef060fd | ||
|
|
157495ad59 | ||
|
|
87c251b6e2 | ||
|
|
bdeca0185f | ||
|
|
3c677ce78c | ||
|
|
71de2f7754 | ||
|
|
d5c75cbd98 | ||
|
|
99069c84b1 | ||
|
|
10770bd99e | ||
|
|
6887131364 | ||
|
|
fee1ab30e9 | ||
|
|
d9b2b0c824 | ||
|
|
e2175132f5 | ||
|
|
18d9d80105 | ||
|
|
1ea3aab3f1 | ||
|
|
51a21c42fc | ||
|
|
1f01f7c06a | ||
|
|
c622f3540a | ||
|
|
b9324bf03b | ||
|
|
3c10263023 | ||
|
|
67f49697f7 | ||
|
|
5eb4e0af1f | ||
|
|
3c8e81c11c | ||
|
|
f48b116754 | ||
|
|
70471f0a54 | ||
|
|
9bb4d95f0b | ||
|
|
ea7beaaaef | ||
|
|
c59d3efbcc | ||
|
|
72ce3ff1e4 | ||
|
|
184d378c90 | ||
|
|
529dfce413 | ||
|
|
803708ea1e | ||
|
|
437e5addb4 | ||
|
|
8b5524901c | ||
|
|
84c23e8680 | ||
|
|
f8661b24e4 | ||
|
|
487027d98f | ||
|
|
6e61698ff8 | ||
|
|
b2ae6317d2 | ||
|
|
3ed6ce3f1c | ||
|
|
6209317cd4 | ||
|
|
d58df166e7 | ||
|
|
85dd5fae86 | ||
|
|
fb2709f96c | ||
|
|
24271b398e | ||
|
|
fe5db24620 | ||
|
|
818eebb99f | ||
|
|
82966e652d | ||
|
|
c8bd5b3679 | ||
|
|
767b5afd38 | ||
|
|
7ba888ce7d | ||
|
|
9a61e64ccf | ||
|
|
a64c16b8c9 | ||
|
|
3f2d9ed0f4 | ||
|
|
0c90d42a0f | ||
|
|
55388e30a2 | ||
|
|
548e4c8d9a | ||
|
|
ccfc14cac1 | ||
|
|
e71f2fe16a | ||
|
|
2bad85b217 | ||
|
|
6101d791e3 | ||
|
|
3855bf2399 | ||
|
|
a0e2e06cc1 | ||
|
|
35bb85ebaf | ||
|
|
cda22a3a2b | ||
|
|
56c6abe08f | ||
|
|
801f2c46c8 | ||
|
|
bc84d5cf31 | ||
|
|
d1265af406 | ||
|
|
feb66e6a18 | ||
|
|
f39fd9ee53 | ||
|
|
6f979786e7 | ||
|
|
77f1be59b0 | ||
|
|
de1aa99fd4 | ||
|
|
b7f44ffd65 | ||
|
|
683722edbd | ||
|
|
9f0f3713c7 | ||
|
|
0194376fe2 | ||
|
|
78eaac59f6 | ||
|
|
c2d4e717e4 | ||
|
|
80c0356fac | ||
|
|
e99c92543e | ||
|
|
78233308cc | ||
|
|
abfb707900 | ||
|
|
8095408e1b | ||
|
|
c0d4a98f3c | ||
|
|
0297791f23 | ||
|
|
89ce9f322a | ||
|
|
0201a39bcf | ||
|
|
6c2bdb1b20 | ||
|
|
992fd26bcd | ||
|
|
27f31cf3d0 | ||
|
|
dee3f7fa6f | ||
|
|
b3e2162d71 | ||
|
|
946a45b8a3 | ||
|
|
fa6a99192e | ||
|
|
b008982eeb | ||
|
|
01dc41cc47 | ||
|
|
964800f765 | ||
|
|
f1293ec77b | ||
|
|
76af23f386 | ||
|
|
b2ef5e915c | ||
|
|
f7f69f9f7b | ||
|
|
f11d374ae7 | ||
|
|
8cf73c00a9 | ||
|
|
bf8c47b9b7 | ||
|
|
33084300ad | ||
|
|
70a2f9ce4e | ||
|
|
a821f04fb8 | ||
|
|
f8dfcac17a | ||
|
|
889326d2ad | ||
|
|
25084b5669 | ||
|
|
2ac5d8d7de | ||
|
|
32c36af126 | ||
|
|
177235a2c7 | ||
|
|
26d6286b8c | ||
|
|
2954faa5ec | ||
|
|
d965b7c25d | ||
|
|
5a96eb451c | ||
|
|
d8537ea3f0 | ||
|
|
2c89e17896 | ||
|
|
c3ca6c7563 | ||
|
|
707ea7d379 | ||
|
|
08e65d8332 | ||
|
|
6a89ca0a7f | ||
|
|
d8a8bdfc97 | ||
|
|
a4fc1dd570 | ||
|
|
344430a8f2 | ||
|
|
85d2a13148 | ||
|
|
8b16318c38 | ||
|
|
439392cf26 | ||
|
|
97502f3baa | ||
|
|
5255f4df0c | ||
|
|
91e57bbf1b | ||
|
|
7ea8a85be9 | ||
|
|
5d228c6d80 | ||
|
|
77f2fb6994 | ||
|
|
c63ecd6aba | ||
|
|
f64b7ba0ef | ||
|
|
9d35cf4ea8 | ||
|
|
c663ad1a56 | ||
|
|
2acc388ae9 | ||
|
|
d4494c1f21 | ||
|
|
d2ce0cd51b | ||
|
|
c78f74e50b | ||
|
|
943efd1433 | ||
|
|
378d53f152 | ||
|
|
9b51fd1fc2 | ||
|
|
51ab510e71 | ||
|
|
d0f26744eb | ||
|
|
0a8d6f1eb4 | ||
|
|
cb6fb881ac | ||
|
|
9bf3edbaad | ||
|
|
6d7ca887e3 | ||
|
|
0d0bb87bf3 | ||
|
|
70bae9158c | ||
|
|
60be2b6d00 | ||
|
|
4b59782924 | ||
|
|
fdcaabf761 | ||
|
|
09debfa74f | ||
|
|
dae1d92253 | ||
|
|
843ca0b00d | ||
|
|
c753373434 | ||
|
|
975cfb9ec6 | ||
|
|
818fc5e601 | ||
|
|
d40232abcc | ||
|
|
3facbbcc1f | ||
|
|
9e52d41090 | ||
|
|
6a399ac36c | ||
|
|
c042361544 | ||
|
|
a0ebd73f48 | ||
|
|
4ad56ded89 | ||
|
|
e5f908d241 | ||
|
|
879dd61189 | ||
|
|
68254a835d | ||
|
|
5f800ba9d9 | ||
|
|
7c930415a6 | ||
|
|
74e7bf321e | ||
|
|
0930e4dd86 | ||
|
|
6c77939c62 | ||
|
|
72c45be083 | ||
|
|
f891f57cab | ||
|
|
b0f00b2751 | ||
|
|
c3aa3a5495 | ||
|
|
cb1b6f14f6 | ||
|
|
92d254b66e | ||
|
|
042854653c | ||
|
|
56ff66ccc1 | ||
|
|
36407c1d43 | ||
|
|
bdd7c7f599 | ||
|
|
f248cf2ae0 | ||
|
|
5884ad84fe | ||
|
|
90c70a7f76 | ||
|
|
343bf003a4 | ||
|
|
01d33788e5 | ||
|
|
db0de3a53f | ||
|
|
470c62b04a | ||
|
|
8959b1652c | ||
|
|
e3b59a117a | ||
|
|
4afe687e4c | ||
|
|
fdb2979f66 | ||
|
|
94d4ccad80 | ||
|
|
5b11234335 | ||
|
|
9909f02803 | ||
|
|
ce29ca2fd0 | ||
|
|
e0caa7838d | ||
|
|
709f189aa1 | ||
|
|
3b6446ff86 | ||
|
|
e323387238 | ||
|
|
c63279c5eb | ||
|
|
c87b716425 | ||
|
|
25b3cd620a | ||
|
|
357e0f1444 | ||
|
|
78f3d1cb7d | ||
|
|
2fc1ec0fdc | ||
|
|
b1840ca68a | ||
|
|
4b50676ebc | ||
|
|
7c3182d903 | ||
|
|
70d3b3a3e6 | ||
|
|
0b77a1e7cf | ||
|
|
fdeb1f971c | ||
|
|
5ac9953488 | ||
|
|
01f7d254c9 | ||
|
|
dc5b5ba278 | ||
|
|
2b07c02129 | ||
|
|
a08a7824c0 | ||
|
|
9a7c851dcf | ||
|
|
b509817038 | ||
|
|
f64d7f8dd3 | ||
|
|
61ffca07e2 | ||
|
|
e0f1d528b8 | ||
|
|
cf374a1422 | ||
|
|
9db96d1736 | ||
|
|
dac773fa03 | ||
|
|
50b253fa00 | ||
|
|
35b469d738 | ||
|
|
629a3d1ef6 | ||
|
|
29dfa0b2e3 | ||
|
|
67ada15c1b | ||
|
|
0126791537 | ||
|
|
81e4cfcba6 | ||
|
|
ed60e06ae2 | ||
|
|
ec1424cd8e | ||
|
|
efac8639c3 | ||
|
|
9bb94c5df5 | ||
|
|
e1df915357 | ||
|
|
71a7905a58 | ||
|
|
321f31a54b | ||
|
|
a5240925fc | ||
|
|
6ca020d547 | ||
|
|
d14aeb6cc1 | ||
|
|
a939ceecad | ||
|
|
c45f560790 | ||
|
|
ac449427b2 | ||
|
|
729ff6673c | ||
|
|
38f1f68030 | ||
|
|
0ae327d934 | ||
|
|
f4e9a6606d | ||
|
|
a87db600cf | ||
|
|
71787a70f7 | ||
|
|
2d0525ab31 | ||
|
|
e0729a1725 | ||
|
|
616159f936 | ||
|
|
c1d2b625ea | ||
|
|
22a6195eca | ||
|
|
9cac2e7487 | ||
|
|
fcb2917c21 | ||
|
|
8a00d8d8d4 | ||
|
|
8fa8cc33d8 | ||
|
|
e4634b6edb | ||
|
|
0efc522837 | ||
|
|
45a4063aea | ||
|
|
17a1be5b0c | ||
|
|
3cb7f17aaf | ||
|
|
29ad41a645 | ||
|
|
2e071b538f | ||
|
|
df02c59310 | ||
|
|
de54744893 | ||
|
|
476dc50482 | ||
|
|
e44eade97b | ||
|
|
22290c05fb | ||
|
|
95a58faf94 | ||
|
|
1e3e551ee7 | ||
|
|
c7c646136f | ||
|
|
22258435ff | ||
|
|
81921c3406 | ||
|
|
0ed41ba9f2 | ||
|
|
fb1221d985 | ||
|
|
d9839ea5a4 | ||
|
|
d6df9a7796 | ||
|
|
deca3ffe69 | ||
|
|
ab2b16956e | ||
|
|
c606e7fe75 | ||
|
|
ff13c34027 | ||
|
|
636a3daedc | ||
|
|
1b04c5326a | ||
|
|
e6b603ba44 | ||
|
|
a64fad8ff8 | ||
|
|
19a53ed44e | ||
|
|
8d5e6a37e6 | ||
|
|
819bb1b7f3 | ||
|
|
f94eae0583 | ||
|
|
1dd0f16a5c | ||
|
|
752ad89ac7 | ||
|
|
37c85de38d | ||
|
|
bc2350b349 | ||
|
|
bbef426f86 | ||
|
|
561b4a2dcf | ||
|
|
e78d6a59a0 | ||
|
|
d7cac557af | ||
|
|
4667424215 | ||
|
|
c0ad223b7c | ||
|
|
c0e879edae | ||
|
|
4c4e30864b | ||
|
|
516feee101 | ||
|
|
fe4a89191c | ||
|
|
9a92aa1553 | ||
|
|
a2d3d5ae0d | ||
|
|
c81ce363c1 | ||
|
|
2288a725d2 | ||
|
|
3205e69e11 | ||
|
|
f4c81f5e78 | ||
|
|
e897fd6076 | ||
|
|
e680544ca3 | ||
|
|
02702213dc | ||
|
|
ab31a1fd2d | ||
|
|
ded6e8123d | ||
|
|
eaab2e042a | ||
|
|
3752c39bad | ||
|
|
6e745494c2 | ||
|
|
82d5ce403c | ||
|
|
addc65933f | ||
|
|
272cbfe400 | ||
|
|
26a39bb005 | ||
|
|
c44e8d6a89 | ||
|
|
a97066c41d | ||
|
|
6d883d5528 | ||
|
|
e2d7906930 | ||
|
|
d22354a485 | ||
|
|
0939b7e1c6 | ||
|
|
7a395d07c4 | ||
|
|
55b13a6f41 | ||
|
|
7c231ae955 | ||
|
|
15d78527fe | ||
|
|
2367bf343b | ||
|
|
7d48fe70b5 | ||
|
|
b00e990eb2 | ||
|
|
b117073237 | ||
|
|
505e653ff0 | ||
|
|
df58511bf0 | ||
|
|
9896cd3767 | ||
|
|
e4e476fde9 | ||
|
|
ffe1034c0d | ||
|
|
6e18f46965 | ||
|
|
d1a2234cc5 | ||
|
|
74fa7c9435 | ||
|
|
cfebb9d8b4 | ||
|
|
8b6443aa89 | ||
|
|
652be426ae | ||
|
|
2a2fe1347a | ||
|
|
a8f41838ac | ||
|
|
2218ad1c64 | ||
|
|
cabb4ab257 | ||
|
|
27cd0d068f | ||
|
|
2c9219bb0f | ||
|
|
d204f386bb | ||
|
|
c6aa0c41b6 | ||
|
|
877f879af3 | ||
|
|
75beecc6b9 | ||
|
|
0b8b1c89b8 | ||
|
|
f04b299b1a | ||
|
|
ec6caee2dc | ||
|
|
17b36b924e | ||
|
|
fb62143398 | ||
|
|
56295d12d8 | ||
|
|
eb77217318 | ||
|
|
e397d6c576 | ||
|
|
66d01d69d7 | ||
|
|
60822c7f03 | ||
|
|
173945a170 | ||
|
|
654e92f0d7 | ||
|
|
2b2a52d0b8 | ||
|
|
b143beef37 | ||
|
|
f3dcda346c | ||
|
|
bec78ae70f | ||
|
|
67d5a3e9f4 | ||
|
|
78a6ff72ee | ||
|
|
a097ff9d49 | ||
|
|
4e46dbbdf4 | ||
|
|
5d7f44aa3e | ||
|
|
82a8e551b9 | ||
|
|
4105eecace | ||
|
|
342809c830 | ||
|
|
b96865ab89 | ||
|
|
b8821693a8 | ||
|
|
9267cb59af | ||
|
|
562ab63735 | ||
|
|
6484e0121c | ||
|
|
f4978c1bde | ||
|
|
47e76c9f85 | ||
|
|
5a4111bcc8 | ||
|
|
d789ccba46 | ||
|
|
bb54d08f02 | ||
|
|
c2e09031e1 | ||
|
|
b6a0e62871 | ||
|
|
8da515da6b | ||
|
|
aeb3fd3932 | ||
|
|
3dbe31501c | ||
|
|
26806daedc | ||
|
|
ae5fe0a2a5 | ||
|
|
ccce2213da | ||
|
|
72a3e406b8 | ||
|
|
4f5c3a831e | ||
|
|
f9032a676a | ||
|
|
529ca9a643 | ||
|
|
deb6e9d839 | ||
|
|
7e2a8f4491 | ||
|
|
eaee772c86 | ||
|
|
537eeb63dc | ||
|
|
de5201e212 | ||
|
|
54cc214c33 | ||
|
|
78e593285d | ||
|
|
bbbb1c1a23 | ||
|
|
1fd6651121 | ||
|
|
36eaed5936 | ||
|
|
af19d7ad93 | ||
|
|
8e131713de | ||
|
|
874aa09b82 | ||
|
|
71b59af774 | ||
|
|
2fcde81301 | ||
|
|
ab37740348 | ||
|
|
aa6da018eb | ||
|
|
2022f28228 | ||
|
|
3473602efc | ||
|
|
324eec24df | ||
|
|
1b5521b5c9 | ||
|
|
040dc9eff7 | ||
|
|
6f8a7295c2 | ||
|
|
0a95e3ee62 | ||
|
|
fc179dcb51 | ||
|
|
6b9eec61e5 | ||
|
|
f400684d59 | ||
|
|
f817279d33 | ||
|
|
4620219408 | ||
|
|
1a033af5f2 | ||
|
|
76839dd59a | ||
|
|
5db00ceab3 | ||
|
|
149b00d2a3 | ||
|
|
f98c645642 | ||
|
|
e7ee114d9f | ||
|
|
7296088005 | ||
|
|
1d09afd9e0 | ||
|
|
2d1ee4b279 | ||
|
|
f2235548da | ||
|
|
48d1d0e858 | ||
|
|
bece9a9108 | ||
|
|
f4a8429461 | ||
|
|
9d37a88069 | ||
|
|
de751952af | ||
|
|
e81d7f099d | ||
|
|
5d30de695f | ||
|
|
cecf39cecd | ||
|
|
28b64e87f5 | ||
|
|
2e82d2ddb8 | ||
|
|
e6a73a02b5 | ||
|
|
065eeac3b2 | ||
|
|
9f753c0333 | ||
|
|
c6531aaab4 | ||
|
|
c25e93edd5 | ||
|
|
fcdd6e96a5 | ||
|
|
c5e640d56c | ||
|
|
56151e9d3d | ||
|
|
8289c07b28 | ||
|
|
a84057561e | ||
|
|
072cf5cd0f | ||
|
|
ed373067fb | ||
|
|
3c21f4418f | ||
|
|
5baa759283 | ||
|
|
ca7cc10f7c | ||
|
|
c49a3afec7 | ||
|
|
fe3c11fd3e | ||
|
|
639c282c61 | ||
|
|
05d34bf9ec | ||
|
|
32de4e2990 | ||
|
|
0a1bbb9d21 | ||
|
|
22c60cb97f | ||
|
|
22f6d92ca6 | ||
|
|
c1a999c02e | ||
|
|
4f15237446 | ||
|
|
e351279278 | ||
|
|
c95c4b2f16 | ||
|
|
f6c787e15f | ||
|
|
c30ba4be1a | ||
|
|
9601e0053e | ||
|
|
9adc88f8c8 | ||
|
|
5a03ad431d | ||
|
|
3581c97424 | ||
|
|
9b37a2a8b7 | ||
|
|
d427d49c65 | ||
|
|
1b0dea11f6 | ||
|
|
a280551ee3 | ||
|
|
10b6cfcf93 | ||
|
|
6997a8da54 | ||
|
|
621b7c0bfa | ||
|
|
45670902d1 | ||
|
|
56f8df1d2b | ||
|
|
6075e03d06 | ||
|
|
172b13d03a | ||
|
|
9b729d6c3e | ||
|
|
d6d722c016 | ||
|
|
0ac7b5c702 | ||
|
|
dc38b8ea2d | ||
|
|
d425d70d4c | ||
|
|
ddf6e707ec | ||
|
|
d126dac303 | ||
|
|
081a366c16 | ||
|
|
87f45b8215 | ||
|
|
90a1ee994f | ||
|
|
44f6f44fa2 | ||
|
|
e73e4b0558 | ||
|
|
144c037114 | ||
|
|
7d12502542 | ||
|
|
a5496eb5d5 | ||
|
|
2b15bed3fa | ||
|
|
f729072fae | ||
|
|
ef97fade99 | ||
|
|
2975d9d878 | ||
|
|
c1283d4a0c | ||
|
|
cde11e75d6 | ||
|
|
8dc709a6e0 | ||
|
|
92b5dffec2 | ||
|
|
b3dca76b67 | ||
|
|
74327bcec6 | ||
|
|
b7a6fda00a | ||
|
|
cd99318bf0 | ||
|
|
bbc02f0cf3 | ||
|
|
d4a471e948 | ||
|
|
e52e7d9ee9 | ||
|
|
c5cca4376e | ||
|
|
c3cd110688 | ||
|
|
d78d97c815 | ||
|
|
2419d4a112 | ||
|
|
8dd1c2d846 | ||
|
|
2e0450bfcd | ||
|
|
4389364b55 | ||
|
|
bb6cf322a0 | ||
|
|
93b616a434 | ||
|
|
1599bd41ce | ||
|
|
de1bf770f0 | ||
|
|
165e331aea | ||
|
|
5fe29016b7 | ||
|
|
3acc51ac87 | ||
|
|
e76800e283 | ||
|
|
aa81a311ac | ||
|
|
213e8dfb82 | ||
|
|
ae50ff65fa | ||
|
|
fac1dc19e2 | ||
|
|
30b4c8036e | ||
|
|
a360df29bf | ||
|
|
40bb718527 | ||
|
|
63f102643b | ||
|
|
77fd189010 | ||
|
|
89c1a97798 | ||
|
|
7453734dbe | ||
|
|
3e3ee9ba36 | ||
|
|
31405af143 | ||
|
|
5212d456ab | ||
|
|
9f443b92a8 | ||
|
|
bb58f7dd0a | ||
|
|
8c92a58bbc | ||
|
|
d019c26578 | ||
|
|
2d8722b7d0 | ||
|
|
0be88d4343 | ||
|
|
f77481f3b5 | ||
|
|
abb1fc5e45 | ||
|
|
7e8b8c7624 | ||
|
|
1b2962fa03 | ||
|
|
1b7afbecdc | ||
|
|
33ee1fc72e | ||
|
|
d7f0346671 | ||
|
|
5276cc8f07 | ||
|
|
e9c9058827 | ||
|
|
c6f0782d26 | ||
|
|
340c9c4ce6 | ||
|
|
9637b8b666 | ||
|
|
cd2430c2ee | ||
|
|
0ac5cbb35d | ||
|
|
45e3223d51 | ||
|
|
81316f6766 | ||
|
|
81393925d0 | ||
|
|
dc277b7e71 | ||
|
|
8e2b1c5993 | ||
|
|
1309185ecc | ||
|
|
f54cdf4a4c | ||
|
|
22440cf95d | ||
|
|
2e37e20014 | ||
|
|
70c228dbec | ||
|
|
471611f848 | ||
|
|
8c493a0155 | ||
|
|
cf4d7ba80b | ||
|
|
17f77f9ae7 | ||
|
|
1ff5de1443 | ||
|
|
79b4dc0333 | ||
|
|
ab6ea1f4c0 | ||
|
|
8cfb586bee | ||
|
|
31e7016948 | ||
|
|
4574acfc49 | ||
|
|
f6e2bcaad7 | ||
|
|
2518199be2 | ||
|
|
e5cbd82343 | ||
|
|
46ac451284 | ||
|
|
e0c6baf1ff | ||
|
|
e1eff07478 | ||
|
|
eed485bd2c | ||
|
|
47ff7f0b64 | ||
|
|
26fb822da1 | ||
|
|
d0064a4b0b | ||
|
|
5169e8fda4 | ||
|
|
6bc983d50b | ||
|
|
80a188e6a3 | ||
|
|
65681a5b74 | ||
|
|
ea19ad0c1f | ||
|
|
ddeeab7d80 | ||
|
|
f1760b67ee | ||
|
|
01f7acb4d6 | ||
|
|
40991cf6d1 | ||
|
|
beab5f9e96 | ||
|
|
ad7f773632 | ||
|
|
d77185ded4 | ||
|
|
4bcfbb9bee | ||
|
|
5a1d37d607 | ||
|
|
c6e68ff9b1 | ||
|
|
8506f5948f | ||
|
|
a11155e441 | ||
|
|
3a3c6f5507 | ||
|
|
106b33a57f | ||
|
|
ea27eeb929 | ||
|
|
1ae10f553b | ||
|
|
c6cde35203 | ||
|
|
7825f69e06 | ||
|
|
cc08e0a614 | ||
|
|
3e303f2c95 | ||
|
|
7e971a6c4d | ||
|
|
09116830c9 | ||
|
|
80d9ce8553 | ||
|
|
d2dc255bc5 | ||
|
|
f0066bc2aa | ||
|
|
29c3737704 | ||
|
|
97f2e93f8a | ||
|
|
b1ad51f5b9 | ||
|
|
7236a7074c | ||
|
|
925ae8d0c1 | ||
|
|
a84b18f22c | ||
|
|
b5589d53bb | ||
|
|
cf9c567e29 | ||
|
|
f1aed0b632 | ||
|
|
35bf943244 | ||
|
|
926a292c9c | ||
|
|
068bfa0ec5 | ||
|
|
686eb787c9 | ||
|
|
95ddea9fb0 | ||
|
|
fe83a20c4d | ||
|
|
2453ae095f | ||
|
|
1412ad4da4 | ||
|
|
03e911ecd5 | ||
|
|
6b26811229 | ||
|
|
4ed86ce182 | ||
|
|
3393c55768 | ||
|
|
ed949c07b1 | ||
|
|
44b4c53d04 | ||
|
|
91fe8ced6e | ||
|
|
8a269858c5 | ||
|
|
d677db7f4e | ||
|
|
91c898f19c | ||
|
|
282b932a8e | ||
|
|
78ce57e19b | ||
|
|
b81aafbc23 | ||
|
|
33264ba59a | ||
|
|
5affa4f9fd | ||
|
|
90d6580377 | ||
|
|
c0c1228e10 | ||
|
|
d16908d463 | ||
|
|
c360db9698 | ||
|
|
407c3f3c95 | ||
|
|
3e9f0f9384 | ||
|
|
863ed888ad | ||
|
|
93ee75a339 | ||
|
|
352b97910a | ||
|
|
0a645281c6 | ||
|
|
9da684b7c6 | ||
|
|
5547f0635c | ||
|
|
2166bbfe03 | ||
|
|
853d4d6b59 | ||
|
|
a426839b3b | ||
|
|
10d42f352b | ||
|
|
e8f1bca17e | ||
|
|
2d34925725 | ||
|
|
dcef017f9b | ||
|
|
468b39bc32 | ||
|
|
ce9cadc46d | ||
|
|
fdc53927ca | ||
|
|
3d529d19fa | ||
|
|
2fd4a37ee4 | ||
|
|
4fb6ba9530 | ||
|
|
04ab7444a4 | ||
|
|
df88d8bad6 | ||
|
|
4afbd65e3b | ||
|
|
0c44785a07 | ||
|
|
55fc4e9777 | ||
|
|
df64f8864f | ||
|
|
aee6f45a8b | ||
|
|
500e0862f3 | ||
|
|
ed38be57bb | ||
|
|
c937cb591e | ||
|
|
b1b7bc4463 | ||
|
|
38acb89b93 | ||
|
|
28c20f4d21 | ||
|
|
fa73a0a704 | ||
|
|
0f617a8601 | ||
|
|
5801073f84 | ||
|
|
2cf60fff10 | ||
|
|
a8abec196b | ||
|
|
777486ce2c | ||
|
|
41a833c258 | ||
|
|
c94d353b54 | ||
|
|
7ab44507f5 | ||
|
|
640ed18f90 | ||
|
|
bfbd50e8cb | ||
|
|
c7bcd20a38 | ||
|
|
ed00876800 | ||
|
|
dd5db5b32c | ||
|
|
1989fd25a1 | ||
|
|
c7ce31d00c | ||
|
|
46515ce695 | ||
|
|
64e2fb73a8 | ||
|
|
e61a971790 | ||
|
|
449ba3e3a9 | ||
|
|
3fbf1e1fec | ||
|
|
702882c6ee | ||
|
|
d82f06593d | ||
|
|
a3e07347a4 | ||
|
|
409ec19959 | ||
|
|
f46d7ee426 | ||
|
|
aba1d844f8 | ||
|
|
103332f6cd | ||
|
|
577fad6d5f | ||
|
|
6a507fdad1 | ||
|
|
055c5388e0 | ||
|
|
6c409b6fb1 | ||
|
|
a99aec05eb | ||
|
|
b26b972d3d | ||
|
|
00386eba24 | ||
|
|
bce0ac79cb | ||
|
|
72d389a7c3 | ||
|
|
d7331455fc | ||
|
|
d1d2292028 | ||
|
|
1df24eb11f | ||
|
|
bc24f006ef | ||
|
|
12e21c935c | ||
|
|
97209caa9c | ||
|
|
d78c83b7d3 | ||
|
|
8568302f38 | ||
|
|
c9b911836b | ||
|
|
30f19b245d | ||
|
|
77570c48e9 | ||
|
|
e9d7e224b0 | ||
|
|
fc531021fb | ||
|
|
0d11c3688e | ||
|
|
463942df80 | ||
|
|
4d1d765cbe | ||
|
|
a714e3c563 | ||
|
|
72d20a3546 | ||
|
|
1910d68430 | ||
|
|
819f96da6c | ||
|
|
ea9c659fb1 | ||
|
|
a3061afebe | ||
|
|
ba556bee0b | ||
|
|
58933a8270 | ||
|
|
3f636465f6 | ||
|
|
8e1c53bd4e | ||
|
|
425f0ccc1b | ||
|
|
3779372674 | ||
|
|
a0e1bdb963 | ||
|
|
893011e7bf | ||
|
|
4fc01afbaa | ||
|
|
46fad3c5e7 | ||
|
|
adcf70da76 | ||
|
|
8af6365c5d | ||
|
|
8c9b46b0df | ||
|
|
0054ce5847 | ||
|
|
d73fcbd2b3 | ||
|
|
47a7656ec4 | ||
|
|
386d958c88 | ||
|
|
bfe38b012e | ||
|
|
b59cd0da92 | ||
|
|
3355d23218 | ||
|
|
dc57f6b027 | ||
|
|
05e01f3a76 | ||
|
|
effbfac7d9 | ||
|
|
8839edc377 | ||
|
|
e3ce522588 | ||
|
|
110589d332 | ||
|
|
95ec42bba7 | ||
|
|
d15d208ad3 | ||
|
|
c1fabb2181 | ||
|
|
6280110c9f | ||
|
|
b90837edb6 | ||
|
|
1214bf6e7e | ||
|
|
e3d095464c | ||
|
|
4847c84347 | ||
|
|
899b1b79a5 | ||
|
|
2f07853975 | ||
|
|
e44c0a8250 | ||
|
|
7ace27c4f9 | ||
|
|
30bcd20ac5 | ||
|
|
ef73336aa5 | ||
|
|
4f650e939f | ||
|
|
38490a6163 | ||
|
|
fd4ebde1fb | ||
|
|
10731e326d | ||
|
|
20a31bd364 | ||
|
|
0ad6d290ae | ||
|
|
f2fb98608a | ||
|
|
521ba5adbd | ||
|
|
ee47d98300 | ||
|
|
e707b1b74f | ||
|
|
0ea46f5048 | ||
|
|
c7b415b2f4 | ||
|
|
a94440fa1f | ||
|
|
2e582e7874 | ||
|
|
e92ad2132f | ||
|
|
6cbc5028cf | ||
|
|
fcb3993a47 | ||
|
|
a8462ab5a3 | ||
|
|
ec149d3227 | ||
|
|
20c276e63e | ||
|
|
ef7a5b6610 | ||
|
|
ffe2ae4e0b | ||
|
|
d69e8a292e | ||
|
|
a048fad7cd | ||
|
|
d9a130cc55 | ||
|
|
dfd45c8678 | ||
|
|
4a430622f4 | ||
|
|
af1e129b6b | ||
|
|
9788f189b1 | ||
|
|
5e22357f9a | ||
|
|
51c1b7f31f | ||
|
|
883fdc583a | ||
|
|
343f8c4864 | ||
|
|
2920953184 | ||
|
|
f593e6be94 | ||
|
|
9d31b143ed | ||
|
|
4be1977925 | ||
|
|
aa36cc09fa | ||
|
|
f635f07ebd | ||
|
|
18dca9407c | ||
|
|
e40d387475 | ||
|
|
12974f6f6b | ||
|
|
228f7591d5 | ||
|
|
0fb72e452f | ||
|
|
2c21abc2d9 | ||
|
|
c2b8459645 | ||
|
|
48fc643952 | ||
|
|
3a27719392 | ||
|
|
813868f16f | ||
|
|
a903c221db | ||
|
|
6d9d6b3d28 | ||
|
|
e96bbb84a4 | ||
|
|
719a8dcafe | ||
|
|
ad9ad8d1d3 | ||
|
|
43cf732ad4 | ||
|
|
97eb110f0e | ||
|
|
4a23accbde | ||
|
|
e41ffeac68 | ||
|
|
785ed5f169 | ||
|
|
39f48cd74e | ||
|
|
5840f9b779 | ||
|
|
b3e4b9a8ca | ||
|
|
5b3732d9f2 | ||
|
|
27bb5bbb0f | ||
|
|
60b7c58af8 | ||
|
|
bf25c92b30 | ||
|
|
6b75609012 | ||
|
|
1ad93c2977 | ||
|
|
62668161b2 | ||
|
|
a14b15d123 | ||
|
|
7f075fcdd3 | ||
|
|
4648f46f51 | ||
|
|
c47693b9b0 | ||
|
|
66ee0c5449 | ||
|
|
0384efc38f | ||
|
|
a12d3cc354 | ||
|
|
13232200c0 | ||
|
|
5c6c7d1c3e | ||
|
|
4e36ae4679 | ||
|
|
f96f4d9117 | ||
|
|
d7089eac96 | ||
|
|
4ecd0430b8 | ||
|
|
31783d9b78 | ||
|
|
8ced04ae97 | ||
|
|
32b7780ff3 | ||
|
|
d99fbde101 | ||
|
|
37360e6d8c | ||
|
|
941738c26f | ||
|
|
a01bfe3688 | ||
|
|
3b3493df94 | ||
|
|
bdb0c576a3 | ||
|
|
04f7e67384 | ||
|
|
5b7e162e4d | ||
|
|
bbab0399cd | ||
|
|
1e3f7038ee | ||
|
|
2da861c2d2 | ||
|
|
17e87a5f53 | ||
|
|
e3a498faf0 | ||
|
|
86bbbcdda8 | ||
|
|
fd7907f893 | ||
|
|
204e5444c8 | ||
|
|
2c138cb027 | ||
|
|
348156043a | ||
|
|
79606f81ed | ||
|
|
1ded99c7de | ||
|
|
28c1fd39a4 | ||
|
|
ff448cbb3c | ||
|
|
833d6bf932 | ||
|
|
e5a6acf28d | ||
|
|
7724b7a2db | ||
|
|
e1085e1907 | ||
|
|
6447f8fbf7 | ||
|
|
fd0e783176 | ||
|
|
93fd485622 | ||
|
|
7d85424948 | ||
|
|
b93c0aa390 | ||
|
|
217551f960 | ||
|
|
9e03507dd4 | ||
|
|
b3822c4b75 | ||
|
|
c3ca1c4804 | ||
|
|
f669f10d9e | ||
|
|
983865d31f | ||
|
|
e0fd94152a | ||
|
|
0b44afa4f6 | ||
|
|
323f58d566 | ||
|
|
25ecfa733f | ||
|
|
6b18168c3c | ||
|
|
6157e3023b | ||
|
|
19215ee009 | ||
|
|
f3fbae66b5 | ||
|
|
ba20728f40 | ||
|
|
9614c3143b | ||
|
|
942e90ae2a | ||
|
|
bfde33d78d | ||
|
|
da28f3317b | ||
|
|
28fe9e2113 | ||
|
|
5f00c89c0a | ||
|
|
5010c6c370 | ||
|
|
8e3fb2f67b | ||
|
|
1428dc4a90 | ||
|
|
88446b17f9 | ||
|
|
54c54199d7 | ||
|
|
c39c9ad4ca | ||
|
|
4b7319afe9 | ||
|
|
d05093f84f | ||
|
|
d556c9b699 | ||
|
|
d4a8cbd8d8 | ||
|
|
9559fb4bf0 | ||
|
|
fb4bb02f1a | ||
|
|
a2b9aef152 | ||
|
|
c772f1b0fe | ||
|
|
6274addad9 | ||
|
|
cf9d296d08 | ||
|
|
ba35fafad4 | ||
|
|
898c5e9bcf | ||
|
|
577171bc5c | ||
|
|
a205cc1505 | ||
|
|
35806f1036 | ||
|
|
d7240e073a | ||
|
|
766d72bd75 | ||
|
|
f200f39779 | ||
|
|
69b610269b | ||
|
|
7213da587c | ||
|
|
0635bd201d | ||
|
|
359e545b69 | ||
|
|
e8f660c2c7 | ||
|
|
4c45fa7768 | ||
|
|
566df336f5 | ||
|
|
a071340564 | ||
|
|
667ad5a532 | ||
|
|
1645dacd1f | ||
|
|
ed9682bf58 | ||
|
|
e101a9311c | ||
|
|
e041b57807 | ||
|
|
4bb8bc94ff | ||
|
|
cbbc3cfb9e | ||
|
|
33e3db9f19 | ||
|
|
bc01fe0dc7 | ||
|
|
468b669c14 | ||
|
|
b2e51990c9 | ||
|
|
4e51a9c778 | ||
|
|
63436955c6 | ||
|
|
8b70da517c | ||
|
|
f8153c6fb9 | ||
|
|
313425f6f8 | ||
|
|
8f874a6b4f | ||
|
|
b6e7019588 | ||
|
|
46f899ac95 | ||
|
|
9141fbf06c | ||
|
|
d54ea96bba | ||
|
|
e69fb8b1b8 | ||
|
|
ddc127f591 | ||
|
|
aa8b138945 | ||
|
|
86f61e5ac9 | ||
|
|
a00773d5aa | ||
|
|
6070213232 | ||
|
|
4bd5494370 | ||
|
|
64048ccf1d | ||
|
|
dfab1d6fa8 | ||
|
|
23afa1e05a | ||
|
|
f707d3893d | ||
|
|
f49d79d0e4 | ||
|
|
fea132ef24 | ||
|
|
36beca9c7c | ||
|
|
01638ad7be | ||
|
|
c3ae7ab0a2 | ||
|
|
8d9e7764aa | ||
|
|
3b99bf60c7 | ||
|
|
cc927d2e43 | ||
|
|
bedcccb633 | ||
|
|
4a755a0c23 | ||
|
|
c58ef27224 | ||
|
|
1cdec1e05f | ||
|
|
6f2a193a50 | ||
|
|
49b2069f0f | ||
|
|
2ce773705d | ||
|
|
0e5087ca8f | ||
|
|
ae0c589d1e | ||
|
|
21e68b6b4a | ||
|
|
a7c60a56b7 | ||
|
|
d7c577061e | ||
|
|
bc5b9478df | ||
|
|
476d5a9b14 | ||
|
|
0d68bec358 | ||
|
|
3d704bd321 | ||
|
|
e0f5ac9e7c | ||
|
|
d3742867ed | ||
|
|
6fda3d84dd | ||
|
|
f5f5ab4ef1 | ||
|
|
2de7e49eeb | ||
|
|
fcebd5f726 | ||
|
|
c204915e1d | ||
|
|
6114ff0d3c | ||
|
|
aded05f493 | ||
|
|
76d374a453 | ||
|
|
4f67ff0c35 | ||
|
|
cca835e016 | ||
|
|
ec55e00049 | ||
|
|
b7c2e8a3ec | ||
|
|
0e720dd9d3 | ||
|
|
f1de182ec6 | ||
|
|
2178a3a408 | ||
|
|
0e81035039 | ||
|
|
f75fab89b0 | ||
|
|
9336a6c130 | ||
|
|
85a16a2f04 | ||
|
|
931b8c7176 | ||
|
|
f83fca56d0 | ||
|
|
9aab707ec7 | ||
|
|
473a5fd98b | ||
|
|
5979d8d0cf | ||
|
|
24f3952c49 | ||
|
|
afc977e044 | ||
|
|
856114019e | ||
|
|
b9e0ff230f | ||
|
|
29914b3220 | ||
|
|
37b4d0d6e2 | ||
|
|
bfbb5e2870 | ||
|
|
32910e76e3 | ||
|
|
c3b2f4691b | ||
|
|
3d240c72f8 | ||
|
|
80505f0440 | ||
|
|
cee882502c | ||
|
|
bf95a4112e | ||
|
|
08526d47bf | ||
|
|
75b7fdd069 | ||
|
|
ae70baf5c4 | ||
|
|
37b5aec264 | ||
|
|
2d22b170d4 | ||
|
|
d796d7baf1 | ||
|
|
d07b813739 | ||
|
|
93fcbb8f61 | ||
|
|
a11f372091 | ||
|
|
eb9c45bbb7 | ||
|
|
d91b07a785 | ||
|
|
2c2c4fed76 | ||
|
|
f31bbe4876 | ||
|
|
d81fb2665f | ||
|
|
e12fe4492b | ||
|
|
e8ace6838f | ||
|
|
854d96af72 | ||
|
|
3a9d9af815 | ||
|
|
65dfbd8bbb | ||
|
|
4a3af34c6a | ||
|
|
6552e6d015 | ||
|
|
d63cde85b9 | ||
|
|
ef32f7179e | ||
|
|
54590f8157 | ||
|
|
125fac770a | ||
|
|
95032e8e06 | ||
|
|
d24969387c | ||
|
|
23e79d3e17 | ||
|
|
4837413989 | ||
|
|
a5123992c9 | ||
|
|
33783e06d8 | ||
|
|
0e1905ad22 | ||
|
|
5747070e39 | ||
|
|
8ceb27421e | ||
|
|
e2d596be20 | ||
|
|
a432019b23 | ||
|
|
df2026f107 | ||
|
|
c055941738 | ||
|
|
4a0a99e264 | ||
|
|
4ae2af21a5 | ||
|
|
49019eaee2 | ||
|
|
41f7ea654e | ||
|
|
5dea03027a | ||
|
|
26acdbed61 | ||
|
|
1e7c3e1555 | ||
|
|
73c11600ba | ||
|
|
6e7520bfea | ||
|
|
45b31d304c | ||
|
|
5eeb925384 | ||
|
|
4e122abbce | ||
|
|
324f477b63 | ||
|
|
e2494467ea | ||
|
|
4a44b12384 | ||
|
|
ebe616247f | ||
|
|
cab8153201 | ||
|
|
b1d3b71df7 | ||
|
|
57045fa8e1 | ||
|
|
00c76df04b | ||
|
|
d402cfca52 | ||
|
|
762a0d760e | ||
|
|
7052bccfbc | ||
|
|
ee91d7d79c | ||
|
|
1d627ce5b9 | ||
|
|
efb7108c58 | ||
|
|
63517b6b1c | ||
|
|
83542b310c | ||
|
|
9b4506de3b | ||
|
|
e07733e5c3 | ||
|
|
82f4df638e | ||
|
|
55d207ac9a | ||
|
|
b4f21b2f09 | ||
|
|
4f9ee9f19f | ||
|
|
82070f9918 | ||
|
|
f08ed17509 | ||
|
|
a1e4c06bc7 | ||
|
|
f3d63806c3 | ||
|
|
5190b486c4 | ||
|
|
ea426d6650 | ||
|
|
c297186aee | ||
|
|
570a516cbf | ||
|
|
4ac2196ddf | ||
|
|
348ec65059 | ||
|
|
2bcee59d39 | ||
|
|
6efac43210 | ||
|
|
211b6f9115 | ||
|
|
fbcc9cf3e2 | ||
|
|
6967c0c22f | ||
|
|
4363a16a62 | ||
|
|
7c42b3de10 | ||
|
|
5de28e999e | ||
|
|
9993590d45 | ||
|
|
786d0b2899 | ||
|
|
5c3418ca8e | ||
|
|
22cdce5102 | ||
|
|
ca131b2c26 | ||
|
|
512d96d384 | ||
|
|
a36592485c | ||
|
|
87577726ec | ||
|
|
5bd45b685f | ||
|
|
8dbb636a14 | ||
|
|
0a3f093d60 | ||
|
|
66b5efaacb | ||
|
|
fd74216249 | ||
|
|
e508547914 | ||
|
|
f47692773c | ||
|
|
562480225c | ||
|
|
ab35d37766 | ||
|
|
d623812c24 | ||
|
|
20897596b0 | ||
|
|
9ff6a5bc85 | ||
|
|
2e371300d3 | ||
|
|
f2a7c574e5 | ||
|
|
96ca181f2d | ||
|
|
d00c551027 | ||
|
|
75211903d7 | ||
|
|
f7e1b85091 | ||
|
|
fedf63b759 | ||
|
|
1d2b0bbed7 | ||
|
|
41af90e2eb | ||
|
|
1ca3d68d7e | ||
|
|
02ad6129d6 | ||
|
|
79727f0195 | ||
|
|
d53edf7b61 | ||
|
|
48a70cb6e5 | ||
|
|
9b7c2587fe | ||
|
|
b6cabe8fb8 | ||
|
|
a5dc01d85e | ||
|
|
8a0e2d780a | ||
|
|
914cbfb9dc | ||
|
|
6d56ee51db | ||
|
|
0554500f2f | ||
|
|
e9fbce2106 | ||
|
|
096a404859 | ||
|
|
44a4f2f9f3 | ||
|
|
b3f6b2756e | ||
|
|
499b23c335 | ||
|
|
c41eb6b517 | ||
|
|
612a67f5ed | ||
|
|
6547d0efb4 | ||
|
|
0c7456d7a0 | ||
|
|
0abb23e26a | ||
|
|
15e2c131b2 | ||
|
|
ceba630cff | ||
|
|
8239855e88 | ||
|
|
538bb72556 | ||
|
|
769e372e8f | ||
|
|
80620b6aac | ||
|
|
d2ab8dd4d4 | ||
|
|
f905e79f4c | ||
|
|
562fe2e5e6 | ||
|
|
330d79379f | ||
|
|
a185e52203 | ||
|
|
1e1ab787bc | ||
|
|
e617cac69c | ||
|
|
2813d536bb | ||
|
|
acbde736f0 | ||
|
|
b20a97e5a1 | ||
|
|
4268830d18 | ||
|
|
1cdf058938 | ||
|
|
35b636a27d | ||
|
|
97f69f9498 | ||
|
|
1a10dade25 | ||
|
|
507988832f | ||
|
|
a694e705ea | ||
|
|
d1ebb831ef | ||
|
|
c5a4e83e3e | ||
|
|
035f950229 | ||
|
|
35ffb65df0 | ||
|
|
74f0c983d3 | ||
|
|
fe06a22589 | ||
|
|
6cada5bd09 | ||
|
|
e589ddae5a | ||
|
|
9d8c74ec1e | ||
|
|
d7acffd8da | ||
|
|
a9d81b19ed | ||
|
|
494cc7fb1e | ||
|
|
fed2d50a9a | ||
|
|
939553cc85 | ||
|
|
490ac908b8 | ||
|
|
df78d5f162 | ||
|
|
b22de50107 | ||
|
|
63cca0f30f | ||
|
|
4bdd2c6d9f | ||
|
|
2c0c417c22 | ||
|
|
504eb00819 | ||
|
|
acaf62f31f | ||
|
|
24a3165634 | ||
|
|
d0a1cd5fa0 | ||
|
|
d3827fc823 | ||
|
|
2d9896f6ed | ||
|
|
0dcdb2188a | ||
|
|
b2f79e2965 | ||
|
|
0b2650127a | ||
|
|
2884fdd00a | ||
|
|
82818db873 | ||
|
|
066aa8f3f7 | ||
|
|
927254f6ce | ||
|
|
e8e565fe8c | ||
|
|
82907f5d8f | ||
|
|
bab1112251 | ||
|
|
6bced51764 | ||
|
|
a5d0c27d6b | ||
|
|
5bc1c9c688 | ||
|
|
703a127f0e | ||
|
|
54dd9aa814 | ||
|
|
f32d7a7daf | ||
|
|
96b256bd4c | ||
|
|
4581dcc1d1 | ||
|
|
a3444f0c5c | ||
|
|
b5011d6b1d | ||
|
|
8727dabe27 | ||
|
|
56ac4ab982 | ||
|
|
c3b5d62ea6 | ||
|
|
43058193df | ||
|
|
9d19183786 | ||
|
|
102d2e13f9 | ||
|
|
48fe6f2740 | ||
|
|
15ca5ad4ec | ||
|
|
dacf880b88 | ||
|
|
f86719f54c | ||
|
|
ad47f48a79 | ||
|
|
3594c846da | ||
|
|
a3b64e4021 | ||
|
|
ecdb4ba130 | ||
|
|
bfdf039ef3 | ||
|
|
4f7bde100c | ||
|
|
6030e54cd7 | ||
|
|
51d8ac47cf | ||
|
|
55e241dc97 | ||
|
|
04f6474799 | ||
|
|
7d23983309 | ||
|
|
00773d7457 | ||
|
|
fcb90f6f11 | ||
|
|
3188f98117 | ||
|
|
20a7dad9a0 | ||
|
|
13cc82e3ed | ||
|
|
22c16c1ec9 | ||
|
|
002ad048b0 | ||
|
|
3dfcb47534 | ||
|
|
dbb276e470 | ||
|
|
ef6af9a967 | ||
|
|
c0318de646 | ||
|
|
99640bd70c | ||
|
|
ec909a1625 | ||
|
|
ae9963aec3 | ||
|
|
17b6851d00 | ||
|
|
aba8f3549f | ||
|
|
a06af6da0a | ||
|
|
c5097ab879 | ||
|
|
7be870369c | ||
|
|
4f95cef08f | ||
|
|
6565e0af18 | ||
|
|
ebec84b3c0 | ||
|
|
2657e64df8 | ||
|
|
418ce51805 | ||
|
|
c17160602c | ||
|
|
5d42e53b18 | ||
|
|
7f97340667 | ||
|
|
f5d9990cc8 | ||
|
|
7a6a958179 | ||
|
|
aa142eed07 | ||
|
|
6df4a04f84 | ||
|
|
28317b6227 | ||
|
|
18489be4d1 | ||
|
|
c6fb383adf | ||
|
|
e9b11af986 | ||
|
|
6a38c2eaa2 | ||
|
|
a1d53ee6f0 | ||
|
|
6ed4369647 | ||
|
|
4300e87485 | ||
|
|
76a023c33a | ||
|
|
966a5c9a04 | ||
|
|
72b5035733 | ||
|
|
e72df20b60 | ||
|
|
5d7d6278ee | ||
|
|
f21c0df652 | ||
|
|
484c22b649 | ||
|
|
70b31856e9 | ||
|
|
d181ce73e6 | ||
|
|
c546af7865 | ||
|
|
6d947710d1 | ||
|
|
e0b1ec165c | ||
|
|
7695e8e46e | ||
|
|
3de86d975e | ||
|
|
d232cecbb0 | ||
|
|
00f1490eff | ||
|
|
494ff2ac71 | ||
|
|
41b65521f9 | ||
|
|
eaaf485325 | ||
|
|
8a31522444 | ||
|
|
6ede7eac16 | ||
|
|
95fec47897 | ||
|
|
88872f31dd | ||
|
|
ef0b659ad1 | ||
|
|
af6608585c | ||
|
|
262531799b | ||
|
|
645429d8d8 | ||
|
|
872986926e | ||
|
|
d9ced25e98 | ||
|
|
128b58127b | ||
|
|
dfb28abbe7 | ||
|
|
8604a672bd | ||
|
|
0f82c0ec05 | ||
|
|
5b2af29efd | ||
|
|
bf139aa634 | ||
|
|
db5815ec09 | ||
|
|
693aa0c9f7 | ||
|
|
3e54156abf | ||
|
|
5dd7d25762 | ||
|
|
98631721fa | ||
|
|
c9574b0d09 | ||
|
|
1afb77e63e | ||
|
|
13536e69af | ||
|
|
9d85ea234e | ||
|
|
9f8724ee4c | ||
|
|
cab71a0e00 | ||
|
|
d1cd653ed5 | ||
|
|
f3aaacb6ca | ||
|
|
3eee533a17 | ||
|
|
5de28c062e | ||
|
|
71fdce8050 | ||
|
|
361e3b6f76 | ||
|
|
b3323c78cf | ||
|
|
a6cb55221b | ||
|
|
39b4c866a1 | ||
|
|
4580812b64 | ||
|
|
5e3476673e | ||
|
|
ca3a63a885 | ||
|
|
59501f2ca5 | ||
|
|
9646152550 | ||
|
|
7d11c95be8 | ||
|
|
140a051fc9 | ||
|
|
3bc2f6aba7 | ||
|
|
eb16534ad9 | ||
|
|
b9d3f921e9 | ||
|
|
1c4ec47f1a | ||
|
|
6d5e2a1a9f | ||
|
|
5b49156a5f | ||
|
|
a4deb42d3d | ||
|
|
103804683d | ||
|
|
50fed7ae42 | ||
|
|
da7285856c | ||
|
|
de8462803d | ||
|
|
9097762a53 | ||
|
|
9954f8e0da | ||
|
|
085d7b4c95 | ||
|
|
d1a7b03b55 | ||
|
|
14da838637 | ||
|
|
0211ff78e1 | ||
|
|
8b6769d7e8 | ||
|
|
00532b564b | ||
|
|
19de364958 | ||
|
|
91ca3af477 | ||
|
|
079b343981 | ||
|
|
c60c7235c1 | ||
|
|
e79c30f765 | ||
|
|
b6ea00daa4 | ||
|
|
ab06501e04 | ||
|
|
8b1d3b478c | ||
|
|
b1724ec235 | ||
|
|
1ef7be0af6 | ||
|
|
e7b727c0ba | ||
|
|
6e48bcfd9e | ||
|
|
f02c97e622 | ||
|
|
a174eb4b85 | ||
|
|
f0c8e19e64 | ||
|
|
14b35a93c0 | ||
|
|
38b57df883 | ||
|
|
eee7cc1397 | ||
|
|
90205c6330 | ||
|
|
2078867d7b | ||
|
|
a24a40f4bc | ||
|
|
5f4d6c0f8d | ||
|
|
a43a32fc55 | ||
|
|
d76358c37f | ||
|
|
5e9c6a0677 | ||
|
|
b115313955 | ||
|
|
b49f38ca4b | ||
|
|
e79261a566 | ||
|
|
1d4548fb3b | ||
|
|
e286108bb5 | ||
|
|
81b6d34693 | ||
|
|
b16575b407 | ||
|
|
f01be72d8f | ||
|
|
e1e018fcfc | ||
|
|
16e3a682fe | ||
|
|
789dec673e | ||
|
|
54284be74b | ||
|
|
8e987847ee | ||
|
|
28005ae633 | ||
|
|
b31f753c11 | ||
|
|
a5c8e5125c | ||
|
|
553e37b701 | ||
|
|
90231f76cc | ||
|
|
d9696e77eb | ||
|
|
d0c5040fe9 | ||
|
|
eb14d632d9 | ||
|
|
2fadf0c82f | ||
|
|
c1aa0d0004 | ||
|
|
5d5dde33b7 | ||
|
|
5172a95f53 | ||
|
|
352c97f25c | ||
|
|
3f9d671fb1 | ||
|
|
ac697c0bd8 | ||
|
|
48a43eb268 | ||
|
|
a870650faf | ||
|
|
7fd44d0247 | ||
|
|
f9eee73ef4 | ||
|
|
f491266105 | ||
|
|
cdd1bdbfbd | ||
|
|
411d8e9034 | ||
|
|
e826fc4a31 | ||
|
|
5e3fd4bbf8 | ||
|
|
adb802f28e | ||
|
|
31722b2f72 | ||
|
|
a65dcd518d | ||
|
|
b62821cb74 | ||
|
|
62f06582ef | ||
|
|
222b0136bf | ||
|
|
720263ae2d | ||
|
|
b77289937f | ||
|
|
bf4097aa60 | ||
|
|
d755a529ef | ||
|
|
79d8bfb864 | ||
|
|
b57d6f5d21 | ||
|
|
baf43f9780 | ||
|
|
4d7cda0001 | ||
|
|
0c9bbb5dde | ||
|
|
9cd81c62eb | ||
|
|
9609595525 | ||
|
|
6a351419d6 | ||
|
|
a9f8dff7e9 | ||
|
|
9b8ddb0580 | ||
|
|
87e6363686 | ||
|
|
88da732d5c | ||
|
|
13d8c72a82 | ||
|
|
fe79365c63 | ||
|
|
5e9499873d | ||
|
|
8cbb8089fb | ||
|
|
5df8763166 | ||
|
|
6a5ec2d953 | ||
|
|
7e47ecd91e | ||
|
|
894ba19345 | ||
|
|
9d3bda9bce | ||
|
|
014cbc3c4d | ||
|
|
66def746e6 | ||
|
|
0d577da288 | ||
|
|
9ad013253a | ||
|
|
81ae388ac4 | ||
|
|
880c0e6802 | ||
|
|
8d3ee36392 | ||
|
|
a642946665 | ||
|
|
895c6490bb | ||
|
|
0b1a1c03dc | ||
|
|
b69969942a | ||
|
|
c71e1b9f8b | ||
|
|
611436a943 | ||
|
|
ee26147dde | ||
|
|
4350b69a18 | ||
|
|
caaa6108d8 | ||
|
|
a53e8f708c | ||
|
|
d9e82f4ffc | ||
|
|
b89dcb7fca | ||
|
|
e94173aa12 | ||
|
|
958e1dcb9c | ||
|
|
9bf0ec9de7 | ||
|
|
cb22a95df2 | ||
|
|
00297db3ab | ||
|
|
00fe02c1d1 | ||
|
|
77062e2e4e | ||
|
|
f33c2f14e5 | ||
|
|
e6184e4bd8 | ||
|
|
f8c925c265 | ||
|
|
da5a0c8749 | ||
|
|
ecaa6874ce | ||
|
|
67422a5cd8 | ||
|
|
81f0cf72ae | ||
|
|
ef5d026074 | ||
|
|
b6700f8adb | ||
|
|
efcd797648 | ||
|
|
f4b9aaffef | ||
|
|
6aa6c9d34f | ||
|
|
1490b51f71 | ||
|
|
4524177931 | ||
|
|
f1643492bc | ||
|
|
b565c65628 | ||
|
|
c6a4a00fda | ||
|
|
4ba4c12a87 | ||
|
|
f50ef5be9a | ||
|
|
b4c0fb9351 | ||
|
|
2346f06d23 | ||
|
|
cdb8e3a08a | ||
|
|
9df2b51813 | ||
|
|
0daffaf4ad | ||
|
|
96dc9bd840 | ||
|
|
cde9f0a5c8 | ||
|
|
25ef949a97 | ||
|
|
7b2c22f0ba | ||
|
|
74d8fd54d9 | ||
|
|
7572188ab0 | ||
|
|
7a117150ef | ||
|
|
837cc31a93 | ||
|
|
aa88d1c8f4 | ||
|
|
456016959a | ||
|
|
825e80a5a0 | ||
|
|
7760b95a41 | ||
|
|
73d0f75844 | ||
|
|
a9b7f061fe | ||
|
|
72bf31a828 | ||
|
|
a959ff78c9 | ||
|
|
257044b210 | ||
|
|
92b9fe252a | ||
|
|
9f5ac1af94 | ||
|
|
b85633fa6e | ||
|
|
35cc38a947 | ||
|
|
bcef0650dc | ||
|
|
1a33d464f0 | ||
|
|
56bb3dba6a | ||
|
|
4f86b3cd31 | ||
|
|
c27be69d92 | ||
|
|
d48924ed01 | ||
|
|
3921841630 | ||
|
|
c617fcc6e4 | ||
|
|
93ef263c5e | ||
|
|
e09735a3c2 | ||
|
|
c6b784266b | ||
|
|
7336ccb660 | ||
|
|
373e3b953d | ||
|
|
e430c02c7f | ||
|
|
8e4956891c | ||
|
|
46361868ed | ||
|
|
80192ea42a | ||
|
|
7b82c5b00c | ||
|
|
e75db3b73d | ||
|
|
b3d96d8b53 | ||
|
|
0db4a60399 | ||
|
|
bc99026521 | ||
|
|
2779e2e18c | ||
|
|
0b37cb6634 | ||
|
|
cce349ff15 | ||
|
|
9f5276a497 | ||
|
|
e9a2cf361d | ||
|
|
8a4606f5d3 | ||
|
|
e7249002e5 | ||
|
|
7a57b4f51c | ||
|
|
10e779c1e9 | ||
|
|
3d56c52291 | ||
|
|
def10dea18 | ||
|
|
8b811ae65f | ||
|
|
94493d1051 | ||
|
|
593235a09f | ||
|
|
6447545ec4 | ||
|
|
d6b6d92122 | ||
|
|
ded84933b2 | ||
|
|
aa93d26b80 | ||
|
|
31655f2c35 | ||
|
|
97aeb48109 | ||
|
|
bbb84cd849 | ||
|
|
0113925b92 | ||
|
|
250dec1ae6 | ||
|
|
8fc244b271 | ||
|
|
bfe39185fa | ||
|
|
45555782c6 | ||
|
|
a893cce2f7 | ||
|
|
df57293ebb | ||
|
|
33934f1e05 | ||
|
|
a9bd1a1a5c | ||
|
|
6066554a7a | ||
|
|
59e6ee8240 | ||
|
|
2f3a6ab921 | ||
|
|
bd35f5e025 | ||
|
|
a1ba0894d9 | ||
|
|
4b0666c617 | ||
|
|
ef3125b48e | ||
|
|
691bc1d4e6 | ||
|
|
88c30645e7 | ||
|
|
0b1df5b3d5 | ||
|
|
4ff5fe895b | ||
|
|
ae5645251d | ||
|
|
6b302a051f | ||
|
|
2aec310c1e | ||
|
|
dc1f975f13 | ||
|
|
5b2098c9f7 | ||
|
|
4193094232 | ||
|
|
d84a134904 | ||
|
|
a05cde7256 | ||
|
|
ea20f0be48 | ||
|
|
dd3bcc8f73 | ||
|
|
dadf6ffb1d | ||
|
|
7083e77d9c | ||
|
|
dd976dee00 | ||
|
|
305e8d7296 | ||
|
|
437a3a5700 | ||
|
|
2c7326d3fc | ||
|
|
7f512474dc | ||
|
|
90c8b50689 | ||
|
|
e808422320 | ||
|
|
45cb6b21a2 | ||
|
|
5708735ca8 | ||
|
|
bcbe8a73b8 | ||
|
|
c5a9c81642 | ||
|
|
75e5652bf6 | ||
|
|
af458635e2 | ||
|
|
847e77ff72 | ||
|
|
f4aa3e7c18 | ||
|
|
37ce873db8 | ||
|
|
ace1021959 | ||
|
|
470d1ae3ec | ||
|
|
c3b5921d14 | ||
|
|
9300b786f8 | ||
|
|
443ee9bb4e | ||
|
|
620aefec23 | ||
|
|
b925b3e639 | ||
|
|
09835b0462 | ||
|
|
b88358949c | ||
|
|
9c5b4fb3d4 | ||
|
|
e8c14f9185 | ||
|
|
a0580238a6 | ||
|
|
01b370d9b6 | ||
|
|
8815eb836c | ||
|
|
f472c25349 | ||
|
|
809cc090e5 | ||
|
|
d34d87c274 | ||
|
|
c4bff550bb | ||
|
|
818f58e2f9 | ||
|
|
bfb79e38d6 | ||
|
|
a52bf50a0f | ||
|
|
49750b600f | ||
|
|
ffad7c48d2 | ||
|
|
43bd2a8314 | ||
|
|
e96d640082 | ||
|
|
df9d90d017 | ||
|
|
bbad30b86d | ||
|
|
86c12d13a7 | ||
|
|
bd8b4b8fa8 | ||
|
|
4ecb87658b | ||
|
|
c1e8153871 | ||
|
|
d54b0ca556 | ||
|
|
c67969efa2 | ||
|
|
2f9ce0c5cf | ||
|
|
ec702ddc05 | ||
|
|
98ba83e0a4 | ||
|
|
37938a022c | ||
|
|
7ee5f0d448 | ||
|
|
b98de3d992 | ||
|
|
2670fb1b4f | ||
|
|
360981b77d | ||
|
|
c693ad434a | ||
|
|
67bb04c6c2 | ||
|
|
764669398c | ||
|
|
cc3db7d323 | ||
|
|
bd7809bc17 | ||
|
|
0e30e64534 | ||
|
|
01d51c6e45 | ||
|
|
8d63de52f9 | ||
|
|
f2a781545b | ||
|
|
671a1f2923 | ||
|
|
ef1d95ea2d | ||
|
|
182adad9d2 | ||
|
|
91de70b7dc | ||
|
|
aa74dae70f | ||
|
|
4f61d792bc | ||
|
|
603461f19a | ||
|
|
4a535085c5 |
61
.deny.toml
Normal file
61
.deny.toml
Normal file
@ -0,0 +1,61 @@
|
||||
# Config file reference can be found at https://embarkstudios.github.io/cargo-deny/checks/cfg.html.
|
||||
|
||||
[graph]
|
||||
all-features = true
|
||||
exclude-dev = true
|
||||
no-default-features = true
|
||||
|
||||
[advisories]
|
||||
ignore = [
|
||||
{ id = "RUSTSEC-2023-0071", reason = "Marvin Attack: potential key recovery through timing sidechannels" },
|
||||
{ id = "RUSTSEC-2024-0388", reason = "`derivative` is unmaintained; consider using an alternative. Use `cargo tree -p derivative -i > tmp.txt` to check the dependency tree." },
|
||||
{ id = "RUSTSEC-2024-0436", reason = "`paste` has a security vulnerability; consider using an alternative. Use `cargo tree -p paste -i > tmp.txt` to check the dependency tree." },
|
||||
{ id = "RUSTSEC-2025-0055", reason = "`tracing-subscriber` v0.2.25 pulled in by ark-relations v0.4.0 - will be addressed before mainnet" },
|
||||
{ id = "RUSTSEC-2025-0141", reason = "`bincode` is unmaintained but continuing to use it." },
|
||||
{ id = "RUSTSEC-2023-0089", reason = "atomic-polyfill is pulled transitively via risc0-zkvm; waiting on upstream fix (see https://github.com/risc0/risc0/issues/3453)" },
|
||||
]
|
||||
yanked = "deny"
|
||||
unused-ignored-advisory = "deny"
|
||||
|
||||
[bans]
|
||||
allow-wildcard-paths = false
|
||||
multiple-versions = "allow"
|
||||
|
||||
[licenses]
|
||||
allow = [
|
||||
"Apache-2.0 WITH LLVM-exception",
|
||||
"Apache-2.0",
|
||||
"BSD-2-Clause",
|
||||
"BSD-3-Clause",
|
||||
"BSL-1.0",
|
||||
"CC0-1.0",
|
||||
"CDLA-Permissive-2.0",
|
||||
"ISC",
|
||||
"MIT",
|
||||
"MPL-2.0",
|
||||
"Unicode-3.0",
|
||||
"Zlib",
|
||||
]
|
||||
exceptions = [
|
||||
# TEMP: Pending legal review. Pulled transitively via `risc0-zkvm`
|
||||
{ name = "downloader", version = "0.2.8", allow = ["LGPL-3.0-or-later"] },
|
||||
{ name = "malachite", version = "0.4.22", allow = ["LGPL-3.0-only"] },
|
||||
{ name = "malachite-base", version = "0.4.22", allow = ["LGPL-3.0-only"] },
|
||||
{ name = "malachite-float", version = "0.4.22", allow = ["LGPL-3.0-only"] },
|
||||
{ name = "malachite-nz", version = "0.4.22", allow = ["LGPL-3.0-only"] },
|
||||
{ name = "malachite-q", version = "0.4.22", allow = ["LGPL-3.0-only"] },
|
||||
{ name = "managed", version = "0.8.0", allow = ["0BSD"] },
|
||||
]
|
||||
private = { ignore = false }
|
||||
unused-allowed-license = "deny"
|
||||
|
||||
[sources]
|
||||
allow-git = [
|
||||
"https://github.com/EspressoSystems/jellyfish.git",
|
||||
"https://github.com/logos-blockchain/logos-blockchain.git",
|
||||
]
|
||||
unknown-git = "deny"
|
||||
unknown-registry = "deny"
|
||||
|
||||
[sources.allow-org]
|
||||
github = ["logos-co"]
|
||||
45
.dockerignore
Normal file
45
.dockerignore
Normal file
@ -0,0 +1,45 @@
|
||||
# Build artifacts
|
||||
target/
|
||||
**/target/
|
||||
|
||||
# RocksDB data
|
||||
rocksdb/
|
||||
**/rocksdb/
|
||||
|
||||
# Git
|
||||
.git/
|
||||
.gitignore
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# CI/CD
|
||||
.github/
|
||||
ci_scripts/
|
||||
|
||||
# Documentation
|
||||
docs/
|
||||
*.md
|
||||
!README.md
|
||||
|
||||
# Non-build project files
|
||||
completions/
|
||||
configs/
|
||||
Justfile
|
||||
clippy.toml
|
||||
rustfmt.toml
|
||||
flake.nix
|
||||
flake.lock
|
||||
LICENSE
|
||||
|
||||
# Docker compose files (not needed inside build)
|
||||
docker-compose*.yml
|
||||
**/docker-compose*.yml
|
||||
19
.github/actions/install-logos-blockchain-circuits/action.yaml
vendored
Normal file
19
.github/actions/install-logos-blockchain-circuits/action.yaml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
name: Setup Logos Blockchain Circuits
|
||||
|
||||
description: Set up Logos Blockchain Circom Circuits, Rapidsnark prover and Rapidsnark verifier using the setup-logos-blockchain-circuits.sh script.
|
||||
|
||||
inputs:
|
||||
github-token:
|
||||
description: GitHub token for downloading releases
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Setup logos-blockchain-circuits
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.github-token }}
|
||||
run: |
|
||||
curl -sSL https://raw.githubusercontent.com/logos-blockchain/logos-blockchain/main/scripts/setup-logos-blockchain-circuits.sh | bash
|
||||
10
.github/actions/install-risc0/action.yml
vendored
Normal file
10
.github/actions/install-risc0/action.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
name: Install risc0
|
||||
description: Installs risc0 in the environment
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install risc0
|
||||
run: |
|
||||
curl -L https://risczero.com/install | bash
|
||||
/home/runner/.risc0/bin/rzup install
|
||||
shell: bash
|
||||
10
.github/actions/install-system-deps/action.yml
vendored
Normal file
10
.github/actions/install-system-deps/action.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
name: Install system dependencies
|
||||
description: Installs system dependencies in the environment
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential clang libclang-dev libssl-dev pkg-config
|
||||
shell: bash
|
||||
43
.github/pull_request_template.md
vendored
Normal file
43
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
## 🎯 Purpose
|
||||
|
||||
*What problem does this PR solve or what feature does it add? Mention issues related to it*
|
||||
|
||||
TO COMPLETE
|
||||
|
||||
## ⚙️ Approach
|
||||
|
||||
*Describe the core changes introduced by this PR.*
|
||||
|
||||
TO COMPLETE
|
||||
|
||||
- [ ] Change ...
|
||||
- [ ] Add ...
|
||||
- [ ] Fix ...
|
||||
- [ ] ...
|
||||
|
||||
## 🧪 How to Test
|
||||
|
||||
*How to verify that this PR works as intended.*
|
||||
|
||||
TO COMPLETE
|
||||
|
||||
## 🔗 Dependencies
|
||||
|
||||
*Link PRs that must be merged before this one.*
|
||||
|
||||
TO COMPLETE IF APPLICABLE
|
||||
|
||||
## 🔜 Future Work
|
||||
|
||||
*List any work intentionally left out of this PR, whether due to scope, prioritization, or pending decisions.*
|
||||
|
||||
TO COMPLETE IF APPLICABLE
|
||||
|
||||
## 📋 PR Completion Checklist
|
||||
|
||||
*Mark only completed items. A complete PR should have all boxes ticked.*
|
||||
|
||||
- [ ] Complete PR description
|
||||
- [ ] Implement the core functionality
|
||||
- [ ] Add/update tests
|
||||
- [ ] Add/update documentation and inline comments
|
||||
255
.github/workflows/ci.yml
vendored
255
.github/workflows/ci.yml
vendored
@ -1,7 +1,7 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
paths-ignore:
|
||||
- "**.md"
|
||||
- "!.github/workflows/*.yml"
|
||||
@ -14,64 +14,223 @@ on:
|
||||
name: General
|
||||
|
||||
jobs:
|
||||
build-ubuntu-latest:
|
||||
fmt-rs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- name: Install nightly toolchain for rustfmt
|
||||
run: rustup install nightly --profile minimal --component rustfmt
|
||||
|
||||
- name: Check Rust files are formatted
|
||||
run: cargo +nightly fmt --check
|
||||
|
||||
fmt-toml:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- name: Install taplo-cli
|
||||
run: cargo install --locked taplo-cli
|
||||
|
||||
- name: Check TOML files are formatted
|
||||
run: taplo fmt --check .
|
||||
|
||||
machete:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- name: Install active toolchain
|
||||
run: rustup install
|
||||
|
||||
- name: Install cargo-machete
|
||||
run: cargo install cargo-machete
|
||||
|
||||
- name: Check for unused dependencies
|
||||
run: cargo machete
|
||||
|
||||
deny:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- name: Install cargo-deny
|
||||
run: cargo install --locked cargo-deny
|
||||
|
||||
- name: Check licenses and advisories
|
||||
run: cargo deny check
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
|
||||
name: build - ubuntu-latest
|
||||
name: lint
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
- name: build - ubuntu-latest
|
||||
if: success() || failure()
|
||||
run: chmod 777 ./ci_scripts/build-ubuntu.sh && ./ci_scripts/build-ubuntu.sh
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- uses: ./.github/actions/install-system-deps
|
||||
|
||||
lint:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
- uses: ./.github/actions/install-risc0
|
||||
|
||||
- uses: ./.github/actions/install-logos-blockchain-circuits
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install active toolchain
|
||||
run: rustup install
|
||||
|
||||
- name: Lint workspace
|
||||
env:
|
||||
RISC0_SKIP_BUILD: "1"
|
||||
run: cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
|
||||
- name: Lint programs
|
||||
env:
|
||||
RISC0_SKIP_BUILD: "1"
|
||||
run: cargo clippy -p "*programs" -- -D warnings
|
||||
|
||||
unit-tests:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- uses: ./.github/actions/install-system-deps
|
||||
|
||||
- uses: ./.github/actions/install-risc0
|
||||
|
||||
- uses: ./.github/actions/install-logos-blockchain-circuits
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install active toolchain
|
||||
run: rustup install
|
||||
|
||||
- name: Install nextest
|
||||
run: cargo install --locked cargo-nextest
|
||||
|
||||
- name: Run tests
|
||||
env:
|
||||
RISC0_DEV_MODE: "1"
|
||||
RUST_LOG: "info"
|
||||
run: cargo nextest run --workspace --exclude integration_tests
|
||||
|
||||
integration-tests:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- uses: ./.github/actions/install-system-deps
|
||||
|
||||
- uses: ./.github/actions/install-risc0
|
||||
|
||||
- uses: ./.github/actions/install-logos-blockchain-circuits
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install active toolchain
|
||||
run: rustup install
|
||||
|
||||
- name: Install nextest
|
||||
run: cargo install --locked cargo-nextest
|
||||
|
||||
- name: Run tests
|
||||
env:
|
||||
RISC0_DEV_MODE: "1"
|
||||
RUST_LOG: "info"
|
||||
run: cargo nextest run -p integration_tests -- --skip tps_test --skip indexer
|
||||
|
||||
integration-tests-indexer:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- uses: ./.github/actions/install-system-deps
|
||||
|
||||
- uses: ./.github/actions/install-risc0
|
||||
|
||||
- uses: ./.github/actions/install-logos-blockchain-circuits
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install active toolchain
|
||||
run: rustup install
|
||||
|
||||
- name: Install nextest
|
||||
run: cargo install --locked cargo-nextest
|
||||
|
||||
- name: Run tests
|
||||
env:
|
||||
RISC0_DEV_MODE: "1"
|
||||
RUST_LOG: "info"
|
||||
run: cargo nextest run -p integration_tests indexer -- --skip tps_test
|
||||
|
||||
valid-proof-test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- uses: ./.github/actions/install-system-deps
|
||||
|
||||
- uses: ./.github/actions/install-risc0
|
||||
|
||||
- uses: ./.github/actions/install-logos-blockchain-circuits
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install active toolchain
|
||||
run: rustup install
|
||||
|
||||
- name: Test valid proof
|
||||
env:
|
||||
RUST_LOG: "info"
|
||||
run: cargo test -p integration_tests -- --exact private::private_transfer_to_owned_account
|
||||
|
||||
artifacts:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
|
||||
name: lint - ${{ matrix.crate }} - ${{ matrix.platform }}
|
||||
name: artifacts
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
- name: lint - ubuntu-latest
|
||||
if: success() || failure()
|
||||
run: chmod 777 ./ci_scripts/lint-ubuntu.sh && ./ci_scripts/lint-ubuntu.sh
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- uses: ./.github/actions/install-risc0
|
||||
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
timeout-minutes: 60
|
||||
- name: Install just
|
||||
run: cargo install just
|
||||
|
||||
name: test - ${{ matrix.crate }} - ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
- name: test ubuntu-latest
|
||||
if: success() || failure()
|
||||
run: chmod 777 ./ci_scripts/test-ubuntu.sh && ./ci_scripts/test-ubuntu.sh
|
||||
- name: Build artifacts
|
||||
run: just build-artifacts
|
||||
|
||||
- name: Check if artifacts match repository
|
||||
run: |
|
||||
if ! git diff --exit-code artifacts/; then
|
||||
echo "❌ Artifacts in the repository are out of date!"
|
||||
echo "Please run 'just build-artifacts' and commit the changes."
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Artifacts are up to date"
|
||||
|
||||
66
.github/workflows/publish_images.yml
vendored
Normal file
66
.github/workflows/publish_images.yml
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
name: Publish Docker Images
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: sequencer_service
|
||||
dockerfile: ./sequencer/service/Dockerfile
|
||||
build_args: |
|
||||
STANDALONE=false
|
||||
- name: sequencer_service-standalone
|
||||
dockerfile: ./sequencer/service/Dockerfile
|
||||
build_args: |
|
||||
STANDALONE=true
|
||||
- name: indexer_service
|
||||
dockerfile: ./indexer/service/Dockerfile
|
||||
build_args: ""
|
||||
- name: explorer_service
|
||||
dockerfile: ./explorer_service/Dockerfile
|
||||
build_args: ""
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ secrets.DOCKER_REGISTRY }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ secrets.DOCKER_REGISTRY }}/${{ github.repository }}/${{ matrix.name }}
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha,prefix={{branch}}-
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.dockerfile }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: ${{ matrix.build_args }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@ -6,3 +6,9 @@ data/
|
||||
.idea/
|
||||
.vscode/
|
||||
rocksdb
|
||||
sequencer/service/data/
|
||||
storage.json
|
||||
result
|
||||
wallet-ffi/wallet_ffi.h
|
||||
bedrock_signing_key
|
||||
integration_tests/configs/debug/
|
||||
|
||||
7559
Cargo.lock
generated
7559
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
307
Cargo.toml
307
Cargo.toml
@ -1,87 +1,284 @@
|
||||
[workspace.package]
|
||||
license = "MIT or Apache-2.0"
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
resolver = "3"
|
||||
members = [
|
||||
"node_runner",
|
||||
"sequencer_runner",
|
||||
"integration_tests",
|
||||
"storage",
|
||||
"accounts",
|
||||
"utxo",
|
||||
"vm",
|
||||
"networking",
|
||||
"consensus",
|
||||
"node_rpc",
|
||||
"sequencer_rpc",
|
||||
"key_protocol",
|
||||
"mempool",
|
||||
"zkvm",
|
||||
"node_core",
|
||||
"sequencer_core",
|
||||
"rpc_primitives",
|
||||
"wallet",
|
||||
"wallet-ffi",
|
||||
"common",
|
||||
"nssa",
|
||||
"nssa/core",
|
||||
"programs/amm/core",
|
||||
"programs/amm",
|
||||
"programs/token/core",
|
||||
"programs/token",
|
||||
"sequencer/core",
|
||||
"sequencer/service",
|
||||
"sequencer/service/protocol",
|
||||
"sequencer/service/rpc",
|
||||
"indexer/core",
|
||||
"indexer/service",
|
||||
"indexer/service/protocol",
|
||||
"indexer/service/rpc",
|
||||
"explorer_service",
|
||||
"program_methods",
|
||||
"program_methods/guest",
|
||||
"test_program_methods",
|
||||
"test_program_methods/guest",
|
||||
"examples/program_deployment",
|
||||
"examples/program_deployment/methods",
|
||||
"examples/program_deployment/methods/guest",
|
||||
"bedrock_client",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0"
|
||||
nssa = { path = "nssa" }
|
||||
nssa_core = { path = "nssa/core" }
|
||||
common = { path = "common" }
|
||||
mempool = { path = "mempool" }
|
||||
storage = { path = "storage" }
|
||||
key_protocol = { path = "key_protocol" }
|
||||
sequencer_core = { path = "sequencer/core" }
|
||||
sequencer_service_protocol = { path = "sequencer/service/protocol" }
|
||||
sequencer_service_rpc = { path = "sequencer/service/rpc" }
|
||||
sequencer_service = { path = "sequencer/service" }
|
||||
indexer_core = { path = "indexer/core" }
|
||||
indexer_service = { path = "indexer/service" }
|
||||
indexer_service_protocol = { path = "indexer/service/protocol" }
|
||||
indexer_service_rpc = { path = "indexer/service/rpc" }
|
||||
wallet = { path = "wallet" }
|
||||
wallet-ffi = { path = "wallet-ffi", default-features = false }
|
||||
token_core = { path = "programs/token/core" }
|
||||
token_program = { path = "programs/token" }
|
||||
amm_core = { path = "programs/amm/core" }
|
||||
amm_program = { path = "programs/amm" }
|
||||
test_program_methods = { path = "test_program_methods" }
|
||||
bedrock_client = { path = "bedrock_client" }
|
||||
|
||||
tokio = { version = "1.50", features = [
|
||||
"net",
|
||||
"rt-multi-thread",
|
||||
"sync",
|
||||
"fs",
|
||||
] }
|
||||
tokio-util = "0.7.18"
|
||||
risc0-zkvm = { version = "3.0.5", features = ['std'] }
|
||||
risc0-build = "3.0.5"
|
||||
anyhow = "1.0.98"
|
||||
num_cpus = "1.13.1"
|
||||
openssl = { version = "0.10", features = ["vendored"] }
|
||||
openssl-probe = { version = "0.1.2" }
|
||||
serde = { version = "1.0.60", default-features = false, features = ["derive"] }
|
||||
serde_json = "1.0.81"
|
||||
serde_with = "3.16.1"
|
||||
actix = "0.13.0"
|
||||
actix-cors = "0.6.1"
|
||||
actix-cors = "0.7.1"
|
||||
jsonrpsee = "0.26.0"
|
||||
futures = "0.3"
|
||||
actix-rt = "*"
|
||||
|
||||
env_logger = "0.10"
|
||||
log = "0.4"
|
||||
lru = "0.7.8"
|
||||
thiserror = "1.0"
|
||||
rs_merkle = "1.4"
|
||||
lazy_static = "1.5.0"
|
||||
env_logger = "0.11"
|
||||
log = "0.4.28"
|
||||
lru = "0.16.3"
|
||||
thiserror = "2.0"
|
||||
sha2 = "0.10.8"
|
||||
monotree = "0.1.5"
|
||||
hex = "0.4.3"
|
||||
bytemuck = "1.24.0"
|
||||
bytesize = { version = "2.3.1", features = ["serde"] }
|
||||
humantime-serde = "1.1"
|
||||
humantime = "2.1"
|
||||
aes-gcm = "0.10.3"
|
||||
toml = "0.7.4"
|
||||
secp256k1-zkp = "0.11.0"
|
||||
toml = "0.9.8"
|
||||
bincode = "1.3.3"
|
||||
tempfile = "3.14.0"
|
||||
light-poseidon = "0.3.0"
|
||||
ark-bn254 = "0.5.0"
|
||||
ark-ff = "0.5.0"
|
||||
tiny-keccak = { version = "2.0.2", features = ["keccak"] }
|
||||
base64 = "0.22.1"
|
||||
bip39 = "2.2.0"
|
||||
hmac-sha512 = "1.1.7"
|
||||
chrono = "0.4.41"
|
||||
borsh = "1.5.7"
|
||||
base58 = "0.2.0"
|
||||
itertools = "0.14.0"
|
||||
url = { version = "2.5.4", features = ["serde"] }
|
||||
tokio-retry = "0.3.0"
|
||||
schemars = "1.2"
|
||||
async-stream = "0.3.6"
|
||||
|
||||
rocksdb = { version = "0.21.0", default-features = false, features = [
|
||||
logos-blockchain-common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git" }
|
||||
logos-blockchain-key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git" }
|
||||
logos-blockchain-core = { git = "https://github.com/logos-blockchain/logos-blockchain.git" }
|
||||
logos-blockchain-chain-broadcast-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git" }
|
||||
logos-blockchain-chain-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git" }
|
||||
|
||||
rocksdb = { version = "0.24.0", default-features = false, features = [
|
||||
"snappy",
|
||||
"bindgen-runtime",
|
||||
] }
|
||||
rand = { version = "0.8.5", features = ["std", "std_rng", "getrandom"] }
|
||||
k256 = { version = "0.13.3", features = [
|
||||
"ecdsa-core",
|
||||
"arithmetic",
|
||||
"expose-field",
|
||||
"serde",
|
||||
"pem",
|
||||
] }
|
||||
elliptic-curve = { version = "0.13.8", features = ["arithmetic"] }
|
||||
actix-web = { version = "4.13.0", default-features = false, features = [
|
||||
"macros",
|
||||
] }
|
||||
clap = { version = "4.5.42", features = ["derive", "env"] }
|
||||
reqwest = { version = "0.12", features = ["json", "rustls-tls", "stream"] }
|
||||
|
||||
[workspace.dependencies.rand]
|
||||
features = ["std", "std_rng", "getrandom"]
|
||||
version = "0.8.5"
|
||||
# Profile for leptos WASM release builds
|
||||
[profile.wasm-release]
|
||||
inherits = "release"
|
||||
opt-level = 'z'
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.dependencies.k256]
|
||||
features = ["ecdsa-core", "arithmetic", "expose-field"]
|
||||
version = "0.13.4"
|
||||
[workspace.lints.rust]
|
||||
warnings = "deny"
|
||||
|
||||
[workspace.dependencies.elliptic-curve]
|
||||
features = ["arithmetic"]
|
||||
version = "0.13.8"
|
||||
[workspace.lints]
|
||||
clippy.all = { level = "deny", priority = -1 }
|
||||
|
||||
[workspace.dependencies.serde]
|
||||
features = ["derive"]
|
||||
version = "1.0.60"
|
||||
# Pedantic
|
||||
clippy.pedantic = { level = "deny", priority = -1 }
|
||||
|
||||
[workspace.dependencies.actix-web]
|
||||
default-features = false
|
||||
version = "=4.1.0"
|
||||
# Reason: documenting every function returning Result is too verbose and doesn't add much value when you have good error types.
|
||||
clippy.missing-errors-doc = "allow"
|
||||
# Reason: most of the panics are internal and not part of the public API, so documenting them is not necessary.
|
||||
clippy.missing-panics-doc = "allow"
|
||||
# Reason: this isn't always bad and actually works well for our financial and cryptography code.
|
||||
clippy.similar-names = "allow"
|
||||
# Reason: this lint is too strict and hard to fix.
|
||||
clippy.too-many-lines = "allow"
|
||||
# Reason: std hasher is fine for us in public functions.
|
||||
clippy.implicit-hasher = "allow"
|
||||
|
||||
[workspace.dependencies.clap]
|
||||
features = ["derive", "env"]
|
||||
version = "3.1.6"
|
||||
# Restriction
|
||||
clippy.restriction = { level = "deny", priority = -1 }
|
||||
|
||||
[workspace.dependencies.tokio-retry]
|
||||
version = "0.3.0"
|
||||
# Reason: we deny the whole `restriction` group but we allow things that don't make sense for us.
|
||||
# That way we can still benefit from new lints added to the `restriction` group without having to
|
||||
# explicitly allow them.
|
||||
# As a downside our contributors don't know if some lint was enabled intentionally or just no one
|
||||
# else faced it before to allow it but we can handle this during code reviews.
|
||||
clippy.blanket-clippy-restriction-lints = "allow"
|
||||
# Reason: we can't avoid using unwrap for now.
|
||||
clippy.unwrap-used = "allow"
|
||||
# Reason: we can't avoid using expect for now.
|
||||
clippy.expect-used = "allow"
|
||||
# Reason: unreachable is good in many cases.
|
||||
clippy.unreachable = "allow"
|
||||
# Reason: this is ridiculous strict in our codebase and doesn't add any value.
|
||||
clippy.single-call-fn = "allow"
|
||||
# Reason: we use panic in some places and it's okay.
|
||||
clippy.panic = "allow"
|
||||
# Reason: shadowing is good most of the times.
|
||||
clippy.shadow-reuse = "allow"
|
||||
# Reason: implicit return is good.
|
||||
clippy.implicit-return = "allow"
|
||||
# Reason: std is fine for us, we don't need to use core.
|
||||
clippy.std-instead-of-core = "allow"
|
||||
# Reason: std is fine for us, we don't need to use alloc.
|
||||
clippy.std-instead-of-alloc = "allow"
|
||||
# Reason: default methods are good most of the time.
|
||||
clippy.missing-trait-methods = "allow"
|
||||
# Reason: this is too verbose and doesn't help much if you have rust analyzer.
|
||||
clippy.pattern-type-mismatch = "allow"
|
||||
# Reason: decreases readability.
|
||||
clippy.assertions-on-result-states = "allow"
|
||||
# Reason: documenting every assert is too verbose.
|
||||
clippy.missing-assert-message = "allow"
|
||||
# Reason: documenting private items is too verbose and doesn't add much value.
|
||||
clippy.missing-docs-in-private-items = "allow"
|
||||
# Reason: we use separated suffix style.
|
||||
clippy.separated_literal_suffix = "allow"
|
||||
# Reason: sometimes absolute paths are more readable.
|
||||
clippy.absolute-paths = "allow"
|
||||
# Reason: sometimes it's as readable as full variable naming.
|
||||
clippy.min-ident-chars = "allow"
|
||||
# Reason: it's very common and handy.
|
||||
clippy.indexing-slicing = "allow"
|
||||
# Reason: we use little endian style.
|
||||
clippy.little-endian-bytes = "allow"
|
||||
# Reason: we use this style of pub visibility.
|
||||
clippy.pub-with-shorthand = "allow"
|
||||
# Reason: question mark operator is very cool.
|
||||
clippy.question-mark-used = "allow"
|
||||
# Reason: it's fine to panic in tests and some functions where it makes sense.
|
||||
clippy.panic-in-result-fn = "allow"
|
||||
# Reason: we don't care that much about inlining and LTO should take care of it.
|
||||
clippy.missing_inline_in_public_items = "allow"
|
||||
# Reason: it's okay for us.
|
||||
clippy.default-numeric-fallback = "allow"
|
||||
# Reason: this is fine for us.
|
||||
clippy.exhaustive-enums = "allow"
|
||||
# Reason: this is fine for us.
|
||||
clippy.exhaustive-structs = "allow"
|
||||
# Reason: this helps readability when item is imported in other modules.
|
||||
clippy.module-name-repetitions = "allow"
|
||||
# Reason: mostly historical reasons, maybe we'll address this in future.
|
||||
clippy.mod-module-files = "allow"
|
||||
# Reason: named module files is our preferred way.
|
||||
clippy.self-named-module-files = "allow"
|
||||
# Reason: this is actually quite handy.
|
||||
clippy.impl-trait-in-params = "allow"
|
||||
# Reason: this is often useful.
|
||||
clippy.use-debug = "allow"
|
||||
# Reason: this is sometimes useful.
|
||||
clippy.field-scoped-visibility-modifiers = "allow"
|
||||
# Reason: `pub use` is good for re-exports and hiding unnecessary details.
|
||||
clippy.pub-use = "allow"
|
||||
# Reason: we prefer semicolons inside blocks.
|
||||
clippy.semicolon-outside-block = "allow"
|
||||
# Reason: we don't do it blindly, this is mostly internal constraints checks.
|
||||
clippy.unwrap-in-result = "allow"
|
||||
# Reason: we don't see any problems with that.
|
||||
clippy.shadow-same = "allow"
|
||||
# Reason: this lint is too verbose.
|
||||
clippy.let-underscore-untyped = "allow"
|
||||
# Reason: this lint is actually bad as it forces to use wildcard `..` instead of
|
||||
# field-by-field `_` which may lead to subtle bugs when new fields are added to the struct.
|
||||
clippy.unneeded-field-pattern = "allow"
|
||||
|
||||
[workspace.dependencies.reqwest]
|
||||
features = ["json"]
|
||||
version = "0.11.16"
|
||||
# Nursery
|
||||
clippy.nursery = { level = "deny", priority = -1 }
|
||||
|
||||
[workspace.dependencies.tokio]
|
||||
features = ["net", "rt-multi-thread", "sync", "fs"]
|
||||
version = "1.28.2"
|
||||
# Reason: this is okay if it compiles.
|
||||
clippy.future-not-send = "allow"
|
||||
# Reason: this is actually a good lint, but currently it gives a lot of false-positives.
|
||||
clippy.significant-drop-tightening = "allow"
|
||||
|
||||
[workspace.dependencies.tracing]
|
||||
features = ["std"]
|
||||
version = "0.1.13"
|
||||
# Correctness
|
||||
clippy.correctness = { level = "deny", priority = -1 }
|
||||
|
||||
# Complexity
|
||||
clippy.complexity = { level = "deny", priority = -1 }
|
||||
|
||||
# Perf
|
||||
clippy.perf = { level = "deny", priority = -1 }
|
||||
|
||||
# Suspicious
|
||||
clippy.suspicious = { level = "deny", priority = -1 }
|
||||
|
||||
# Style
|
||||
clippy.style = { level = "deny", priority = -1 }
|
||||
|
||||
# Cargo
|
||||
clippy.cargo = { level = "deny", priority = -1 }
|
||||
|
||||
# Reason: we're not at this stage yet and it will be a pain to create a new crate.
|
||||
clippy.cargo-common-metadata = "allow"
|
||||
# Reason: hard to address right now and mostly comes from dependencies
|
||||
# so the fix would be just a long list of exceptions.
|
||||
clippy.multiple-crate-versions = "allow"
|
||||
|
||||
70
Justfile
Normal file
70
Justfile
Normal file
@ -0,0 +1,70 @@
|
||||
set shell := ["bash", "-eu", "-o", "pipefail", "-c"]
|
||||
|
||||
default:
|
||||
@just --list
|
||||
|
||||
# ---- Configuration ----
|
||||
METHODS_PATH := "program_methods"
|
||||
TEST_METHODS_PATH := "test_program_methods"
|
||||
ARTIFACTS := "artifacts"
|
||||
|
||||
# Build risc0 program artifacts
|
||||
build-artifacts:
|
||||
@echo "🔨 Building artifacts"
|
||||
@for methods_path in {{METHODS_PATH}} {{TEST_METHODS_PATH}}; do \
|
||||
echo "Building artifacts for $methods_path"; \
|
||||
CARGO_TARGET_DIR=target/$methods_path cargo risczero build --manifest-path $methods_path/guest/Cargo.toml; \
|
||||
mkdir -p {{ARTIFACTS}}/$methods_path; \
|
||||
cp target/$methods_path/riscv32im-risc0-zkvm-elf/docker/*.bin {{ARTIFACTS}}/$methods_path; \
|
||||
done
|
||||
|
||||
# Run tests
|
||||
test:
|
||||
@echo "🧪 Running tests"
|
||||
RISC0_DEV_MODE=1 cargo nextest run --no-fail-fast
|
||||
|
||||
# Run Bedrock node in docker
|
||||
[working-directory: 'bedrock']
|
||||
run-bedrock:
|
||||
@echo "⛓️ Running bedrock"
|
||||
docker compose up
|
||||
|
||||
# Run Sequencer
|
||||
[working-directory: 'sequencer/service']
|
||||
run-sequencer:
|
||||
@echo "🧠 Running sequencer"
|
||||
RUST_LOG=info RISC0_DEV_MODE=1 cargo run --release -p sequencer_service configs/debug/sequencer_config.json
|
||||
|
||||
# Run Indexer
|
||||
[working-directory: 'indexer/service']
|
||||
run-indexer mock="":
|
||||
@echo "🔍 Running indexer"
|
||||
@if [ "{{mock}}" = "mock" ]; then \
|
||||
echo "🧪 Using mock data"; \
|
||||
RUST_LOG=info RISC0_DEV_MODE=1 cargo run --release --features mock-responses -p indexer_service configs/indexer_config.json; \
|
||||
else \
|
||||
echo "🚀 Using real data"; \
|
||||
RUST_LOG=info RISC0_DEV_MODE=1 cargo run --release -p indexer_service configs/indexer_config.json; \
|
||||
fi
|
||||
|
||||
# Run Explorer
|
||||
[working-directory: 'explorer_service']
|
||||
run-explorer:
|
||||
@echo "🌐 Running explorer"
|
||||
RUST_LOG=info cargo leptos serve
|
||||
|
||||
# Run Wallet
|
||||
[working-directory: 'wallet']
|
||||
run-wallet +args:
|
||||
@echo "🔑 Running wallet"
|
||||
NSSA_WALLET_HOME_DIR=$(pwd)/configs/debug cargo run --release -p wallet -- {{args}}
|
||||
|
||||
# Clean runtime data
|
||||
clean:
|
||||
@echo "🧹 Cleaning run artifacts"
|
||||
rm -rf sequencer/service/bedrock_signing_key
|
||||
rm -rf sequencer/service/rocksdb
|
||||
rm -rf indexer/service/rocksdb
|
||||
rm -rf wallet/configs/debug/storage.json
|
||||
rm -rf rocksdb
|
||||
cd bedrock && docker compose down -v
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Vac
|
||||
|
||||
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.
|
||||
241
README.md
241
README.md
@ -1,2 +1,239 @@
|
||||
# nescience-testnet
|
||||
This repo serves for Nescience Node testnet
|
||||
# Logos Execution Zone (LEZ)
|
||||
|
||||
Logos Execution Zone (LEZ) is a programmable blockchain that cleanly separates public and private state while keeping them fully interoperable. Developers can build apps that operate across transparent and privacy-preserving accounts without changing their logic. Privacy is enforced by the protocol itself through zero-knowledge proofs (ZKPs), so it is always available and automatic.
|
||||
|
||||
|
||||
## Background
|
||||
|
||||
These features are provided by the Logos Execution Environment (LEE). Traditional public blockchains expose a fully transparent state: the mapping from account IDs to account values is entirely visible. LEE introduces a parallel *private state* that coexists with the public one. Together, public and private accounts form a partition of the account ID space: public IDs are visible on-chain, while private accounts are accessible only to holders of the corresponding viewing keys. Consistency across both states is enforced by ZKPs.
|
||||
|
||||
Public accounts are stored on-chain as a visible map from IDs to account states, and their values are updated in place. Private accounts are never stored on-chain in raw form. Each update produces a new commitment that binds the current value while keeping it hidden. Previous commitments remain on-chain, but a nullifier set marks old versions as spent, ensuring that only the most recent private state can be used in execution.
|
||||
|
||||
|
||||
### Programmability and selective privacy
|
||||
|
||||
LEZ aims to deliver full programmability in a hybrid public/private model, with the same flexibility and composability as public blockchains. Developers write and deploy programs in LEZ without addressing privacy concerns. The protocol automatically supports executions that involve any combination of public and private accounts. From the program’s perspective, all accounts look the same, and privacy is enforced transparently. This lets developers focus on business logic while the system guarantees privacy and correctness.
|
||||
|
||||
To our knowledge, this design is unique to LEZ. Other privacy-focused programmable blockchains often require developers to explicitly handle private inputs inside their app logic. In LEZ, privacy is protocol-level: programs do not change, accounts are treated uniformly, and private execution works out of the box.
|
||||
|
||||
---
|
||||
|
||||
## Example: Creating and transferring tokens across states
|
||||
|
||||
1. Token creation (public execution)
|
||||
- Alice submits a transaction that executes the token program `New` function on-chain.
|
||||
- A new public token definition account is created.
|
||||
- The minted tokens are recorded on-chain in Alice’s public account.
|
||||
|
||||
2. Transfer from public to private (local / privacy-preserving execution)
|
||||
- Alice runs the token program `Transfer` function locally, sending to Bob’s private account.
|
||||
- A ZKP of correct execution is generated.
|
||||
- The proof is submitted to the blockchain and verified by validators.
|
||||
- Alice’s public balance is updated on-chain.
|
||||
- Bob’s private balance remains hidden, while the transfer is provably correct.
|
||||
|
||||
3. Transferring private to public (local / privacy-preserving execution)
|
||||
- Bob executes the token program `Transfer` function locally, sending to Charlie’s public account.
|
||||
- A ZKP of correct execution is generated.
|
||||
- Bob’s private account and balance still remain hidden.
|
||||
- Charlie's public account is modified with the new tokens added.
|
||||
4. Transferring public to public (public execution):
|
||||
- Alice submits a transaction to execute the token program `Transfer` function on-chain, specifying Charlie's public account as recipient.
|
||||
- The execution is handled on-chain without ZKPs involved.
|
||||
- Alice's and Charlie's accounts are modified according to the transaction.
|
||||
|
||||
4. Transfer from public to public (public execution)
|
||||
- Alice submits an on-chain transaction to run `Transfer`, sending to Charlie’s public account.
|
||||
- Execution is handled fully on-chain without ZKPs.
|
||||
- Alice’s and Charlie’s public balances are updated.
|
||||
|
||||
|
||||
### Key points:
|
||||
- The same token program is used in every execution.
|
||||
- The only difference is execution mode: public execution updates visible state on-chain, while private execution relies on ZKPs.
|
||||
- Validators verify proofs only for privacy-preserving transactions, keeping processing efficient.
|
||||
|
||||
---
|
||||
|
||||
## The account’s model
|
||||
|
||||
To achieve both state separation and full programmability, LEZ uses a stateless program model. Programs hold no internal state. All persistent data is stored in accounts passed explicitly into each execution. This enables precise access control and visibility while preserving composability across public and private states.
|
||||
|
||||
### Execution types
|
||||
|
||||
LEZ supports two execution types:
|
||||
- Public execution runs transparently on-chain.
|
||||
- Private execution runs off-chain and is verified on-chain with ZKPs.
|
||||
|
||||
Both public and private executions use the same Risc0 VM bytecode. Public transactions are executed directly on-chain like any standard RISC-V VM call, without proof generation. Private transactions are executed locally by users, who generate Risc0 proofs that validators verify instead of re-executing the program.
|
||||
|
||||
This design keeps public transactions as fast as any RISC-V–based VM and makes private transactions efficient for validators. It also supports parallel execution similar to Solana, improving throughput. The main computational cost for privacy-preserving transactions is on the user side, where ZK proofs are generated.
|
||||
|
||||
---
|
||||
---
|
||||
---
|
||||
|
||||
# Versioning
|
||||
|
||||
We release versions as git tags (e.g. `v0.1.0`). If no critical issues with version is found you can expect it to be immutable. All further features and fixes will be a part of the next tag. As the project is in active development we don't provide backward compatibility yet.
|
||||
For each tag we publish docker images of our services.
|
||||
If you depend on this project you can pin your rust dependency to a git tag like this:
|
||||
|
||||
```toml
|
||||
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.1.0" }
|
||||
```
|
||||
|
||||
# Install dependencies
|
||||
### Install build dependencies
|
||||
|
||||
- On Linux
|
||||
Ubuntu / Debian
|
||||
```sh
|
||||
apt install build-essential clang libclang-dev libssl-dev pkg-config
|
||||
```
|
||||
|
||||
- On Fedora
|
||||
```sh
|
||||
sudo dnf install clang clang-devel openssl-devel pkgconf
|
||||
```
|
||||
|
||||
- On Mac
|
||||
```sh
|
||||
xcode-select --install
|
||||
brew install pkg-config openssl
|
||||
```
|
||||
|
||||
### Install Rust
|
||||
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
```
|
||||
|
||||
### Install Risc0
|
||||
|
||||
```sh
|
||||
curl -L https://risczero.com/install | bash
|
||||
```
|
||||
|
||||
### Then restart your shell and run
|
||||
```sh
|
||||
rzup install
|
||||
```
|
||||
|
||||
# Run tests
|
||||
|
||||
The LEZ repository includes both unit and integration test suites.
|
||||
|
||||
### Unit tests
|
||||
|
||||
```bash
|
||||
# RISC0_DEV_MODE=1 is used to skip proof generation and reduce test runtime overhead
|
||||
RISC0_DEV_MODE=1 cargo test --release
|
||||
```
|
||||
|
||||
### Integration tests
|
||||
|
||||
```bash
|
||||
export NSSA_WALLET_HOME_DIR=$(pwd)/integration_tests/configs/debug/wallet/
|
||||
cd integration_tests
|
||||
# RISC0_DEV_MODE=1 skips proof generation; RUST_LOG=info enables runtime logs
|
||||
RUST_LOG=info RISC0_DEV_MODE=1 cargo run $(pwd)/configs/debug all
|
||||
```
|
||||
|
||||
# Run the sequencer and node
|
||||
## Running Manually
|
||||
### Normal mode
|
||||
The sequencer and logos blockchain node can be run locally:
|
||||
1. On one terminal go to the `logos-blockchain/logos-blockchain` repo and run a local logos blockchain node:
|
||||
- `git checkout master; git pull`
|
||||
- `cargo clean`
|
||||
- `rm -r ~/.logos-blockchain-circuits`
|
||||
- `./scripts/setup-logos-blockchain-circuits.sh`
|
||||
- `cargo build --all-features`
|
||||
- `./target/debug/logos-blockchain-node --deployment nodes/node/standalone-deployment-config.yaml nodes/node/standalone-node-config.yaml`
|
||||
|
||||
- Alternatively (WARNING: This node is outdated) go to `logos-blockchain/lssa/` repo and run the node from docker:
|
||||
- `cd bedrock`
|
||||
- Change line 14 of `docker-compose.yml` from `"0:18080/tcp"` into `"8080:18080/tcp"`
|
||||
- `docker compose up`
|
||||
|
||||
2. On another terminal go to the `logos-blockchain/lssa` repo and run indexer service:
|
||||
- `RUST_LOG=info cargo run -p indexer_service indexer/service/configs/indexer_config.json`
|
||||
|
||||
3. On another terminal go to the `logos-blockchain/lssa` repo and run the sequencer:
|
||||
- `RUST_LOG=info cargo run -p sequencer_service sequencer/service/configs/debug/sequencer_config.json`
|
||||
4. (To run the explorer): on another terminal go to `logos-blockchain/lssa/explorer_service` and run the following:
|
||||
- `cargo install cargo-leptos`
|
||||
- `cargo leptos build --release`
|
||||
- `cargo leptos serve --release`
|
||||
|
||||
### Notes on cleanup
|
||||
|
||||
After stopping services above you need to remove 3 folders to start cleanly:
|
||||
1. In the `logos-blockchain/logos-blockchain` folder `state` (not needed in case of docker setup)
|
||||
2. In the `lssa` folder `sequencer/service/rocksdb`
|
||||
3. In the `lssa` file `sequencer/service/bedrock_signing_key`
|
||||
4. In the `lssa` folder `indexer/service/rocksdb`
|
||||
|
||||
### Normal mode (`just` commands)
|
||||
We provide a `Justfile` for developer and user needs, you can run the whole setup with it. The only difference will be that logos-blockchain (bedrock) will be started from docker.
|
||||
|
||||
#### 1'st Terminal
|
||||
|
||||
```bash
|
||||
just run-bedrock
|
||||
```
|
||||
|
||||
#### 2'nd Terminal
|
||||
|
||||
```bash
|
||||
just run-indexer
|
||||
```
|
||||
|
||||
#### 3'rd Terminal
|
||||
|
||||
```bash
|
||||
just run-sequencer
|
||||
```
|
||||
|
||||
#### 4'th Terminal
|
||||
|
||||
```bash
|
||||
just run-explorer
|
||||
```
|
||||
|
||||
#### 5'th Terminal
|
||||
|
||||
You can run any command our wallet support by passing it as an argument for `just run-wallet`, for example:
|
||||
|
||||
```bash
|
||||
just run-wallet check-health
|
||||
```
|
||||
|
||||
This will use a wallet binary built from this repo and not the one installed in your system if you have some. Also another wallet home directory will be used. This is done to not to mess up with your local wallet and to easily clean generated files (see next section).
|
||||
|
||||
#### Shutdown
|
||||
|
||||
1. Press `ctrl-c` in every terminal
|
||||
2. Run `just clean` to clean runtime data
|
||||
|
||||
### Standalone mode
|
||||
The sequencer can be run in standalone mode with:
|
||||
```bash
|
||||
RUST_LOG=info cargo run --features standalone -p sequencer_service sequencer/service/configs/debug
|
||||
```
|
||||
|
||||
## Running with Docker
|
||||
|
||||
You can run the whole setup with Docker:
|
||||
|
||||
```bash
|
||||
docker compose up
|
||||
```
|
||||
|
||||
With that you can send transactions from local wallet to the Sequencer running inside Docker using `wallet/configs/debug` as well as exploring blocks by opening `http://localhost:8080`.
|
||||
|
||||
## Caution for local image builds
|
||||
|
||||
If you're going to build sequencer image locally you should better adjust default docker settings and set `defaultKeepStorage` at least `25GB` so that it can keep layers properly cached.
|
||||
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "accounts"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
serde_json.workspace = true
|
||||
env_logger.workspace = true
|
||||
log.workspace = true
|
||||
serde.workspace = true
|
||||
k256.workspace = true
|
||||
sha2.workspace = true
|
||||
rand.workspace = true
|
||||
elliptic-curve.workspace = true
|
||||
hex.workspace = true
|
||||
aes-gcm.workspace = true
|
||||
|
||||
[dependencies.storage]
|
||||
path = "../storage"
|
||||
|
||||
[dependencies.utxo]
|
||||
path = "../utxo"
|
||||
@ -1,205 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use k256::AffinePoint;
|
||||
use log::info;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use storage::{merkle_tree_public::TreeHashType, nullifier::UTXONullifier};
|
||||
use utxo::{
|
||||
utxo_core::{UTXOPayload, UTXO},
|
||||
utxo_tree::UTXOSparseMerkleTree,
|
||||
};
|
||||
|
||||
use crate::key_management::{
|
||||
constants_types::{CipherText, Nonce},
|
||||
ephemeral_key_holder::EphemeralKeyHolder,
|
||||
AddressKeyHolder,
|
||||
};
|
||||
|
||||
pub type PublicKey = AffinePoint;
|
||||
pub type AccountAddress = TreeHashType;
|
||||
|
||||
pub struct Account {
|
||||
pub key_holder: AddressKeyHolder,
|
||||
pub address: AccountAddress,
|
||||
pub balance: u64,
|
||||
pub utxo_tree: UTXOSparseMerkleTree,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub fn new() -> Self {
|
||||
let key_holder = AddressKeyHolder::new_os_random();
|
||||
let address = key_holder.address;
|
||||
let balance = 0;
|
||||
let utxo_tree = UTXOSparseMerkleTree::new();
|
||||
|
||||
Self {
|
||||
key_holder,
|
||||
address,
|
||||
balance,
|
||||
utxo_tree,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_balance(balance: u64) -> Self {
|
||||
let key_holder = AddressKeyHolder::new_os_random();
|
||||
let address = key_holder.address;
|
||||
let utxo_tree = UTXOSparseMerkleTree::new();
|
||||
|
||||
Self {
|
||||
key_holder,
|
||||
address,
|
||||
balance,
|
||||
utxo_tree,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn produce_ephemeral_key_holder(&self) -> EphemeralKeyHolder {
|
||||
self.key_holder.produce_ephemeral_key_holder()
|
||||
}
|
||||
|
||||
pub fn encrypt_data(
|
||||
ephemeral_key_holder: &EphemeralKeyHolder,
|
||||
viewing_public_key_receiver: AffinePoint,
|
||||
data: &[u8],
|
||||
) -> (CipherText, Nonce) {
|
||||
ephemeral_key_holder.encrypt_data(viewing_public_key_receiver, data)
|
||||
}
|
||||
|
||||
pub fn decrypt_data(
|
||||
&self,
|
||||
ephemeral_public_key_sender: AffinePoint,
|
||||
ciphertext: CipherText,
|
||||
nonce: Nonce,
|
||||
) -> Result<Vec<u8>, aes_gcm::Error> {
|
||||
self.key_holder
|
||||
.decrypt_data(ephemeral_public_key_sender, ciphertext, nonce)
|
||||
}
|
||||
|
||||
pub fn mark_spent_utxo(
|
||||
&mut self,
|
||||
utxo_nullifier_map: HashMap<TreeHashType, UTXONullifier>,
|
||||
) -> Result<()> {
|
||||
for (hash, nullifier) in utxo_nullifier_map {
|
||||
if let Some(utxo_entry) = self.utxo_tree.store.get_mut(&hash) {
|
||||
utxo_entry.consume_utxo(nullifier)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_new_utxo_outputs(&mut self, utxos: Vec<UTXO>) -> Result<()> {
|
||||
Ok(self.utxo_tree.insert_items(utxos)?)
|
||||
}
|
||||
|
||||
pub fn update_public_balance(&mut self, new_balance: u64) {
|
||||
self.balance = new_balance;
|
||||
}
|
||||
|
||||
pub fn add_asset<Asset: Serialize>(
|
||||
&mut self,
|
||||
asset: Asset,
|
||||
amount: u128,
|
||||
privacy_flag: bool,
|
||||
) -> Result<()> {
|
||||
let payload_with_asset = UTXOPayload {
|
||||
owner: self.address,
|
||||
asset: serde_json::to_vec(&asset)?,
|
||||
amount,
|
||||
privacy_flag,
|
||||
};
|
||||
|
||||
let asset_utxo = UTXO::create_utxo_from_payload(payload_with_asset);
|
||||
|
||||
self.utxo_tree.insert_item(asset_utxo)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn log(&self) {
|
||||
info!("Keys generated");
|
||||
info!("Account address is {:?}", hex::encode(self.address));
|
||||
info!("Account balance is {:?}", self.balance);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Account {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn generate_dummy_utxo_nullifier() -> UTXONullifier {
|
||||
UTXONullifier::default()
|
||||
}
|
||||
|
||||
fn generate_dummy_utxo(address: TreeHashType, amount: u128) -> UTXO {
|
||||
let payload = UTXOPayload {
|
||||
owner: address,
|
||||
asset: vec![],
|
||||
amount,
|
||||
privacy_flag: false,
|
||||
};
|
||||
UTXO::create_utxo_from_payload(payload)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_account() {
|
||||
let account = Account::new();
|
||||
|
||||
assert_eq!(account.balance, 0);
|
||||
assert!(account.key_holder.address != [0u8; 32]); // Check if the address is not empty
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mark_spent_utxo() {
|
||||
let mut account = Account::new();
|
||||
let utxo = generate_dummy_utxo(account.address, 100);
|
||||
account.add_new_utxo_outputs(vec![utxo]).unwrap();
|
||||
|
||||
let mut utxo_nullifier_map = HashMap::new();
|
||||
utxo_nullifier_map.insert(account.address, generate_dummy_utxo_nullifier());
|
||||
|
||||
let result = account.mark_spent_utxo(utxo_nullifier_map);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert!(account.utxo_tree.store.get(&account.address).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_new_utxo_outputs() {
|
||||
let mut account = Account::new();
|
||||
let utxo1 = generate_dummy_utxo(account.address, 100);
|
||||
let utxo2 = generate_dummy_utxo(account.address, 200);
|
||||
|
||||
let result = account.add_new_utxo_outputs(vec![utxo1.clone(), utxo2.clone()]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(account.utxo_tree.store.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_public_balance() {
|
||||
let mut account = Account::new();
|
||||
account.update_public_balance(500);
|
||||
|
||||
assert_eq!(account.balance, 500);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_asset() {
|
||||
let mut account = Account::new();
|
||||
let asset = "dummy_asset";
|
||||
let amount = 1000u128;
|
||||
|
||||
let result = account.add_asset(asset, amount, false);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(account.utxo_tree.store.len(), 1);
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
use elliptic_curve::{
|
||||
consts::{B0, B1},
|
||||
generic_array::GenericArray,
|
||||
};
|
||||
use sha2::digest::typenum::{UInt, UTerm};
|
||||
|
||||
pub const NULLIFIER_SECRET_CONST: [u8; 32] = [
|
||||
38, 29, 97, 210, 148, 172, 75, 220, 36, 249, 27, 111, 73, 14, 250, 38, 55, 87, 164, 169, 95,
|
||||
101, 135, 28, 212, 241, 107, 46, 162, 60, 59, 93,
|
||||
];
|
||||
pub const VIEVING_SECRET_CONST: [u8; 32] = [
|
||||
97, 23, 175, 117, 11, 48, 215, 162, 150, 103, 46, 195, 179, 178, 93, 52, 137, 190, 202, 60,
|
||||
254, 87, 112, 250, 57, 242, 117, 206, 195, 149, 213, 206,
|
||||
];
|
||||
|
||||
pub type CipherText = Vec<u8>;
|
||||
pub type Nonce = GenericArray<u8, UInt<UInt<UInt<UInt<UTerm, B1>, B1>, B0>, B0>>;
|
||||
@ -1,62 +0,0 @@
|
||||
use aes_gcm::{aead::Aead, AeadCore, Aes256Gcm, Key, KeyInit};
|
||||
use elliptic_curve::group::GroupEncoding;
|
||||
use elliptic_curve::PrimeField;
|
||||
use k256::{AffinePoint, FieldBytes, Scalar};
|
||||
use log::info;
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
|
||||
use super::constants_types::{CipherText, Nonce};
|
||||
|
||||
#[derive(Debug)]
|
||||
///Ephemeral secret key holder. Non-clonable as intended for one-time use. Produces ephemeral public keys. Can produce shared secret for sender.
|
||||
pub struct EphemeralKeyHolder {
|
||||
ephemeral_secret_key: Scalar,
|
||||
}
|
||||
|
||||
impl EphemeralKeyHolder {
|
||||
pub fn new_os_random() -> Self {
|
||||
let mut bytes = FieldBytes::default();
|
||||
|
||||
OsRng.fill_bytes(&mut bytes);
|
||||
|
||||
Self {
|
||||
ephemeral_secret_key: Scalar::from_repr(bytes).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_ephemeral_public_key(&self) -> AffinePoint {
|
||||
(AffinePoint::GENERATOR * self.ephemeral_secret_key).into()
|
||||
}
|
||||
|
||||
pub fn calculate_shared_secret_sender(
|
||||
&self,
|
||||
viewing_public_key_receiver: AffinePoint,
|
||||
) -> AffinePoint {
|
||||
(viewing_public_key_receiver * self.ephemeral_secret_key).into()
|
||||
}
|
||||
|
||||
pub fn encrypt_data(
|
||||
&self,
|
||||
viewing_public_key_receiver: AffinePoint,
|
||||
data: &[u8],
|
||||
) -> (CipherText, Nonce) {
|
||||
let key_point = self.calculate_shared_secret_sender(viewing_public_key_receiver);
|
||||
let binding = key_point.to_bytes();
|
||||
let key_raw = &binding.as_slice()[..32];
|
||||
let key_raw_adjust: [u8; 32] = key_raw.try_into().unwrap();
|
||||
|
||||
let key: Key<Aes256Gcm> = key_raw_adjust.into();
|
||||
|
||||
let cipher = Aes256Gcm::new(&key);
|
||||
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
|
||||
|
||||
(cipher.encrypt(&nonce, data).unwrap(), nonce)
|
||||
}
|
||||
|
||||
pub fn log(&self) {
|
||||
info!(
|
||||
"Ephemeral private key is {:?}",
|
||||
hex::encode(self.ephemeral_secret_key.to_bytes())
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,383 +0,0 @@
|
||||
use aes_gcm::{aead::Aead, Aes256Gcm, Key, KeyInit};
|
||||
use constants_types::{CipherText, Nonce};
|
||||
use elliptic_curve::group::GroupEncoding;
|
||||
use ephemeral_key_holder::EphemeralKeyHolder;
|
||||
use k256::AffinePoint;
|
||||
use log::info;
|
||||
use secret_holders::{SeedHolder, TopSecretKeyHolder, UTXOSecretKeyHolder};
|
||||
use storage::merkle_tree_public::TreeHashType;
|
||||
|
||||
use crate::account_core::PublicKey;
|
||||
|
||||
pub mod constants_types;
|
||||
pub mod ephemeral_key_holder;
|
||||
pub mod secret_holders;
|
||||
|
||||
#[derive(Clone)]
|
||||
///Entrypoint to key management
|
||||
pub struct AddressKeyHolder {
|
||||
//Will be useful in future
|
||||
#[allow(dead_code)]
|
||||
top_secret_key_holder: TopSecretKeyHolder,
|
||||
pub utxo_secret_key_holder: UTXOSecretKeyHolder,
|
||||
pub address: TreeHashType,
|
||||
pub nullifer_public_key: PublicKey,
|
||||
pub viewing_public_key: PublicKey,
|
||||
}
|
||||
|
||||
impl AddressKeyHolder {
|
||||
pub fn new_os_random() -> Self {
|
||||
//Currently dropping SeedHolder at the end of initialization.
|
||||
//Now entirely sure if we need it in the future.
|
||||
let seed_holder = SeedHolder::new_os_random();
|
||||
let top_secret_key_holder = seed_holder.produce_top_secret_key_holder();
|
||||
|
||||
let utxo_secret_key_holder = top_secret_key_holder.produce_utxo_secret_holder();
|
||||
|
||||
let address = utxo_secret_key_holder.generate_address();
|
||||
let nullifer_public_key = utxo_secret_key_holder.generate_nullifier_public_key();
|
||||
let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key();
|
||||
|
||||
Self {
|
||||
top_secret_key_holder,
|
||||
utxo_secret_key_holder,
|
||||
address,
|
||||
nullifer_public_key,
|
||||
viewing_public_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_shared_secret_receiver(
|
||||
&self,
|
||||
ephemeral_public_key_sender: AffinePoint,
|
||||
) -> AffinePoint {
|
||||
(ephemeral_public_key_sender * self.utxo_secret_key_holder.viewing_secret_key).into()
|
||||
}
|
||||
|
||||
pub fn produce_ephemeral_key_holder(&self) -> EphemeralKeyHolder {
|
||||
EphemeralKeyHolder::new_os_random()
|
||||
}
|
||||
|
||||
pub fn decrypt_data(
|
||||
&self,
|
||||
ephemeral_public_key_sender: AffinePoint,
|
||||
ciphertext: CipherText,
|
||||
nonce: Nonce,
|
||||
) -> Result<Vec<u8>, aes_gcm::Error> {
|
||||
let key_point = self.calculate_shared_secret_receiver(ephemeral_public_key_sender);
|
||||
let binding = key_point.to_bytes();
|
||||
let key_raw = &binding.as_slice()[..32];
|
||||
let key_raw_adjust: [u8; 32] = key_raw.try_into().unwrap();
|
||||
|
||||
let key: Key<Aes256Gcm> = key_raw_adjust.into();
|
||||
|
||||
let cipher = Aes256Gcm::new(&key);
|
||||
|
||||
cipher.decrypt(&nonce, ciphertext.as_slice())
|
||||
}
|
||||
|
||||
pub fn log(&self) {
|
||||
info!(
|
||||
"Secret spending key is {:?}",
|
||||
hex::encode(self.top_secret_key_holder.secret_spending_key.to_bytes()),
|
||||
);
|
||||
info!(
|
||||
"Nulifier secret key is {:?}",
|
||||
hex::encode(self.utxo_secret_key_holder.nullifier_secret_key.to_bytes()),
|
||||
);
|
||||
info!(
|
||||
"Viewing secret key is {:?}",
|
||||
hex::encode(self.utxo_secret_key_holder.viewing_secret_key.to_bytes()),
|
||||
);
|
||||
info!(
|
||||
"Nullifier public key is {:?}",
|
||||
hex::encode(self.nullifer_public_key.to_bytes()),
|
||||
);
|
||||
info!(
|
||||
"Viewing public key is {:?}",
|
||||
hex::encode(self.viewing_public_key.to_bytes()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use aes_gcm::{
|
||||
aead::{Aead, KeyInit, OsRng},
|
||||
Aes256Gcm,
|
||||
};
|
||||
use constants_types::{CipherText, Nonce};
|
||||
use constants_types::{NULLIFIER_SECRET_CONST, VIEVING_SECRET_CONST};
|
||||
use elliptic_curve::ff::Field;
|
||||
use elliptic_curve::group::prime::PrimeCurveAffine;
|
||||
use elliptic_curve::group::GroupEncoding;
|
||||
use k256::{AffinePoint, ProjectivePoint, Scalar};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_new_os_random() {
|
||||
// Ensure that a new AddressKeyHolder instance can be created without errors.
|
||||
let address_key_holder = AddressKeyHolder::new_os_random();
|
||||
|
||||
// Check that key holder fields are initialized with expected types
|
||||
assert!(!Into::<bool>::into(
|
||||
address_key_holder.nullifer_public_key.is_identity()
|
||||
));
|
||||
assert!(!Into::<bool>::into(
|
||||
address_key_holder.viewing_public_key.is_identity()
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_shared_secret_receiver() {
|
||||
let address_key_holder = AddressKeyHolder::new_os_random();
|
||||
|
||||
// Generate a random ephemeral public key sender
|
||||
let scalar = Scalar::random(&mut OsRng);
|
||||
let ephemeral_public_key_sender = (ProjectivePoint::generator() * scalar).to_affine();
|
||||
|
||||
// Calculate shared secret
|
||||
let shared_secret =
|
||||
address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender);
|
||||
|
||||
// Ensure the shared secret is not an identity point (suggesting non-zero output)
|
||||
assert!(!Into::<bool>::into(shared_secret.is_identity()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decrypt_data() {
|
||||
let address_key_holder = AddressKeyHolder::new_os_random();
|
||||
|
||||
// Generate an ephemeral key and shared secret
|
||||
let scalar = Scalar::random(OsRng);
|
||||
let ephemeral_public_key_sender = address_key_holder
|
||||
.produce_ephemeral_key_holder()
|
||||
.generate_ephemeral_public_key();
|
||||
let shared_secret =
|
||||
address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender);
|
||||
|
||||
// Prepare the encryption key from shared secret
|
||||
let key_raw = shared_secret.to_bytes();
|
||||
let key_raw_adjust_pre = &key_raw.as_slice()[..32];
|
||||
let key_raw_adjust: [u8; 32] = key_raw_adjust_pre.try_into().unwrap();
|
||||
let key: Key<Aes256Gcm> = key_raw_adjust.into();
|
||||
|
||||
let cipher = Aes256Gcm::new(&key);
|
||||
|
||||
// Encrypt sample data
|
||||
let nonce = Nonce::from_slice(b"unique nonce");
|
||||
let plaintext = b"Sensitive data";
|
||||
let ciphertext = cipher
|
||||
.encrypt(nonce, plaintext.as_ref())
|
||||
.expect("encryption failure");
|
||||
|
||||
// Attempt decryption
|
||||
let decrypted_data: Vec<u8> = address_key_holder
|
||||
.decrypt_data(
|
||||
ephemeral_public_key_sender,
|
||||
CipherText::from(ciphertext),
|
||||
nonce.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Verify decryption is successful and matches original plaintext
|
||||
assert_eq!(decrypted_data, plaintext);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_os_random_initialization() {
|
||||
// Ensure that AddressKeyHolder is initialized correctly
|
||||
let address_key_holder = AddressKeyHolder::new_os_random();
|
||||
|
||||
// Check that key holder fields are initialized with expected types and values
|
||||
assert!(!Into::<bool>::into(
|
||||
address_key_holder.nullifer_public_key.is_identity()
|
||||
));
|
||||
assert!(!Into::<bool>::into(
|
||||
address_key_holder.viewing_public_key.is_identity()
|
||||
));
|
||||
assert!(address_key_holder.address.as_slice().len() > 0); // Assume TreeHashType has non-zero length for a valid address
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_shared_secret_with_identity_point() {
|
||||
let address_key_holder = AddressKeyHolder::new_os_random();
|
||||
|
||||
// Use identity point as ephemeral public key
|
||||
let identity_point = AffinePoint::identity();
|
||||
|
||||
// Calculate shared secret
|
||||
let shared_secret = address_key_holder.calculate_shared_secret_receiver(identity_point);
|
||||
|
||||
// The shared secret with the identity point should also result in the identity point
|
||||
assert!(Into::<bool>::into(shared_secret.is_identity()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_decrypt_data_with_incorrect_nonce() {
|
||||
let address_key_holder = AddressKeyHolder::new_os_random();
|
||||
|
||||
// Generate ephemeral public key and shared secret
|
||||
let scalar = Scalar::random(OsRng);
|
||||
let ephemeral_public_key_sender = (ProjectivePoint::generator() * scalar).to_affine();
|
||||
let shared_secret =
|
||||
address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender);
|
||||
|
||||
// Prepare the encryption key from shared secret
|
||||
let key_raw = shared_secret.to_bytes();
|
||||
let key_raw_adjust_pre = &key_raw.as_slice()[..32];
|
||||
let key_raw_adjust: [u8; 32] = key_raw_adjust_pre.try_into().unwrap();
|
||||
let key: Key<Aes256Gcm> = key_raw_adjust.into();
|
||||
|
||||
let cipher = Aes256Gcm::new(&key);
|
||||
|
||||
// Encrypt sample data with a specific nonce
|
||||
let nonce = Nonce::from_slice(b"unique nonce");
|
||||
let plaintext = b"Sensitive data";
|
||||
let ciphertext = cipher
|
||||
.encrypt(nonce, plaintext.as_ref())
|
||||
.expect("encryption failure");
|
||||
|
||||
// Attempt decryption with an incorrect nonce
|
||||
let incorrect_nonce = Nonce::from_slice(b"wrong nonce");
|
||||
let decrypted_data = address_key_holder
|
||||
.decrypt_data(
|
||||
ephemeral_public_key_sender,
|
||||
CipherText::from(ciphertext.clone()),
|
||||
incorrect_nonce.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// The decryption should fail or produce incorrect output due to nonce mismatch
|
||||
assert_ne!(decrypted_data, plaintext);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_decrypt_data_with_incorrect_ciphertext() {
|
||||
let address_key_holder = AddressKeyHolder::new_os_random();
|
||||
|
||||
// Generate ephemeral public key and shared secret
|
||||
let scalar = Scalar::random(OsRng);
|
||||
let ephemeral_public_key_sender = (ProjectivePoint::generator() * scalar).to_affine();
|
||||
let shared_secret =
|
||||
address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender);
|
||||
|
||||
// Prepare the encryption key from shared secret
|
||||
let key_raw = shared_secret.to_bytes();
|
||||
let key_raw_adjust_pre = &key_raw.as_slice()[..32];
|
||||
let key_raw_adjust: [u8; 32] = key_raw_adjust_pre.try_into().unwrap();
|
||||
let key: Key<Aes256Gcm> = key_raw_adjust.into();
|
||||
|
||||
let cipher = Aes256Gcm::new(&key);
|
||||
|
||||
// Encrypt sample data
|
||||
let nonce = Nonce::from_slice(b"unique nonce");
|
||||
let plaintext = b"Sensitive data";
|
||||
let ciphertext = cipher
|
||||
.encrypt(nonce, plaintext.as_ref())
|
||||
.expect("encryption failure");
|
||||
|
||||
// Tamper with the ciphertext to simulate corruption
|
||||
let mut corrupted_ciphertext = ciphertext.clone();
|
||||
corrupted_ciphertext[0] ^= 1; // Flip a bit in the ciphertext
|
||||
|
||||
// Attempt decryption
|
||||
let result = address_key_holder
|
||||
.decrypt_data(
|
||||
ephemeral_public_key_sender,
|
||||
CipherText::from(corrupted_ciphertext),
|
||||
nonce.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// The decryption should fail or produce incorrect output due to tampered ciphertext
|
||||
assert_ne!(result, plaintext);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encryption_decryption_round_trip() {
|
||||
let address_key_holder = AddressKeyHolder::new_os_random();
|
||||
|
||||
// Generate ephemeral key and shared secret
|
||||
let scalar = Scalar::random(OsRng);
|
||||
let ephemeral_public_key_sender = (ProjectivePoint::generator() * scalar).to_affine();
|
||||
|
||||
// Encrypt sample data
|
||||
let plaintext = b"Round-trip test data";
|
||||
let nonce = Nonce::from_slice(b"unique nonce");
|
||||
|
||||
let shared_secret =
|
||||
address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender);
|
||||
// Prepare the encryption key from shared secret
|
||||
let key_raw = shared_secret.to_bytes();
|
||||
let key_raw_adjust_pre = &key_raw.as_slice()[..32];
|
||||
let key_raw_adjust: [u8; 32] = key_raw_adjust_pre.try_into().unwrap();
|
||||
let key: Key<Aes256Gcm> = key_raw_adjust.into();
|
||||
let cipher = Aes256Gcm::new(&key);
|
||||
|
||||
let ciphertext = cipher
|
||||
.encrypt(nonce, plaintext.as_ref())
|
||||
.expect("encryption failure");
|
||||
|
||||
// Decrypt the data using the `AddressKeyHolder` instance
|
||||
let decrypted_data = address_key_holder
|
||||
.decrypt_data(
|
||||
ephemeral_public_key_sender,
|
||||
CipherText::from(ciphertext),
|
||||
nonce.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Verify the decrypted data matches the original plaintext
|
||||
assert_eq!(decrypted_data, plaintext);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_generation_test() {
|
||||
let seed_holder = SeedHolder::new_os_random();
|
||||
let top_secret_key_holder = seed_holder.produce_top_secret_key_holder();
|
||||
|
||||
let utxo_secret_key_holder = top_secret_key_holder.produce_utxo_secret_holder();
|
||||
|
||||
let address = utxo_secret_key_holder.generate_address();
|
||||
let nullifer_public_key = utxo_secret_key_holder.generate_nullifier_public_key();
|
||||
let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key();
|
||||
|
||||
println!("======Prerequisites======");
|
||||
println!();
|
||||
|
||||
println!(
|
||||
"Group generator {:?}",
|
||||
hex::encode(AffinePoint::GENERATOR.to_bytes())
|
||||
);
|
||||
println!(
|
||||
"Nullifier constant {:?}",
|
||||
hex::encode(NULLIFIER_SECRET_CONST)
|
||||
);
|
||||
println!("Viewing constatnt {:?}", hex::encode(VIEVING_SECRET_CONST));
|
||||
println!();
|
||||
|
||||
println!("======Holders======");
|
||||
println!();
|
||||
|
||||
println!("{seed_holder:?}");
|
||||
println!("{top_secret_key_holder:?}");
|
||||
println!("{utxo_secret_key_holder:?}");
|
||||
println!();
|
||||
|
||||
println!("======Public data======");
|
||||
println!();
|
||||
println!("Address{:?}", hex::encode(address));
|
||||
println!(
|
||||
"Nulifier public key {:?}",
|
||||
hex::encode(nullifer_public_key.to_bytes())
|
||||
);
|
||||
println!(
|
||||
"Viewing public key {:?}",
|
||||
hex::encode(viewing_public_key.to_bytes())
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,113 +0,0 @@
|
||||
use elliptic_curve::group::GroupEncoding;
|
||||
use elliptic_curve::PrimeField;
|
||||
use k256::{AffinePoint, FieldBytes, Scalar};
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
use sha2::{digest::FixedOutput, Digest};
|
||||
use storage::merkle_tree_public::TreeHashType;
|
||||
|
||||
use super::constants_types::{NULLIFIER_SECRET_CONST, VIEVING_SECRET_CONST};
|
||||
|
||||
#[derive(Debug)]
|
||||
///Seed holder. Non-clonable to ensure that different holders use different seeds.
|
||||
/// Produces `TopSecretKeyHolder` objects.
|
||||
pub struct SeedHolder {
|
||||
seed: Scalar,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
///Secret spending key holder. Produces `UTXOSecretKeyHolder` objects.
|
||||
pub struct TopSecretKeyHolder {
|
||||
pub secret_spending_key: Scalar,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
///Nullifier secret key and viewing secret key holder. Produces public keys. Can produce address. Can produce shared secret for recepient.
|
||||
pub struct UTXOSecretKeyHolder {
|
||||
pub nullifier_secret_key: Scalar,
|
||||
pub viewing_secret_key: Scalar,
|
||||
}
|
||||
|
||||
impl SeedHolder {
|
||||
pub fn new_os_random() -> Self {
|
||||
let mut bytes = FieldBytes::default();
|
||||
|
||||
OsRng.fill_bytes(&mut bytes);
|
||||
|
||||
Self {
|
||||
seed: Scalar::from_repr(bytes).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_secret_spending_key_hash(&self) -> TreeHashType {
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
|
||||
hasher.update(self.seed.to_bytes());
|
||||
|
||||
<TreeHashType>::from(hasher.finalize_fixed())
|
||||
}
|
||||
|
||||
pub fn generate_secret_spending_key_scalar(&self) -> Scalar {
|
||||
let hash = self.generate_secret_spending_key_hash();
|
||||
|
||||
Scalar::from_repr(hash.into()).unwrap()
|
||||
}
|
||||
|
||||
pub fn produce_top_secret_key_holder(&self) -> TopSecretKeyHolder {
|
||||
TopSecretKeyHolder {
|
||||
secret_spending_key: self.generate_secret_spending_key_scalar(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TopSecretKeyHolder {
|
||||
pub fn generate_nullifier_secret_key(&self) -> Scalar {
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
|
||||
hasher.update(self.secret_spending_key.to_bytes());
|
||||
hasher.update(NULLIFIER_SECRET_CONST);
|
||||
|
||||
let hash = <TreeHashType>::from(hasher.finalize_fixed());
|
||||
|
||||
Scalar::from_repr(hash.into()).unwrap()
|
||||
}
|
||||
|
||||
pub fn generate_viewing_secret_key(&self) -> Scalar {
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
|
||||
hasher.update(self.secret_spending_key.to_bytes());
|
||||
hasher.update(VIEVING_SECRET_CONST);
|
||||
|
||||
let hash = <TreeHashType>::from(hasher.finalize_fixed());
|
||||
|
||||
Scalar::from_repr(hash.into()).unwrap()
|
||||
}
|
||||
|
||||
pub fn produce_utxo_secret_holder(&self) -> UTXOSecretKeyHolder {
|
||||
UTXOSecretKeyHolder {
|
||||
nullifier_secret_key: self.generate_nullifier_secret_key(),
|
||||
viewing_secret_key: self.generate_viewing_secret_key(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UTXOSecretKeyHolder {
|
||||
pub fn generate_nullifier_public_key(&self) -> AffinePoint {
|
||||
(AffinePoint::GENERATOR * self.nullifier_secret_key).into()
|
||||
}
|
||||
|
||||
pub fn generate_viewing_public_key(&self) -> AffinePoint {
|
||||
(AffinePoint::GENERATOR * self.viewing_secret_key).into()
|
||||
}
|
||||
|
||||
pub fn generate_address(&self) -> TreeHashType {
|
||||
let npk = self.generate_nullifier_public_key();
|
||||
let vpk = self.generate_viewing_public_key();
|
||||
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
|
||||
hasher.update(npk.to_bytes());
|
||||
hasher.update(vpk.to_bytes());
|
||||
|
||||
<TreeHashType>::from(hasher.finalize_fixed())
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
pub mod account_core;
|
||||
pub mod key_management;
|
||||
BIN
artifacts/program_methods/amm.bin
Normal file
BIN
artifacts/program_methods/amm.bin
Normal file
Binary file not shown.
BIN
artifacts/program_methods/authenticated_transfer.bin
Normal file
BIN
artifacts/program_methods/authenticated_transfer.bin
Normal file
Binary file not shown.
BIN
artifacts/program_methods/pinata.bin
Normal file
BIN
artifacts/program_methods/pinata.bin
Normal file
Binary file not shown.
BIN
artifacts/program_methods/pinata_token.bin
Normal file
BIN
artifacts/program_methods/pinata_token.bin
Normal file
Binary file not shown.
BIN
artifacts/program_methods/privacy_preserving_circuit.bin
Normal file
BIN
artifacts/program_methods/privacy_preserving_circuit.bin
Normal file
Binary file not shown.
BIN
artifacts/program_methods/token.bin
Normal file
BIN
artifacts/program_methods/token.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/burner.bin
Normal file
BIN
artifacts/test_program_methods/burner.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/chain_caller.bin
Normal file
BIN
artifacts/test_program_methods/chain_caller.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/changer_claimer.bin
Normal file
BIN
artifacts/test_program_methods/changer_claimer.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/claimer.bin
Normal file
BIN
artifacts/test_program_methods/claimer.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/data_changer.bin
Normal file
BIN
artifacts/test_program_methods/data_changer.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/extra_output.bin
Normal file
BIN
artifacts/test_program_methods/extra_output.bin
Normal file
Binary file not shown.
Binary file not shown.
BIN
artifacts/test_program_methods/minter.bin
Normal file
BIN
artifacts/test_program_methods/minter.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/missing_output.bin
Normal file
BIN
artifacts/test_program_methods/missing_output.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/modified_transfer.bin
Normal file
BIN
artifacts/test_program_methods/modified_transfer.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/nonce_changer.bin
Normal file
BIN
artifacts/test_program_methods/nonce_changer.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/noop.bin
Normal file
BIN
artifacts/test_program_methods/noop.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/program_owner_changer.bin
Normal file
BIN
artifacts/test_program_methods/program_owner_changer.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/simple_balance_transfer.bin
Normal file
BIN
artifacts/test_program_methods/simple_balance_transfer.bin
Normal file
Binary file not shown.
32
bedrock/README.md
Normal file
32
bedrock/README.md
Normal file
@ -0,0 +1,32 @@
|
||||
# Bedrock Configuration Files for All-in-One run and Integration Tests
|
||||
|
||||
## How to update
|
||||
|
||||
- `docker-compose.yml` file.
|
||||
|
||||
Compare with `https://github.com/logos-blockchain/logos-blockchain/blob/master/compose.static.yml` and update the file accordingly, don't bring unneeded things like grafana and etc.
|
||||
Replace `sha` hash with the latest `testnet` tag hash.
|
||||
|
||||
- `scripts` folder.
|
||||
|
||||
```bash
|
||||
curl https://raw.githubusercontent.com/logos-blockchain/logos-blockchain/master/testnet/scripts/run_cfgsync.sh >> scripts/run_cfgsync.sh
|
||||
curl https://raw.githubusercontent.com/logos-blockchain/logos-blockchain/master/testnet/scripts/run_logos_blockchain_node.sh >> scripts/run_logos_blockchain_node.sh
|
||||
chmod +x scripts/*
|
||||
```
|
||||
|
||||
Then in `scripts/run_logos_blockchain_node.sh` update `cfgsync-client` to `logos-blockchain-cfgsync-client` and in `scripts/run_cfgsync.sh` update `cfgsync-server` to `logos-blockchain-cfgsync-server` if it hasn't been fixed already, see <https://github.com/logos-blockchain/logos-blockchain/pull/2092>.
|
||||
|
||||
- `cfgsync.yaml` file.
|
||||
|
||||
```bash
|
||||
curl -O https://raw.githubusercontent.com/logos-blockchain/logos-blockchain/master/testnet/cfgsync.yaml
|
||||
```
|
||||
|
||||
Set `logger`, `tracing` and `metrics` to `None`
|
||||
|
||||
- `kzgrs_test_params` file.
|
||||
|
||||
```bash
|
||||
curl -O https://raw.githubusercontent.com/logos-blockchain/logos-blockchain/master/tests/kzgrs/kzgrs_test_params
|
||||
```
|
||||
12
bedrock/cfgsync.yaml
Normal file
12
bedrock/cfgsync.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
port: 4400
|
||||
n_hosts: 4
|
||||
timeout: 10
|
||||
|
||||
# Tracing
|
||||
tracing_settings:
|
||||
logger: Stdout
|
||||
tracing: None
|
||||
filter: None
|
||||
metrics: None
|
||||
console: None
|
||||
level: DEBUG
|
||||
46
bedrock/docker-compose.yml
Normal file
46
bedrock/docker-compose.yml
Normal file
@ -0,0 +1,46 @@
|
||||
services:
|
||||
|
||||
cfgsync:
|
||||
image: ghcr.io/logos-blockchain/logos-blockchain@sha256:000982e751dfd346ca5346b8025c685fc3abc585079c59cde3bde7fd63100657
|
||||
volumes:
|
||||
- ./scripts:/etc/logos-blockchain/scripts
|
||||
- ./cfgsync.yaml:/etc/logos-blockchain/cfgsync.yaml:z
|
||||
entrypoint: /etc/logos-blockchain/scripts/run_cfgsync.sh
|
||||
|
||||
logos-blockchain-node-0:
|
||||
image: ghcr.io/logos-blockchain/logos-blockchain@sha256:000982e751dfd346ca5346b8025c685fc3abc585079c59cde3bde7fd63100657
|
||||
ports:
|
||||
- "${PORT:-8080}:18080/tcp"
|
||||
volumes:
|
||||
- ./scripts:/etc/logos-blockchain/scripts
|
||||
- ./kzgrs_test_params:/kzgrs_test_params:z
|
||||
depends_on:
|
||||
- cfgsync
|
||||
entrypoint: /etc/logos-blockchain/scripts/run_logos_blockchain_node.sh
|
||||
|
||||
logos-blockchain-node-1:
|
||||
image: ghcr.io/logos-blockchain/logos-blockchain@sha256:000982e751dfd346ca5346b8025c685fc3abc585079c59cde3bde7fd63100657
|
||||
volumes:
|
||||
- ./scripts:/etc/logos-blockchain/scripts
|
||||
- ./kzgrs_test_params:/kzgrs_test_params:z
|
||||
depends_on:
|
||||
- cfgsync
|
||||
entrypoint: /etc/logos-blockchain/scripts/run_logos_blockchain_node.sh
|
||||
|
||||
logos-blockchain-node-2:
|
||||
image: ghcr.io/logos-blockchain/logos-blockchain@sha256:000982e751dfd346ca5346b8025c685fc3abc585079c59cde3bde7fd63100657
|
||||
volumes:
|
||||
- ./scripts:/etc/logos-blockchain/scripts
|
||||
- ./kzgrs_test_params:/kzgrs_test_params:z
|
||||
depends_on:
|
||||
- cfgsync
|
||||
entrypoint: /etc/logos-blockchain/scripts/run_logos_blockchain_node.sh
|
||||
|
||||
logos-blockchain-node-3:
|
||||
image: ghcr.io/logos-blockchain/logos-blockchain@sha256:000982e751dfd346ca5346b8025c685fc3abc585079c59cde3bde7fd63100657
|
||||
volumes:
|
||||
- ./scripts:/etc/logos-blockchain/scripts
|
||||
- ./kzgrs_test_params:/kzgrs_test_params:z
|
||||
depends_on:
|
||||
- cfgsync
|
||||
entrypoint: /etc/logos-blockchain/scripts/run_logos_blockchain_node.sh
|
||||
BIN
bedrock/kzgrs_test_params
Normal file
BIN
bedrock/kzgrs_test_params
Normal file
Binary file not shown.
5
bedrock/scripts/run_cfgsync.sh
Executable file
5
bedrock/scripts/run_cfgsync.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
exec /usr/bin/logos-blockchain-cfgsync-server /etc/logos-blockchain/cfgsync.yaml
|
||||
13
bedrock/scripts/run_logos_blockchain_node.sh
Executable file
13
bedrock/scripts/run_logos_blockchain_node.sh
Executable file
@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
export CFG_FILE_PATH="/config.yaml" \
|
||||
CFG_SERVER_ADDR="http://cfgsync:4400" \
|
||||
CFG_HOST_IP=$(hostname -i) \
|
||||
CFG_HOST_IDENTIFIER="validator-$(hostname -i)" \
|
||||
LOG_LEVEL="INFO" \
|
||||
POL_PROOF_DEV_MODE=true
|
||||
|
||||
/usr/bin/logos-blockchain-cfgsync-client && \
|
||||
exec /usr/bin/logos-blockchain-node /config.yaml
|
||||
23
bedrock_client/Cargo.toml
Normal file
23
bedrock_client/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "bedrock_client"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
common.workspace = true
|
||||
|
||||
reqwest.workspace = true
|
||||
anyhow.workspace = true
|
||||
tokio-retry.workspace = true
|
||||
futures.workspace = true
|
||||
log.workspace = true
|
||||
serde.workspace = true
|
||||
humantime-serde.workspace = true
|
||||
logos-blockchain-common-http-client.workspace = true
|
||||
logos-blockchain-core.workspace = true
|
||||
logos-blockchain-chain-broadcast-service.workspace = true
|
||||
logos-blockchain-chain-service.workspace = true
|
||||
121
bedrock_client/src/lib.rs
Normal file
121
bedrock_client/src/lib.rs
Normal file
@ -0,0 +1,121 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use common::config::BasicAuth;
|
||||
use futures::{Stream, TryFutureExt as _};
|
||||
#[expect(clippy::single_component_path_imports, reason = "Satisfy machete")]
|
||||
use humantime_serde;
|
||||
use log::{info, warn};
|
||||
pub use logos_blockchain_chain_broadcast_service::BlockInfo;
|
||||
use logos_blockchain_chain_service::CryptarchiaInfo;
|
||||
pub use logos_blockchain_common_http_client::{CommonHttpClient, Error};
|
||||
pub use logos_blockchain_core::{block::Block, header::HeaderId, mantle::SignedMantleTx};
|
||||
use reqwest::{Client, Url};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio_retry::Retry;
|
||||
|
||||
/// Fibonacci backoff retry strategy configuration.
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
pub struct BackoffConfig {
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub start_delay: Duration,
|
||||
pub max_retries: usize,
|
||||
}
|
||||
|
||||
impl Default for BackoffConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
start_delay: Duration::from_millis(100),
|
||||
max_retries: 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple wrapper
|
||||
/// maybe extend in the future for our purposes
|
||||
/// `Clone` is cheap because `CommonHttpClient` is internally reference counted (`Arc`).
|
||||
#[derive(Clone)]
|
||||
pub struct BedrockClient {
|
||||
http_client: CommonHttpClient,
|
||||
node_url: Url,
|
||||
backoff: BackoffConfig,
|
||||
}
|
||||
|
||||
impl BedrockClient {
|
||||
pub fn new(backoff: BackoffConfig, node_url: Url, auth: Option<BasicAuth>) -> Result<Self> {
|
||||
info!("Creating Bedrock client with node URL {node_url}");
|
||||
let client = Client::builder()
|
||||
//Add more fields if needed
|
||||
.timeout(std::time::Duration::from_secs(60))
|
||||
.build()
|
||||
.context("Failed to build HTTP client")?;
|
||||
|
||||
let auth = auth.map(|a| {
|
||||
logos_blockchain_common_http_client::BasicAuthCredentials::new(a.username, a.password)
|
||||
});
|
||||
|
||||
let http_client = CommonHttpClient::new_with_client(client, auth);
|
||||
Ok(Self {
|
||||
http_client,
|
||||
node_url,
|
||||
backoff,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn post_transaction(&self, tx: SignedMantleTx) -> Result<Result<(), Error>, Error> {
|
||||
Retry::spawn(self.backoff_strategy(), || async {
|
||||
match self
|
||||
.http_client
|
||||
.post_transaction(self.node_url.clone(), tx.clone())
|
||||
.await
|
||||
{
|
||||
Ok(()) => Ok(Ok(())),
|
||||
Err(err) => match err {
|
||||
// Retry arm.
|
||||
// Retrying only reqwest errors: mainly connected to http.
|
||||
Error::Request(_) => Err(err),
|
||||
// Returning non-retryable error
|
||||
Error::Server(_) | Error::Client(_) | Error::Url(_) => Ok(Err(err)),
|
||||
},
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_lib_stream(&self) -> Result<impl Stream<Item = BlockInfo>, Error> {
|
||||
self.http_client.get_lib_stream(self.node_url.clone()).await
|
||||
}
|
||||
|
||||
pub async fn get_block_by_id(
|
||||
&self,
|
||||
header_id: HeaderId,
|
||||
) -> Result<Option<Block<SignedMantleTx>>, Error> {
|
||||
Retry::spawn(self.backoff_strategy(), || {
|
||||
self.http_client
|
||||
.get_block_by_id(self.node_url.clone(), header_id)
|
||||
.inspect_err(|err| warn!("Block fetching failed with error: {err:#}"))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_consensus_info(&self) -> Result<CryptarchiaInfo, Error> {
|
||||
Retry::spawn(self.backoff_strategy(), || {
|
||||
self.http_client
|
||||
.consensus_info(self.node_url.clone())
|
||||
.inspect_err(|err| warn!("Block fetching failed with error: {err:#}"))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
fn backoff_strategy(&self) -> impl Iterator<Item = Duration> {
|
||||
let start_delay_millis = self
|
||||
.backoff
|
||||
.start_delay
|
||||
.as_millis()
|
||||
.try_into()
|
||||
.expect("Start delay must be less than u64::MAX milliseconds");
|
||||
|
||||
tokio_retry::strategy::FibonacciBackoff::from_millis(start_delay_millis)
|
||||
.take(self.backoff.max_retries)
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
set -e
|
||||
curl -L https://risczero.com/install | bash
|
||||
/Users/runner/.risc0/bin/rzup install
|
||||
cargo build
|
||||
@ -1,4 +0,0 @@
|
||||
set -e
|
||||
curl -L https://risczero.com/install | bash
|
||||
/home/runner/.risc0/bin/rzup install
|
||||
cargo build
|
||||
@ -1,8 +0,0 @@
|
||||
set -e
|
||||
|
||||
curl -L https://risczero.com/install | bash
|
||||
/home/runner/.risc0/bin/rzup install
|
||||
cargo install taplo-cli --locked
|
||||
|
||||
cargo fmt -- --check
|
||||
taplo fmt --check
|
||||
@ -1,6 +0,0 @@
|
||||
set -e
|
||||
|
||||
curl -L https://risczero.com/install | bash
|
||||
/home/runner/.risc0/bin/rzup install
|
||||
|
||||
cargo test --release
|
||||
54
clippy.toml
Normal file
54
clippy.toml
Normal file
@ -0,0 +1,54 @@
|
||||
module-item-order-groupings = [
|
||||
[
|
||||
"use",
|
||||
[
|
||||
"use",
|
||||
],
|
||||
],
|
||||
[
|
||||
"modules",
|
||||
[
|
||||
"extern_crate",
|
||||
"mod",
|
||||
"foreign_mod",
|
||||
],
|
||||
],
|
||||
[
|
||||
"macros",
|
||||
[
|
||||
"macro",
|
||||
],
|
||||
],
|
||||
[
|
||||
"global_asm",
|
||||
[
|
||||
"global_asm",
|
||||
],
|
||||
],
|
||||
[
|
||||
"UPPER_SNAKE_CASE",
|
||||
[
|
||||
"static",
|
||||
"const",
|
||||
],
|
||||
],
|
||||
[
|
||||
"PascalCase",
|
||||
[
|
||||
"ty_alias",
|
||||
"enum",
|
||||
"struct",
|
||||
"union",
|
||||
"trait",
|
||||
"trait_alias",
|
||||
"impl",
|
||||
],
|
||||
],
|
||||
[
|
||||
"lower_snake_case",
|
||||
[
|
||||
"fn",
|
||||
],
|
||||
],
|
||||
]
|
||||
source-item-ordering = ["module"]
|
||||
23
common/Cargo.toml
Normal file
23
common/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "common"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nssa.workspace = true
|
||||
nssa_core.workspace = true
|
||||
|
||||
anyhow.workspace = true
|
||||
thiserror.workspace = true
|
||||
serde.workspace = true
|
||||
serde_with.workspace = true
|
||||
base64.workspace = true
|
||||
sha2.workspace = true
|
||||
log.workspace = true
|
||||
hex.workspace = true
|
||||
borsh.workspace = true
|
||||
logos-blockchain-common-http-client.workspace = true
|
||||
153
common/src/block.rs
Normal file
153
common/src/block.rs
Normal file
@ -0,0 +1,153 @@
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use nssa::AccountId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest as _, Sha256, digest::FixedOutput as _};
|
||||
|
||||
use crate::{HashType, transaction::NSSATransaction};
|
||||
|
||||
pub type MantleMsgId = [u8; 32];
|
||||
pub type BlockHash = HashType;
|
||||
pub type BlockId = u64;
|
||||
pub type TimeStamp = u64;
|
||||
|
||||
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
|
||||
pub struct BlockMeta {
|
||||
pub id: BlockId,
|
||||
pub hash: BlockHash,
|
||||
pub msg_id: MantleMsgId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// Our own hasher.
|
||||
/// Currently it is SHA256 hasher wrapper. May change in a future.
|
||||
pub struct OwnHasher;
|
||||
|
||||
impl OwnHasher {
|
||||
fn hash(data: &[u8]) -> HashType {
|
||||
let mut hasher = Sha256::new();
|
||||
|
||||
hasher.update(data);
|
||||
HashType(<[u8; 32]>::from(hasher.finalize_fixed()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
|
||||
pub struct BlockHeader {
|
||||
pub block_id: BlockId,
|
||||
pub prev_block_hash: BlockHash,
|
||||
pub hash: BlockHash,
|
||||
pub timestamp: TimeStamp,
|
||||
pub signature: nssa::Signature,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
|
||||
pub struct BlockBody {
|
||||
pub transactions: Vec<NSSATransaction>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
|
||||
pub enum BedrockStatus {
|
||||
Pending,
|
||||
Safe,
|
||||
Finalized,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
|
||||
pub struct Block {
|
||||
pub header: BlockHeader,
|
||||
pub body: BlockBody,
|
||||
pub bedrock_status: BedrockStatus,
|
||||
pub bedrock_parent_id: MantleMsgId,
|
||||
}
|
||||
|
||||
impl Serialize for Block {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
crate::borsh_base64::serialize(self, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Block {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
crate::borsh_base64::deserialize(deserializer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
||||
pub struct HashableBlockData {
|
||||
pub block_id: BlockId,
|
||||
pub prev_block_hash: BlockHash,
|
||||
pub timestamp: TimeStamp,
|
||||
pub transactions: Vec<NSSATransaction>,
|
||||
}
|
||||
|
||||
impl HashableBlockData {
|
||||
#[must_use]
|
||||
pub fn into_pending_block(
|
||||
self,
|
||||
signing_key: &nssa::PrivateKey,
|
||||
bedrock_parent_id: MantleMsgId,
|
||||
) -> Block {
|
||||
let data_bytes = borsh::to_vec(&self).unwrap();
|
||||
let signature = nssa::Signature::new(signing_key, &data_bytes);
|
||||
let hash = OwnHasher::hash(&data_bytes);
|
||||
Block {
|
||||
header: BlockHeader {
|
||||
block_id: self.block_id,
|
||||
prev_block_hash: self.prev_block_hash,
|
||||
hash,
|
||||
timestamp: self.timestamp,
|
||||
signature,
|
||||
},
|
||||
body: BlockBody {
|
||||
transactions: self.transactions,
|
||||
},
|
||||
bedrock_status: BedrockStatus::Pending,
|
||||
bedrock_parent_id,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn block_hash(&self) -> BlockHash {
|
||||
OwnHasher::hash(&borsh::to_vec(&self).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Block> for HashableBlockData {
|
||||
fn from(value: Block) -> Self {
|
||||
Self {
|
||||
block_id: value.header.block_id,
|
||||
prev_block_hash: value.header.prev_block_hash,
|
||||
timestamp: value.header.timestamp,
|
||||
transactions: value.body.transactions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct for account (de-)serialization.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AccountInitialData {
|
||||
pub account_id: AccountId,
|
||||
pub balance: u128,
|
||||
}
|
||||
|
||||
/// Helper struct to (de-)serialize initial commitments.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CommitmentsInitialData {
|
||||
pub npk: nssa_core::NullifierPublicKey,
|
||||
pub account: nssa_core::account::Account,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{HashType, block::HashableBlockData, test_utils};
|
||||
|
||||
#[test]
|
||||
fn encoding_roundtrip() {
|
||||
let transactions = vec![test_utils::produce_dummy_empty_transaction()];
|
||||
let block = test_utils::produce_dummy_block(1, Some(HashType([1; 32])), transactions);
|
||||
let hashable = HashableBlockData::from(block);
|
||||
let bytes = borsh::to_vec(&hashable).unwrap();
|
||||
let block_from_bytes = borsh::from_slice::<HashableBlockData>(&bytes).unwrap();
|
||||
assert_eq!(hashable, block_from_bytes);
|
||||
}
|
||||
}
|
||||
25
common/src/borsh_base64.rs
Normal file
25
common/src/borsh_base64.rs
Normal file
@ -0,0 +1,25 @@
|
||||
//! This module provides utilities for serializing and deserializing data by combining Borsh and
|
||||
//! Base64 encodings.
|
||||
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn serialize<T: BorshSerialize, S: serde::Serializer>(
|
||||
value: &T,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
let borsh_encoded = borsh::to_vec(value).map_err(serde::ser::Error::custom)?;
|
||||
let base64_encoded = STANDARD.encode(&borsh_encoded);
|
||||
Serialize::serialize(&base64_encoded, serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, T: BorshDeserialize, D: serde::Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> Result<T, D::Error> {
|
||||
let base64_encoded = <String as Deserialize>::deserialize(deserializer)?;
|
||||
let borsh_encoded = STANDARD
|
||||
.decode(base64_encoded.as_bytes())
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
borsh::from_slice(&borsh_encoded).map_err(serde::de::Error::custom)
|
||||
}
|
||||
55
common/src/config.rs
Normal file
55
common/src/config.rs
Normal file
@ -0,0 +1,55 @@
|
||||
//! Common configuration structures and utilities.
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use logos_blockchain_common_http_client::BasicAuthCredentials;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BasicAuth {
|
||||
pub username: String,
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BasicAuth {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.username)?;
|
||||
if let Some(password) = &self.password {
|
||||
write!(f, ":{password}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for BasicAuth {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let parse = || {
|
||||
let mut parts = s.splitn(2, ':');
|
||||
let username = parts.next()?;
|
||||
let password = parts.next().filter(|p| !p.is_empty());
|
||||
if parts.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((username, password))
|
||||
};
|
||||
|
||||
let (username, password) = parse().ok_or_else(|| {
|
||||
anyhow::anyhow!("Invalid auth format. Expected 'user' or 'user:password'")
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
username: username.to_owned(),
|
||||
password: password.map(std::string::ToString::to_string),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BasicAuth> for BasicAuthCredentials {
|
||||
fn from(value: BasicAuth) -> Self {
|
||||
Self::new(value.username, value.password)
|
||||
}
|
||||
}
|
||||
96
common/src/lib.rs
Normal file
96
common/src/lib.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||
|
||||
pub mod block;
|
||||
mod borsh_base64;
|
||||
pub mod config;
|
||||
pub mod transaction;
|
||||
|
||||
// Module for tests utility functions
|
||||
// TODO: Compile only for tests
|
||||
pub mod test_utils;
|
||||
|
||||
pub const PINATA_BASE58: &str = "EfQhKQAkX2FJiwNii2WFQsGndjvF1Mzd7RuVe7QdPLw7";
|
||||
|
||||
#[derive(
|
||||
Default,
|
||||
Copy,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
SerializeDisplay,
|
||||
DeserializeFromStr,
|
||||
BorshSerialize,
|
||||
BorshDeserialize,
|
||||
)]
|
||||
pub struct HashType(pub [u8; 32]);
|
||||
|
||||
impl Display for HashType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", hex::encode(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for HashType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", hex::encode(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for HashType {
|
||||
type Err = hex::FromHexError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut bytes = [0_u8; 32];
|
||||
hex::decode_to_slice(s, &mut bytes)?;
|
||||
Ok(Self(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for HashType {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HashType> for [u8; 32] {
|
||||
fn from(hash: HashType) -> Self {
|
||||
hash.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for HashType {
|
||||
fn from(bytes: [u8; 32]) -> Self {
|
||||
Self(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for HashType {
|
||||
type Error = <[u8; 32] as TryFrom<Vec<u8>>>::Error;
|
||||
|
||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
Ok(Self(value.try_into()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HashType> for Vec<u8> {
|
||||
fn from(hash: HashType) -> Self {
|
||||
hash.0.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serialization_roundtrip() {
|
||||
let original = HashType([1_u8; 32]);
|
||||
let serialized = original.to_string();
|
||||
let deserialized = HashType::from_str(&serialized).unwrap();
|
||||
assert_eq!(original, deserialized);
|
||||
}
|
||||
}
|
||||
85
common/src/test_utils.rs
Normal file
85
common/src/test_utils.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use nssa::AccountId;
|
||||
|
||||
use crate::{
|
||||
HashType,
|
||||
block::{Block, HashableBlockData},
|
||||
transaction::NSSATransaction,
|
||||
};
|
||||
|
||||
// Helpers
|
||||
|
||||
#[must_use]
|
||||
pub fn sequencer_sign_key_for_testing() -> nssa::PrivateKey {
|
||||
nssa::PrivateKey::try_new([37; 32]).unwrap()
|
||||
}
|
||||
|
||||
// Dummy producers
|
||||
|
||||
/// Produce dummy block with.
|
||||
///
|
||||
/// `id` - block id, provide zero for genesis.
|
||||
///
|
||||
/// `prev_hash` - hash of previous block, provide None for genesis.
|
||||
///
|
||||
/// `transactions` - vector of `EncodedTransaction` objects.
|
||||
#[must_use]
|
||||
pub fn produce_dummy_block(
|
||||
id: u64,
|
||||
prev_hash: Option<HashType>,
|
||||
transactions: Vec<NSSATransaction>,
|
||||
) -> Block {
|
||||
let block_data = HashableBlockData {
|
||||
block_id: id,
|
||||
prev_block_hash: prev_hash.unwrap_or_default(),
|
||||
timestamp: id.saturating_mul(100),
|
||||
transactions,
|
||||
};
|
||||
|
||||
block_data.into_pending_block(&sequencer_sign_key_for_testing(), [0; 32])
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn produce_dummy_empty_transaction() -> NSSATransaction {
|
||||
let program_id = nssa::program::Program::authenticated_transfer_program().id();
|
||||
let account_ids = vec![];
|
||||
let nonces = vec![];
|
||||
let instruction_data: u128 = 0;
|
||||
let message = nssa::public_transaction::Message::try_new(
|
||||
program_id,
|
||||
account_ids,
|
||||
nonces,
|
||||
instruction_data,
|
||||
)
|
||||
.unwrap();
|
||||
let private_key = nssa::PrivateKey::try_new([1; 32]).unwrap();
|
||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[&private_key]);
|
||||
|
||||
let nssa_tx = nssa::PublicTransaction::new(message, witness_set);
|
||||
|
||||
NSSATransaction::Public(nssa_tx)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn create_transaction_native_token_transfer(
|
||||
from: AccountId,
|
||||
nonce: u128,
|
||||
to: AccountId,
|
||||
balance_to_move: u128,
|
||||
signing_key: &nssa::PrivateKey,
|
||||
) -> NSSATransaction {
|
||||
let account_ids = vec![from, to];
|
||||
let nonces = vec![nonce.into()];
|
||||
let program_id = nssa::program::Program::authenticated_transfer_program().id();
|
||||
let message = nssa::public_transaction::Message::try_new(
|
||||
program_id,
|
||||
account_ids,
|
||||
nonces,
|
||||
balance_to_move,
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
|
||||
|
||||
let nssa_tx = nssa::PublicTransaction::new(message, witness_set);
|
||||
|
||||
NSSATransaction::Public(nssa_tx)
|
||||
}
|
||||
118
common/src/transaction.rs
Normal file
118
common/src/transaction.rs
Normal file
@ -0,0 +1,118 @@
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use log::warn;
|
||||
use nssa::{AccountId, V03State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::HashType;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
||||
pub enum NSSATransaction {
|
||||
Public(nssa::PublicTransaction),
|
||||
PrivacyPreserving(nssa::PrivacyPreservingTransaction),
|
||||
ProgramDeployment(nssa::ProgramDeploymentTransaction),
|
||||
}
|
||||
|
||||
impl Serialize for NSSATransaction {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
crate::borsh_base64::serialize(self, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for NSSATransaction {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
crate::borsh_base64::deserialize(deserializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl NSSATransaction {
|
||||
#[must_use]
|
||||
pub fn hash(&self) -> HashType {
|
||||
HashType(match self {
|
||||
Self::Public(tx) => tx.hash(),
|
||||
Self::PrivacyPreserving(tx) => tx.hash(),
|
||||
Self::ProgramDeployment(tx) => tx.hash(),
|
||||
})
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn affected_public_account_ids(&self) -> Vec<AccountId> {
|
||||
match self {
|
||||
Self::ProgramDeployment(tx) => tx.affected_public_account_ids(),
|
||||
Self::Public(tx) => tx.affected_public_account_ids(),
|
||||
Self::PrivacyPreserving(tx) => tx.affected_public_account_ids(),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Introduce type-safe wrapper around checked transaction, e.g. AuthenticatedTransaction
|
||||
pub fn transaction_stateless_check(self) -> Result<Self, TransactionMalformationError> {
|
||||
// Stateless checks here
|
||||
match self {
|
||||
Self::Public(tx) => {
|
||||
if tx.witness_set().is_valid_for(tx.message()) {
|
||||
Ok(Self::Public(tx))
|
||||
} else {
|
||||
Err(TransactionMalformationError::InvalidSignature)
|
||||
}
|
||||
}
|
||||
Self::PrivacyPreserving(tx) => {
|
||||
if tx.witness_set().signatures_are_valid_for(tx.message()) {
|
||||
Ok(Self::PrivacyPreserving(tx))
|
||||
} else {
|
||||
Err(TransactionMalformationError::InvalidSignature)
|
||||
}
|
||||
}
|
||||
Self::ProgramDeployment(tx) => Ok(Self::ProgramDeployment(tx)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute_check_on_state(
|
||||
self,
|
||||
state: &mut V03State,
|
||||
) -> Result<Self, nssa::error::NssaError> {
|
||||
match &self {
|
||||
Self::Public(tx) => state.transition_from_public_transaction(tx),
|
||||
Self::PrivacyPreserving(tx) => state.transition_from_privacy_preserving_transaction(tx),
|
||||
Self::ProgramDeployment(tx) => state.transition_from_program_deployment_transaction(tx),
|
||||
}
|
||||
.inspect_err(|err| warn!("Error at transition {err:#?}"))?;
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nssa::PublicTransaction> for NSSATransaction {
|
||||
fn from(value: nssa::PublicTransaction) -> Self {
|
||||
Self::Public(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nssa::PrivacyPreservingTransaction> for NSSATransaction {
|
||||
fn from(value: nssa::PrivacyPreservingTransaction) -> Self {
|
||||
Self::PrivacyPreserving(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nssa::ProgramDeploymentTransaction> for NSSATransaction {
|
||||
fn from(value: nssa::ProgramDeploymentTransaction) -> Self {
|
||||
Self::ProgramDeployment(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
|
||||
)]
|
||||
pub enum TxKind {
|
||||
Public,
|
||||
PrivacyPreserving,
|
||||
ProgramDeployment,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum TransactionMalformationError {
|
||||
#[error("Invalid signature(-s)")]
|
||||
InvalidSignature,
|
||||
#[error("Failed to decode transaction with hash: {tx:?}")]
|
||||
FailedToDecode { tx: HashType },
|
||||
#[error("Transaction size {size} exceeds maximum allowed size of {max} bytes")]
|
||||
TransactionTooLarge { size: usize, max: usize },
|
||||
}
|
||||
206
completions/README.md
Normal file
206
completions/README.md
Normal file
@ -0,0 +1,206 @@
|
||||
# Wallet CLI Completion
|
||||
|
||||
Completion scripts for the LSSA `wallet` command.
|
||||
|
||||
## ZSH
|
||||
|
||||
Works with both vanilla zsh and oh-my-zsh.
|
||||
|
||||
### Features
|
||||
|
||||
- Full completion for all wallet subcommands
|
||||
- Contextual option completion for each command
|
||||
- Dynamic account ID completion via `wallet account list`
|
||||
- Descriptions for all commands and options
|
||||
|
||||
Note that only accounts created by the user auto-complete.
|
||||
Preconfigured accounts and accounts only with `/` (no number) are not completed.
|
||||
|
||||
e.g.:
|
||||
|
||||
```
|
||||
▶ wallet account list
|
||||
Preconfigured Public/7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo,
|
||||
Preconfigured Public/6iArKUXxhUJqS7kCaPNhwMWt3ro71PDyBj7jwAyE2VQV,
|
||||
Preconfigured Private/3oCG8gqdKLMegw4rRfyaMQvuPHpcASt7xwttsmnZLSkw,
|
||||
Preconfigured Private/AKTcXgJ1xoynta1Ec7y6Jso1z1JQtHqd7aPQ1h9er6xX,
|
||||
/ Public/8DstRgMQrB2N9a7ymv98RDDbt8nctrP9ZzaNRSpKDZSu,
|
||||
/0 Public/2gJJjtG9UivBGEhA1Jz6waZQx1cwfYupC5yvKEweHaeH,
|
||||
/ Private/Bcv15B36bs1VqvQAdY6ZGFM1KioByNQQsB92KTNAx6u2
|
||||
```
|
||||
|
||||
Only `Public/2gJJjtG9UivBGEhA1Jz6waZQx1cwfYupC5yvKEweHaeH` is used for completion.
|
||||
|
||||
### Supported Commands
|
||||
|
||||
| Command | Description |
|
||||
|------------------------|-------------------------------------------------------------|
|
||||
| `wallet auth-transfer` | Authenticated transfer (init, send) |
|
||||
| `wallet chain-info` | Chain info queries (current-block-id, block, transaction) |
|
||||
| `wallet account` | Account management (get, list, new, sync-private) |
|
||||
| `wallet pinata` | Piñata faucet (claim) |
|
||||
| `wallet token` | Token operations (new, send) |
|
||||
| `wallet amm` | AMM operations (new, swap, add-liquidity, remove-liquidity) |
|
||||
| `wallet check-health` | Health check |
|
||||
|
||||
### Installation
|
||||
|
||||
#### Vanilla Zsh
|
||||
|
||||
1. Create a completions directory:
|
||||
|
||||
```sh
|
||||
mkdir -p ~/.zsh/completions
|
||||
```
|
||||
|
||||
2. Copy the completion file:
|
||||
|
||||
```sh
|
||||
cp ./zsh/_wallet ~/.zsh/completions/
|
||||
```
|
||||
|
||||
3. Add to your `~/.zshrc` (before any `compinit` call, or add these lines if you don't have one):
|
||||
|
||||
```sh
|
||||
fpath=(~/.zsh/completions $fpath)
|
||||
autoload -Uz compinit && compinit
|
||||
```
|
||||
|
||||
4. Reload your shell:
|
||||
|
||||
```sh
|
||||
exec zsh
|
||||
```
|
||||
|
||||
#### Oh-My-Zsh
|
||||
|
||||
1. Create the plugin directory and copy the file:
|
||||
|
||||
```sh
|
||||
mkdir -p ~/.oh-my-zsh/custom/plugins/wallet
|
||||
cp _wallet ~/.oh-my-zsh/custom/plugins/wallet/
|
||||
```
|
||||
|
||||
2. Add `wallet` to your plugins array in `~/.zshrc`:
|
||||
|
||||
```sh
|
||||
plugins=(... wallet)
|
||||
```
|
||||
|
||||
3. Reload your shell:
|
||||
|
||||
```sh
|
||||
exec zsh
|
||||
```
|
||||
|
||||
### Requirements
|
||||
|
||||
The completion script calls `wallet account list` to dynamically fetch account IDs. Ensure the `wallet` command is in your `$PATH`.
|
||||
|
||||
### Usage
|
||||
|
||||
```sh
|
||||
# Main commands
|
||||
wallet <TAB>
|
||||
|
||||
# Account subcommands
|
||||
wallet account <TAB>
|
||||
|
||||
# Options for auth-transfer send
|
||||
wallet auth-transfer send --<TAB>
|
||||
|
||||
# Account types when creating
|
||||
wallet account new <TAB>
|
||||
# Shows: public private
|
||||
|
||||
# Account IDs (fetched dynamically)
|
||||
wallet account get --account-id <TAB>
|
||||
# Shows: Public/... Private/...
|
||||
```
|
||||
|
||||
## Bash
|
||||
|
||||
Works with bash 4+. The `bash-completion` package is required for auto-sourcing from
|
||||
`/etc/bash_completion.d/`; without it, source the file directly from `~/.bashrc` instead.
|
||||
|
||||
### Features
|
||||
|
||||
- Full completion for all wallet subcommands
|
||||
- Contextual option completion for each command
|
||||
- Dynamic account ID completion via `wallet account list`
|
||||
- Falls back to `Public/` / `Private/` prefixes when no accounts are available
|
||||
|
||||
Note that only accounts created by the user auto-complete (same filtering as zsh — see above).
|
||||
|
||||
### Installation
|
||||
|
||||
#### Option A — source directly from `~/.bashrc` (works everywhere)
|
||||
|
||||
```sh
|
||||
echo "source $(pwd)/completions/bash/wallet" >> ~/.bashrc
|
||||
exec bash
|
||||
```
|
||||
|
||||
#### Option B — system-wide via `bash-completion`
|
||||
|
||||
1. Copy the file:
|
||||
|
||||
```sh
|
||||
cp ./bash/wallet /etc/bash_completion.d/wallet
|
||||
```
|
||||
|
||||
2. Ensure `bash-completion` is initialised in every interactive shell. On many Linux
|
||||
distributions (e.g. Fedora) it is only sourced for **login** shells via
|
||||
`/etc/profile.d/bash_completion.sh`. For non-login shells (e.g. a bash session started
|
||||
inside zsh), add this to `~/.bashrc`:
|
||||
|
||||
```sh
|
||||
[[ -f /usr/share/bash-completion/bash_completion ]] && source /usr/share/bash-completion/bash_completion
|
||||
```
|
||||
|
||||
3. Reload your shell:
|
||||
|
||||
```sh
|
||||
exec bash
|
||||
```
|
||||
|
||||
### Requirements
|
||||
|
||||
The completion script calls `wallet account list` to dynamically fetch account IDs. Ensure the `wallet` command is in your `$PATH`.
|
||||
|
||||
### Usage
|
||||
|
||||
```sh
|
||||
# Main commands
|
||||
wallet <TAB>
|
||||
|
||||
# Account subcommands
|
||||
wallet account <TAB>
|
||||
|
||||
# Options for auth-transfer send
|
||||
wallet auth-transfer send --<TAB>
|
||||
|
||||
# Account types when creating
|
||||
wallet account new <TAB>
|
||||
# Shows: public private
|
||||
|
||||
# Account IDs (fetched dynamically)
|
||||
wallet account get --account-id <TAB>
|
||||
# Shows: Public/... Private/...
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Zsh completions not appearing
|
||||
|
||||
1. Check that `compinit` is called in your `.zshrc`
|
||||
2. Rebuild the completion cache:
|
||||
|
||||
```sh
|
||||
rm -f ~/.zcompdump*
|
||||
exec zsh
|
||||
```
|
||||
|
||||
### Account IDs not completing
|
||||
|
||||
Ensure `wallet account list` works from your command line.
|
||||
382
completions/bash/wallet
Normal file
382
completions/bash/wallet
Normal file
@ -0,0 +1,382 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Bash completion script for the wallet CLI
|
||||
# See instructions in ../README.md
|
||||
|
||||
# Helper function to complete account IDs
|
||||
# Uses `wallet account list` to get available accounts
|
||||
# Only includes accounts with /N prefix (where N is a number)
|
||||
_wallet_complete_account_id() {
|
||||
local cur="$1"
|
||||
local accounts
|
||||
|
||||
if command -v wallet &>/dev/null; then
|
||||
accounts=$(wallet account list 2>/dev/null | grep '^/[0-9]' | awk '{print $2}' | tr -d ',')
|
||||
fi
|
||||
|
||||
if [[ -n "$accounts" ]]; then
|
||||
COMPREPLY=($(compgen -W "$accounts" -- "$cur"))
|
||||
else
|
||||
COMPREPLY=($(compgen -W "Public/ Private/" -- "$cur"))
|
||||
compopt -o nospace 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
_wallet() {
|
||||
local cur prev words cword
|
||||
_init_completion 2>/dev/null || {
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
words=("${COMP_WORDS[@]}")
|
||||
cword=$COMP_CWORD
|
||||
}
|
||||
|
||||
local commands="auth-transfer chain-info account pinata token amm check-health config restore-keys deploy-program help"
|
||||
|
||||
# Find the main command and subcommand by scanning words before the cursor.
|
||||
# Global options that take a value are skipped along with their argument.
|
||||
local cmd="" subcmd=""
|
||||
local cmd_idx=0 subcmd_idx=0
|
||||
local i
|
||||
for ((i = 1; i < cword; i++)); do
|
||||
local w="${words[$i]}"
|
||||
case "$w" in
|
||||
--auth)
|
||||
((i++)) # skip the auth value
|
||||
;;
|
||||
-c | --continuous-run)
|
||||
# boolean flag, no value
|
||||
;;
|
||||
-*)
|
||||
# unrecognised option, skip
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$cmd" ]]; then
|
||||
cmd="$w"
|
||||
cmd_idx=$i
|
||||
elif [[ -z "$subcmd" ]]; then
|
||||
subcmd="$w"
|
||||
subcmd_idx=$i
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
local config_keys="override_rust_log sequencer_addr seq_poll_timeout seq_tx_poll_max_blocks seq_poll_max_retries seq_block_poll_max_amount initial_accounts basic_auth"
|
||||
|
||||
case "$cmd" in
|
||||
"")
|
||||
# Completing the main command or a global option
|
||||
if [[ "$prev" == "--auth" ]]; then
|
||||
return # completing the --auth value; no suggestions
|
||||
fi
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=($(compgen -W "-c --continuous-run --auth" -- "$cur"))
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
auth-transfer)
|
||||
case "$subcmd" in
|
||||
"")
|
||||
COMPREPLY=($(compgen -W "init send help" -- "$cur"))
|
||||
;;
|
||||
init)
|
||||
case "$prev" in
|
||||
--account-id)
|
||||
_wallet_complete_account_id "$cur"
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--account-id" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
send)
|
||||
case "$prev" in
|
||||
--from | --to)
|
||||
_wallet_complete_account_id "$cur"
|
||||
;;
|
||||
--to-npk | --to-vpk | --amount)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--from --to --to-npk --to-vpk --amount" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
chain-info)
|
||||
case "$subcmd" in
|
||||
"")
|
||||
COMPREPLY=($(compgen -W "current-block-id block transaction help" -- "$cur"))
|
||||
;;
|
||||
block)
|
||||
case "$prev" in
|
||||
-i | --id)
|
||||
;; # no specific completion for block ID
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "-i --id" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
transaction)
|
||||
case "$prev" in
|
||||
-t | --hash)
|
||||
;; # no specific completion for tx hash
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "-t --hash" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
account)
|
||||
case "$subcmd" in
|
||||
"")
|
||||
COMPREPLY=($(compgen -W "get new sync-private list ls label help" -- "$cur"))
|
||||
;;
|
||||
get)
|
||||
case "$prev" in
|
||||
-a | --account-id)
|
||||
_wallet_complete_account_id "$cur"
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "-r --raw -k --keys -a --account-id" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
list | ls)
|
||||
COMPREPLY=($(compgen -W "-l --long" -- "$cur"))
|
||||
;;
|
||||
sync-private)
|
||||
;; # no options
|
||||
new)
|
||||
# `account new` is itself a subcommand: public | private
|
||||
local new_subcmd=""
|
||||
for ((i = subcmd_idx + 1; i < cword; i++)); do
|
||||
case "${words[$i]}" in
|
||||
public | private)
|
||||
new_subcmd="${words[$i]}"
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$new_subcmd" ]]; then
|
||||
COMPREPLY=($(compgen -W "public private" -- "$cur"))
|
||||
else
|
||||
case "$prev" in
|
||||
--cci | -l | --label)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--cci -l --label" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
;;
|
||||
label)
|
||||
case "$prev" in
|
||||
-a | --account-id)
|
||||
_wallet_complete_account_id "$cur"
|
||||
;;
|
||||
-l | --label)
|
||||
;; # no specific completion for label value
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "-a --account-id -l --label" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
pinata)
|
||||
case "$subcmd" in
|
||||
"")
|
||||
COMPREPLY=($(compgen -W "claim help" -- "$cur"))
|
||||
;;
|
||||
claim)
|
||||
case "$prev" in
|
||||
--to)
|
||||
_wallet_complete_account_id "$cur"
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--to" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
token)
|
||||
case "$subcmd" in
|
||||
"")
|
||||
COMPREPLY=($(compgen -W "new send burn mint help" -- "$cur"))
|
||||
;;
|
||||
new)
|
||||
case "$prev" in
|
||||
--definition-account-id | --supply-account-id)
|
||||
_wallet_complete_account_id "$cur"
|
||||
;;
|
||||
-n | --name | -t | --total-supply)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--definition-account-id --supply-account-id -n --name -t --total-supply" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
send)
|
||||
case "$prev" in
|
||||
--from | --to)
|
||||
_wallet_complete_account_id "$cur"
|
||||
;;
|
||||
--to-npk | --to-vpk | --amount)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--from --to --to-npk --to-vpk --amount" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
burn)
|
||||
case "$prev" in
|
||||
--definition | --holder)
|
||||
_wallet_complete_account_id "$cur"
|
||||
;;
|
||||
--amount)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--definition --holder --amount" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
mint)
|
||||
case "$prev" in
|
||||
--definition | --holder)
|
||||
_wallet_complete_account_id "$cur"
|
||||
;;
|
||||
--holder-npk | --holder-vpk | --amount)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--definition --holder --holder-npk --holder-vpk --amount" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
amm)
|
||||
case "$subcmd" in
|
||||
"")
|
||||
COMPREPLY=($(compgen -W "new swap add-liquidity remove-liquidity help" -- "$cur"))
|
||||
;;
|
||||
new)
|
||||
case "$prev" in
|
||||
--user-holding-a | --user-holding-b | --user-holding-lp)
|
||||
_wallet_complete_account_id "$cur"
|
||||
;;
|
||||
--balance-a | --balance-b)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--user-holding-a --user-holding-b --user-holding-lp --balance-a --balance-b" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
swap)
|
||||
case "$prev" in
|
||||
--user-holding-a | --user-holding-b)
|
||||
_wallet_complete_account_id "$cur"
|
||||
;;
|
||||
--amount-in | --min-amount-out | --token-definition)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--user-holding-a --user-holding-b --amount-in --min-amount-out --token-definition" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
add-liquidity)
|
||||
case "$prev" in
|
||||
--user-holding-a | --user-holding-b | --user-holding-lp)
|
||||
_wallet_complete_account_id "$cur"
|
||||
;;
|
||||
--max-amount-a | --max-amount-b | --min-amount-lp)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--user-holding-a --user-holding-b --user-holding-lp --max-amount-a --max-amount-b --min-amount-lp" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
remove-liquidity)
|
||||
case "$prev" in
|
||||
--user-holding-a | --user-holding-b | --user-holding-lp)
|
||||
_wallet_complete_account_id "$cur"
|
||||
;;
|
||||
--balance-lp | --min-amount-a | --min-amount-b)
|
||||
;; # no specific completion
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "--user-holding-a --user-holding-b --user-holding-lp --balance-lp --min-amount-a --min-amount-b" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
config)
|
||||
case "$subcmd" in
|
||||
"")
|
||||
COMPREPLY=($(compgen -W "get set description help" -- "$cur"))
|
||||
;;
|
||||
get)
|
||||
# Accepts optional -a/--all flag and an optional positional key
|
||||
COMPREPLY=($(compgen -W "--all -a $config_keys" -- "$cur"))
|
||||
;;
|
||||
set)
|
||||
# set <key> <value> — only complete the key; no completion for the value
|
||||
local set_args=0
|
||||
for ((i = subcmd_idx + 1; i < cword; i++)); do
|
||||
[[ "${words[$i]}" != -* ]] && ((set_args++))
|
||||
done
|
||||
if [[ $set_args -eq 0 ]]; then
|
||||
COMPREPLY=($(compgen -W "$config_keys" -- "$cur"))
|
||||
fi
|
||||
;;
|
||||
description)
|
||||
# description <key> — only complete if no key provided yet
|
||||
local has_key=false
|
||||
for ((i = subcmd_idx + 1; i < cword; i++)); do
|
||||
[[ "${words[$i]}" != -* ]] && has_key=true && break
|
||||
done
|
||||
if ! $has_key; then
|
||||
COMPREPLY=($(compgen -W "$config_keys" -- "$cur"))
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
restore-keys)
|
||||
case "$prev" in
|
||||
-d | --depth)
|
||||
;; # no specific completion for depth value
|
||||
*)
|
||||
COMPREPLY=($(compgen -W "-d --depth" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
deploy-program)
|
||||
COMPREPLY=($(compgen -f -- "$cur"))
|
||||
compopt -o filenames 2>/dev/null
|
||||
;;
|
||||
|
||||
help)
|
||||
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
complete -F _wallet wallet
|
||||
446
completions/zsh/_wallet
Normal file
446
completions/zsh/_wallet
Normal file
@ -0,0 +1,446 @@
|
||||
#compdef wallet
|
||||
|
||||
# Zsh completion script for the wallet CLI
|
||||
# See instructions in ../README.md
|
||||
|
||||
_wallet() {
|
||||
local -a commands
|
||||
local -a subcommands
|
||||
local curcontext="$curcontext" state line
|
||||
typeset -A opt_args
|
||||
|
||||
_arguments -C \
|
||||
'(-c --continuous-run)'{-c,--continuous-run}'[Continuous run flag]' \
|
||||
'--auth[Basic authentication in the format user or user\:password]:auth:' \
|
||||
'1: :->command' \
|
||||
'*:: :->args'
|
||||
|
||||
case $state in
|
||||
command)
|
||||
commands=(
|
||||
'auth-transfer:Authenticated transfer subcommand'
|
||||
'chain-info:Generic chain info subcommand'
|
||||
'account:Account view and sync subcommand'
|
||||
'pinata:Pinata program interaction subcommand'
|
||||
'token:Token program interaction subcommand'
|
||||
'amm:AMM program interaction subcommand'
|
||||
'check-health:Check the wallet can connect to the node and builtin local programs match the remote versions'
|
||||
'config:Command to setup config, get and set config fields'
|
||||
'restore-keys:Restoring keys from given password at given depth'
|
||||
'deploy-program:Deploy a program'
|
||||
'help:Print help message or the help of the given subcommand(s)'
|
||||
)
|
||||
_describe -t commands 'wallet commands' commands
|
||||
;;
|
||||
args)
|
||||
case $line[1] in
|
||||
auth-transfer)
|
||||
_wallet_auth_transfer
|
||||
;;
|
||||
chain-info)
|
||||
_wallet_chain_info
|
||||
;;
|
||||
account)
|
||||
_wallet_account
|
||||
;;
|
||||
pinata)
|
||||
_wallet_pinata
|
||||
;;
|
||||
token)
|
||||
_wallet_token
|
||||
;;
|
||||
amm)
|
||||
_wallet_amm
|
||||
;;
|
||||
config)
|
||||
_wallet_config
|
||||
;;
|
||||
restore-keys)
|
||||
_wallet_restore_keys
|
||||
;;
|
||||
deploy-program)
|
||||
_wallet_deploy_program
|
||||
;;
|
||||
help)
|
||||
_wallet_help
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# auth-transfer subcommand
|
||||
_wallet_auth_transfer() {
|
||||
local -a subcommands
|
||||
|
||||
_arguments -C \
|
||||
'1: :->subcommand' \
|
||||
'*:: :->args'
|
||||
|
||||
case $state in
|
||||
subcommand)
|
||||
subcommands=(
|
||||
'init:Initialize account under authenticated transfer program'
|
||||
'send:Send native tokens from one account to another with variable privacy'
|
||||
'help:Print this message or the help of the given subcommand(s)'
|
||||
)
|
||||
_describe -t subcommands 'auth-transfer subcommands' subcommands
|
||||
;;
|
||||
args)
|
||||
case $line[1] in
|
||||
init)
|
||||
_arguments \
|
||||
'--account-id[Account ID to initialize]:account_id:_wallet_account_ids'
|
||||
;;
|
||||
send)
|
||||
_arguments \
|
||||
'--from[Source account ID]:from_account:_wallet_account_ids' \
|
||||
'--to[Destination account ID (for owned accounts)]:to_account:_wallet_account_ids' \
|
||||
'--to-npk[Destination nullifier public key (for foreign private accounts)]:npk:' \
|
||||
'--to-vpk[Destination viewing public key (for foreign private accounts)]:vpk:' \
|
||||
'--amount[Amount of native tokens to send]:amount:'
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# chain-info subcommand
|
||||
_wallet_chain_info() {
|
||||
local -a subcommands
|
||||
|
||||
_arguments -C \
|
||||
'1: :->subcommand' \
|
||||
'*:: :->args'
|
||||
|
||||
case $state in
|
||||
subcommand)
|
||||
subcommands=(
|
||||
'current-block-id:Get current block id from sequencer'
|
||||
'block:Get block at id from sequencer'
|
||||
'transaction:Get transaction at hash from sequencer'
|
||||
'help:Print this message or the help of the given subcommand(s)'
|
||||
)
|
||||
_describe -t subcommands 'chain-info subcommands' subcommands
|
||||
;;
|
||||
args)
|
||||
case $line[1] in
|
||||
block)
|
||||
_arguments \
|
||||
'--id[Block ID to retrieve]:block_id:'
|
||||
;;
|
||||
transaction)
|
||||
_arguments \
|
||||
'--hash[Transaction hash to retrieve]:tx_hash:'
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# account subcommand
|
||||
_wallet_account() {
|
||||
local -a subcommands
|
||||
|
||||
_arguments -C \
|
||||
'1: :->subcommand' \
|
||||
'*:: :->args'
|
||||
|
||||
case $state in
|
||||
subcommand)
|
||||
subcommands=(
|
||||
'get:Get account data'
|
||||
'list:List all accounts owned by the wallet'
|
||||
'ls:List all accounts (alias for list)'
|
||||
'new:Produce new public or private account'
|
||||
'sync-private:Sync private accounts'
|
||||
'label:Set a label for an account'
|
||||
'help:Print this message or the help of the given subcommand(s)'
|
||||
)
|
||||
_describe -t subcommands 'account subcommands' subcommands
|
||||
;;
|
||||
args)
|
||||
case $line[1] in
|
||||
get)
|
||||
_arguments \
|
||||
'(-r --raw)'{-r,--raw}'[Get raw account data]' \
|
||||
'(-k --keys)'{-k,--keys}'[Display keys (pk for public accounts, npk/vpk for private accounts)]' \
|
||||
'(-a --account-id)'{-a,--account-id}'[Account ID to query]:account_id:_wallet_account_ids'
|
||||
;;
|
||||
list|ls)
|
||||
_arguments \
|
||||
'(-l --long)'{-l,--long}'[Display detailed account information]'
|
||||
;;
|
||||
new)
|
||||
_arguments -C \
|
||||
'1: :->account_type' \
|
||||
'*:: :->new_args'
|
||||
case $state in
|
||||
account_type)
|
||||
compadd public private
|
||||
;;
|
||||
new_args)
|
||||
_arguments \
|
||||
'--cci[Chain index of a parent node]:chain_index:' \
|
||||
'(-l --label)'{-l,--label}'[Label to assign to the new account]:label:'
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
label)
|
||||
_arguments \
|
||||
'(-a --account-id)'{-a,--account-id}'[Account ID to label]:account_id:_wallet_account_ids' \
|
||||
'(-l --label)'{-l,--label}'[The label to assign to the account]:label:'
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# pinata subcommand
|
||||
_wallet_pinata() {
|
||||
local -a subcommands
|
||||
|
||||
_arguments -C \
|
||||
'1: :->subcommand' \
|
||||
'*:: :->args'
|
||||
|
||||
case $state in
|
||||
subcommand)
|
||||
subcommands=(
|
||||
'claim:Claim tokens from the Piñata faucet'
|
||||
'help:Print this message or the help of the given subcommand(s)'
|
||||
)
|
||||
_describe -t subcommands 'pinata subcommands' subcommands
|
||||
;;
|
||||
args)
|
||||
case $line[1] in
|
||||
claim)
|
||||
_arguments \
|
||||
'--to[Destination account ID to receive claimed tokens]:to_account:_wallet_account_ids'
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# token subcommand
|
||||
_wallet_token() {
|
||||
local -a subcommands
|
||||
|
||||
_arguments -C \
|
||||
'1: :->subcommand' \
|
||||
'*:: :->args'
|
||||
|
||||
case $state in
|
||||
subcommand)
|
||||
subcommands=(
|
||||
'new:Produce a new token'
|
||||
'send:Send tokens from one account to another with variable privacy'
|
||||
'burn:Burn tokens on holder, modify definition'
|
||||
'mint:Mint tokens on holder, modify definition'
|
||||
'help:Print this message or the help of the given subcommand(s)'
|
||||
)
|
||||
_describe -t subcommands 'token subcommands' subcommands
|
||||
;;
|
||||
args)
|
||||
case $line[1] in
|
||||
new)
|
||||
_arguments \
|
||||
'--name[Token name]:name:' \
|
||||
'--total-supply[Total supply of tokens to mint]:total_supply:' \
|
||||
'--definition-account-id[Account ID for token definition]:definition_account:_wallet_account_ids' \
|
||||
'--supply-account-id[Account ID to receive initial supply]:supply_account:_wallet_account_ids'
|
||||
;;
|
||||
send)
|
||||
_arguments \
|
||||
'--from[Source holding account ID]:from_account:_wallet_account_ids' \
|
||||
'--to[Destination holding account ID (for owned accounts)]:to_account:_wallet_account_ids' \
|
||||
'--to-npk[Destination nullifier public key (for foreign private accounts)]:npk:' \
|
||||
'--to-vpk[Destination viewing public key (for foreign private accounts)]:vpk:' \
|
||||
'--amount[Amount of tokens to send]:amount:'
|
||||
;;
|
||||
burn)
|
||||
_arguments \
|
||||
'--definition[Definition account ID]:definition_account:_wallet_account_ids' \
|
||||
'--holder[Holder account ID]:holder_account:_wallet_account_ids' \
|
||||
'--amount[Amount of tokens to burn]:amount:'
|
||||
;;
|
||||
mint)
|
||||
_arguments \
|
||||
'--definition[Definition account ID]:definition_account:_wallet_account_ids' \
|
||||
'--holder[Holder account ID (for owned accounts)]:holder_account:_wallet_account_ids' \
|
||||
'--holder-npk[Holder nullifier public key (for foreign private accounts)]:npk:' \
|
||||
'--holder-vpk[Holder viewing public key (for foreign private accounts)]:vpk:' \
|
||||
'--amount[Amount of tokens to mint]:amount:'
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# amm subcommand
|
||||
_wallet_amm() {
|
||||
local -a subcommands
|
||||
|
||||
_arguments -C \
|
||||
'1: :->subcommand' \
|
||||
'*:: :->args'
|
||||
|
||||
case $state in
|
||||
subcommand)
|
||||
subcommands=(
|
||||
'new:Create a new liquidity pool'
|
||||
'swap:Swap tokens using the AMM'
|
||||
'add-liquidity:Add liquidity to an existing pool'
|
||||
'remove-liquidity:Remove liquidity from a pool'
|
||||
'help:Print this message or the help of the given subcommand(s)'
|
||||
)
|
||||
_describe -t subcommands 'amm subcommands' subcommands
|
||||
;;
|
||||
args)
|
||||
case $line[1] in
|
||||
new)
|
||||
_arguments \
|
||||
'--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \
|
||||
'--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \
|
||||
'--user-holding-lp[User LP token holding account ID]:holding_lp:_wallet_account_ids' \
|
||||
'--balance-a[Amount of token A to deposit]:balance_a:' \
|
||||
'--balance-b[Amount of token B to deposit]:balance_b:'
|
||||
;;
|
||||
swap)
|
||||
_arguments \
|
||||
'--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \
|
||||
'--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \
|
||||
'--amount-in[Amount of tokens to swap]:amount_in:' \
|
||||
'--min-amount-out[Minimum tokens expected in return]:min_amount_out:' \
|
||||
'--token-definition[Definition ID of the token being provided]:token_def:'
|
||||
;;
|
||||
add-liquidity)
|
||||
_arguments \
|
||||
'--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \
|
||||
'--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \
|
||||
'--user-holding-lp[User LP token holding account ID]:holding_lp:_wallet_account_ids' \
|
||||
'--max-amount-a[Maximum amount of token A to deposit]:max_amount_a:' \
|
||||
'--max-amount-b[Maximum amount of token B to deposit]:max_amount_b:' \
|
||||
'--min-amount-lp[Minimum LP tokens to receive]:min_amount_lp:'
|
||||
;;
|
||||
remove-liquidity)
|
||||
_arguments \
|
||||
'--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \
|
||||
'--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \
|
||||
'--user-holding-lp[User LP token holding account ID]:holding_lp:_wallet_account_ids' \
|
||||
'--balance-lp[Amount of LP tokens to burn]:balance_lp:' \
|
||||
'--min-amount-a[Minimum token A to receive]:min_amount_a:' \
|
||||
'--min-amount-b[Minimum token B to receive]:min_amount_b:'
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# config subcommand
|
||||
_wallet_config() {
|
||||
local -a subcommands
|
||||
local -a config_keys
|
||||
|
||||
config_keys=(
|
||||
'override_rust_log'
|
||||
'sequencer_addr'
|
||||
'seq_poll_timeout'
|
||||
'seq_tx_poll_max_blocks'
|
||||
'seq_poll_max_retries'
|
||||
'seq_block_poll_max_amount'
|
||||
'initial_accounts'
|
||||
'basic_auth'
|
||||
)
|
||||
|
||||
_arguments -C \
|
||||
'1: :->subcommand' \
|
||||
'*:: :->args'
|
||||
|
||||
case $state in
|
||||
subcommand)
|
||||
subcommands=(
|
||||
'get:Getter of config fields'
|
||||
'set:Setter of config fields'
|
||||
'description:Prints description of corresponding field'
|
||||
'help:Print this message or the help of the given subcommand(s)'
|
||||
)
|
||||
_describe -t subcommands 'config subcommands' subcommands
|
||||
;;
|
||||
args)
|
||||
case $line[1] in
|
||||
get)
|
||||
_arguments \
|
||||
'(-a --all)'{-a,--all}'[Print all config fields]' \
|
||||
'::key:compadd -a config_keys'
|
||||
;;
|
||||
description)
|
||||
compadd -a config_keys
|
||||
;;
|
||||
set)
|
||||
_arguments \
|
||||
'1:key:compadd -a config_keys' \
|
||||
'2:value:'
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# restore-keys subcommand
|
||||
_wallet_restore_keys() {
|
||||
_arguments \
|
||||
'(-d --depth)'{-d,--depth}'[How deep in tree accounts may be]:depth:'
|
||||
}
|
||||
|
||||
# deploy-program subcommand
|
||||
_wallet_deploy_program() {
|
||||
_arguments \
|
||||
'1:binary filepath:_files'
|
||||
}
|
||||
|
||||
# help subcommand
|
||||
_wallet_help() {
|
||||
local -a commands
|
||||
commands=(
|
||||
'auth-transfer:Authenticated transfer subcommand'
|
||||
'chain-info:Generic chain info subcommand'
|
||||
'account:Account view and sync subcommand'
|
||||
'pinata:Pinata program interaction subcommand'
|
||||
'token:Token program interaction subcommand'
|
||||
'amm:AMM program interaction subcommand'
|
||||
'check-health:Check the wallet can connect to the node'
|
||||
'config:Command to setup config, get and set config fields'
|
||||
'restore-keys:Restoring keys from given password at given depth'
|
||||
'deploy-program:Deploy a program'
|
||||
)
|
||||
_describe -t commands 'wallet commands' commands
|
||||
}
|
||||
|
||||
# Helper function to complete account IDs
|
||||
# Uses `wallet account list` to get available accounts
|
||||
# Only includes accounts with /N prefix (where N is a number)
|
||||
_wallet_account_ids() {
|
||||
local -a accounts
|
||||
local line
|
||||
|
||||
# Try to get accounts from wallet account list command
|
||||
# Filter to lines starting with /N (numbered accounts) and extract the account ID
|
||||
if command -v wallet &>/dev/null; then
|
||||
while IFS= read -r line; do
|
||||
# Remove trailing comma if present and add to array
|
||||
[[ -n "$line" ]] && accounts+=("${line%,}")
|
||||
done < <(wallet account list 2>/dev/null | grep '^/[0-9]' | awk '{print $2}')
|
||||
fi
|
||||
|
||||
# Provide type prefixes as fallback if command fails or returns nothing
|
||||
if (( ${#accounts} == 0 )); then
|
||||
compadd -S '' -- 'Public/' 'Private/'
|
||||
return
|
||||
fi
|
||||
|
||||
_multi_parts / accounts
|
||||
}
|
||||
|
||||
_wallet "$@"
|
||||
160
configs/docker-all-in-one/indexer_config.json
Normal file
160
configs/docker-all-in-one/indexer_config.json
Normal file
@ -0,0 +1,160 @@
|
||||
{
|
||||
"home": "./indexer/service",
|
||||
"consensus_info_polling_interval": "1s",
|
||||
"bedrock_client_config": {
|
||||
"addr": "http://logos-blockchain-node-0:18080",
|
||||
"backoff": {
|
||||
"start_delay": "100ms",
|
||||
"max_retries": 5
|
||||
}
|
||||
},
|
||||
"channel_id": "0101010101010101010101010101010101010101010101010101010101010101",
|
||||
"initial_accounts": [
|
||||
{
|
||||
"account_id": "6iArKUXxhUJqS7kCaPNhwMWt3ro71PDyBj7jwAyE2VQV",
|
||||
"balance": 10000
|
||||
},
|
||||
{
|
||||
"account_id": "7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo",
|
||||
"balance": 20000
|
||||
}
|
||||
],
|
||||
"initial_commitments": [
|
||||
{
|
||||
"npk":[
|
||||
177,
|
||||
64,
|
||||
1,
|
||||
11,
|
||||
87,
|
||||
38,
|
||||
254,
|
||||
159,
|
||||
231,
|
||||
165,
|
||||
1,
|
||||
94,
|
||||
64,
|
||||
137,
|
||||
243,
|
||||
76,
|
||||
249,
|
||||
101,
|
||||
251,
|
||||
129,
|
||||
33,
|
||||
101,
|
||||
189,
|
||||
30,
|
||||
42,
|
||||
11,
|
||||
191,
|
||||
34,
|
||||
103,
|
||||
186,
|
||||
227,
|
||||
230
|
||||
] ,
|
||||
"account": {
|
||||
"program_owner": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"balance": 10000,
|
||||
"data": [],
|
||||
"nonce": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"npk": [
|
||||
32,
|
||||
67,
|
||||
72,
|
||||
164,
|
||||
106,
|
||||
53,
|
||||
66,
|
||||
239,
|
||||
141,
|
||||
15,
|
||||
52,
|
||||
230,
|
||||
136,
|
||||
177,
|
||||
2,
|
||||
236,
|
||||
207,
|
||||
243,
|
||||
134,
|
||||
135,
|
||||
210,
|
||||
143,
|
||||
87,
|
||||
232,
|
||||
215,
|
||||
128,
|
||||
194,
|
||||
120,
|
||||
113,
|
||||
224,
|
||||
4,
|
||||
165
|
||||
],
|
||||
"account": {
|
||||
"program_owner": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"balance": 20000,
|
||||
"data": [],
|
||||
"nonce": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"signing_key": [
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37
|
||||
]
|
||||
}
|
||||
167
configs/docker-all-in-one/sequencer_config.json
Normal file
167
configs/docker-all-in-one/sequencer_config.json
Normal file
@ -0,0 +1,167 @@
|
||||
{
|
||||
"home": "/var/lib/sequencer_service",
|
||||
"genesis_id": 1,
|
||||
"is_genesis_random": true,
|
||||
"max_num_tx_in_block": 20,
|
||||
"max_block_size": "1 MiB",
|
||||
"mempool_max_size": 10000,
|
||||
"block_create_timeout": "10s",
|
||||
"retry_pending_blocks_timeout": "7s",
|
||||
"bedrock_config": {
|
||||
"backoff": {
|
||||
"start_delay": "100ms",
|
||||
"max_retries": 5
|
||||
},
|
||||
"channel_id": "0101010101010101010101010101010101010101010101010101010101010101",
|
||||
"node_url": "http://logos-blockchain-node-0:18080"
|
||||
},
|
||||
"indexer_rpc_url": "ws://indexer_service:8779",
|
||||
"initial_accounts": [
|
||||
{
|
||||
"account_id": "6iArKUXxhUJqS7kCaPNhwMWt3ro71PDyBj7jwAyE2VQV",
|
||||
"balance": 10000
|
||||
},
|
||||
{
|
||||
"account_id": "7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo",
|
||||
"balance": 20000
|
||||
}
|
||||
],
|
||||
"initial_commitments": [
|
||||
{
|
||||
"npk":[
|
||||
177,
|
||||
64,
|
||||
1,
|
||||
11,
|
||||
87,
|
||||
38,
|
||||
254,
|
||||
159,
|
||||
231,
|
||||
165,
|
||||
1,
|
||||
94,
|
||||
64,
|
||||
137,
|
||||
243,
|
||||
76,
|
||||
249,
|
||||
101,
|
||||
251,
|
||||
129,
|
||||
33,
|
||||
101,
|
||||
189,
|
||||
30,
|
||||
42,
|
||||
11,
|
||||
191,
|
||||
34,
|
||||
103,
|
||||
186,
|
||||
227,
|
||||
230
|
||||
] ,
|
||||
"account": {
|
||||
"program_owner": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"balance": 10000,
|
||||
"data": [],
|
||||
"nonce": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"npk": [
|
||||
32,
|
||||
67,
|
||||
72,
|
||||
164,
|
||||
106,
|
||||
53,
|
||||
66,
|
||||
239,
|
||||
141,
|
||||
15,
|
||||
52,
|
||||
230,
|
||||
136,
|
||||
177,
|
||||
2,
|
||||
236,
|
||||
207,
|
||||
243,
|
||||
134,
|
||||
135,
|
||||
210,
|
||||
143,
|
||||
87,
|
||||
232,
|
||||
215,
|
||||
128,
|
||||
194,
|
||||
120,
|
||||
113,
|
||||
224,
|
||||
4,
|
||||
165
|
||||
],
|
||||
"account": {
|
||||
"program_owner": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"balance": 20000,
|
||||
"data": [],
|
||||
"nonce": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"signing_key": [
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37,
|
||||
37
|
||||
]
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
[package]
|
||||
name = "consensus"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
serde_json.workspace = true
|
||||
env_logger.workspace = true
|
||||
log.workspace = true
|
||||
serde.workspace = true
|
||||
tokio.workspace = true
|
||||
|
||||
[dependencies.networking]
|
||||
path = "../networking"
|
||||
@ -1,22 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use networking::peer_manager::PeerManager;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[derive(Debug)]
|
||||
///Entrypoint to consensus.
|
||||
/// Manages consensus protocol.
|
||||
pub struct ConsensusManager {
|
||||
pub peer_manager: Arc<Mutex<PeerManager>>,
|
||||
}
|
||||
|
||||
impl ConsensusManager {
|
||||
pub fn new(peer_manager: Arc<Mutex<PeerManager>>) -> Self {
|
||||
Self { peer_manager }
|
||||
}
|
||||
|
||||
//ToDo: change block from generic value into struct, when data block will be defined
|
||||
pub fn vote(&self, _block: serde_json::Value) -> bool {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
27
docker-compose.override.yml
Normal file
27
docker-compose.override.yml
Normal file
@ -0,0 +1,27 @@
|
||||
# This file is automatically applied on top of docker-compose.yml when running `docker compose` commands.
|
||||
|
||||
services:
|
||||
logos-blockchain-node-0:
|
||||
ports: !override
|
||||
- "18080:18080/tcp"
|
||||
environment:
|
||||
- RUST_LOG=error
|
||||
|
||||
sequencer_service:
|
||||
depends_on:
|
||||
- logos-blockchain-node-0
|
||||
- indexer_service
|
||||
volumes:
|
||||
- ./configs/docker-all-in-one/sequencer_config.json:/etc/sequencer_service/sequencer_config.json
|
||||
|
||||
indexer_service:
|
||||
depends_on:
|
||||
- logos-blockchain-node-0
|
||||
volumes:
|
||||
- ./configs/docker-all-in-one/indexer_config.json:/etc/indexer_service/indexer_config.json
|
||||
|
||||
explorer_service:
|
||||
depends_on:
|
||||
- indexer_service
|
||||
environment:
|
||||
- INDEXER_RPC_URL=http://indexer_service:8779
|
||||
13
docker-compose.yml
Normal file
13
docker-compose.yml
Normal file
@ -0,0 +1,13 @@
|
||||
# All-in-one docker compose configuration.
|
||||
# It runs all services from this repo and the bedrock nodes in a single docker network.
|
||||
# This is useful for development and testing purposes.
|
||||
|
||||
include:
|
||||
- path:
|
||||
bedrock/docker-compose.yml
|
||||
- path:
|
||||
sequencer/service/docker-compose.yml
|
||||
- path:
|
||||
indexer/service/docker-compose.yml
|
||||
- path:
|
||||
explorer_service/docker-compose.yml
|
||||
109
docs/LEZ testnet v0.1 tutorials/amm.md
Normal file
109
docs/LEZ testnet v0.1 tutorials/amm.md
Normal file
@ -0,0 +1,109 @@
|
||||
# Automated Market Maker (AMM)
|
||||
|
||||
This tutorial covers the AMM program in LEZ. The AMM manages liquidity pools and enables swaps between custom tokens. By the end, you will have practiced:
|
||||
1. Creating a liquidity pool for a token pair.
|
||||
2. Swapping tokens.
|
||||
3. Withdrawing liquidity from the pool.
|
||||
4. Adding liquidity to the pool.
|
||||
|
||||
## 1. Creating a liquidity pool for a token pair
|
||||
|
||||
We start by creating a pool for the tokens created earlier. In return for providing liquidity, you receive liquidity provider (LP) tokens. LP tokens represent your share of the pool and are required to withdraw liquidity later.
|
||||
|
||||
> [!NOTE]
|
||||
> The AMM does not currently charge swap fees or distribute rewards to liquidity providers. LP tokens therefore represent only a proportional share of the pool reserves. Fee support will be added in future versions.
|
||||
|
||||
### a. Create an LP holding account
|
||||
|
||||
```bash
|
||||
wallet account new public
|
||||
|
||||
# Output:
|
||||
Generated new account with account_id Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf
|
||||
```
|
||||
|
||||
### b. Initialize the pool
|
||||
|
||||
Deposit tokens A and B and specify the account that will receive LP tokens:
|
||||
|
||||
```bash
|
||||
wallet amm new \
|
||||
--user-holding-a Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw \
|
||||
--user-holding-b Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \
|
||||
--user-holding-lp Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf \
|
||||
--balance-a 100 \
|
||||
--balance-b 200
|
||||
```
|
||||
|
||||
> [!Important]
|
||||
> The LP holding account is owned by the token program, so LP tokens are managed using the same token infrastructure as regular tokens.
|
||||
|
||||
```bash
|
||||
wallet account get --account-id Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf
|
||||
|
||||
# Output:
|
||||
Holding account owned by token program
|
||||
{"account_type":"Token holding","definition_id":"7BeDS3e28MA5Err7gBswmR1fUKdHXqmUpTefNPu3pJ9i","balance":100}
|
||||
```
|
||||
|
||||
> [!Tip]
|
||||
> If you inspect the `user-holding-a` and `user-holding-b` accounts, you will see that 100 and 200 tokens were deducted. Those tokens now reside in the pool and are available for swaps by any user.
|
||||
|
||||
## 2. Swapping
|
||||
|
||||
Use `wallet amm swap` to perform a token swap:
|
||||
|
||||
```bash
|
||||
wallet amm swap \
|
||||
--user-holding-a Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw \
|
||||
--user-holding-b Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \
|
||||
# The amount of tokens to swap
|
||||
--amount-in 5 \
|
||||
# The minimum number of tokens expected in return
|
||||
--min-amount-out 8 \
|
||||
# The definition ID of the token being provided to the swap
|
||||
# In this case, we are swapping from TOKENA to TOKENB, and so this is the definition ID of TOKENA
|
||||
--token-definition 4X9kAcnCZ1Ukkbm3nywW9xfCNPK8XaMWCk3zfs1sP4J7
|
||||
```
|
||||
|
||||
Once executed, 5 tokens are deducted from the Token A holding account and the corresponding amount (computed by the pool’s pricing function) is credited to the Token B holding account.
|
||||
|
||||
## 3. Withdrawing liquidity from the pool
|
||||
|
||||
Liquidity providers can withdraw assets by redeeming (burning) LP tokens. The amount received is proportional to the share of LP tokens redeemed relative to the total LP supply.
|
||||
|
||||
Use `wallet amm remove-liquidity`:
|
||||
|
||||
```bash
|
||||
wallet amm remove-liquidity \
|
||||
--user-holding-a Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw \
|
||||
--user-holding-b Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \
|
||||
--user-holding-lp Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf \
|
||||
--balance-lp 20 \
|
||||
--min-amount-a 1 \
|
||||
--min-amount-b 1
|
||||
```
|
||||
|
||||
> [!Important]
|
||||
> This burns `balance-lp` LP tokens from the user’s LP holding account. In return, the AMM transfers tokens A and B from the pool vaults to the user’s holding accounts, based on current reserves.
|
||||
> The `min-amount-a` and `min-amount-b` parameters set the minimum acceptable outputs. If the computed amounts fall below either threshold, the instruction fails to protect against unfavorable pool changes.
|
||||
|
||||
## 4. Adding liquidity to the pool
|
||||
|
||||
To add liquidity, deposit tokens A and B in the ratio implied by current pool reserves. In return, the AMM mints new LP tokens that represent your proportional share.
|
||||
|
||||
Use `wallet amm add-liquidity`:
|
||||
|
||||
```bash
|
||||
wallet amm add-liquidity \
|
||||
--user-holding-a Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw \
|
||||
--user-holding-b Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \
|
||||
--user-holding-lp Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf \
|
||||
--min-amount-lp 1 \
|
||||
--max-amount-a 10 \
|
||||
--max-amount-b 10
|
||||
```
|
||||
|
||||
> [!Important]
|
||||
> `max-amount-a` and `max-amount-b` cap how many tokens A and B can be taken from the user’s accounts. The AMM computes the required amounts based on the pool’s reserve ratio.
|
||||
> `min-amount-lp` sets the minimum LP tokens to mint. If the computed LP amount falls below this threshold, the instruction fails.
|
||||
159
docs/LEZ testnet v0.1 tutorials/custom-tokens.md
Normal file
159
docs/LEZ testnet v0.1 tutorials/custom-tokens.md
Normal file
@ -0,0 +1,159 @@
|
||||
This tutorial focuses on custom tokens using the Token program. As of now, you have used the authenticated-transfers program for native tokens. The Token program is for creating and managing custom tokens. By the end, you will have practiced:
|
||||
1. Creating new tokens.
|
||||
2. Transferring custom tokens.
|
||||
|
||||
> [!Important]
|
||||
> The Token program is a single program that creates and manages all tokens, so you do not deploy a new program for each token.
|
||||
> Token program accounts fall into two types:
|
||||
> - Token definition accounts: store token metadata such as name and total supply. This account is the token’s identifier.
|
||||
> - Token holding accounts: store balances and the definition ID they belong to.
|
||||
|
||||
The CLI provides commands to execute the Token program. Run `wallet token` to see the options:
|
||||
|
||||
```bash
|
||||
Commands:
|
||||
new Produce a new token
|
||||
send Send tokens from one account to another with variable privacy
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
```
|
||||
|
||||
## 1. Creating new tokens
|
||||
|
||||
Use `wallet token new` to execute the `New` function of the Token program. The command expects:
|
||||
- A token name.
|
||||
- A total supply.
|
||||
- Two uninitialized accounts:
|
||||
- One for the token definition account.
|
||||
- One for the token holding account that receives the initial supply.
|
||||
|
||||
### a. Public definition account and public supply account
|
||||
|
||||
1. Create two new public accounts:
|
||||
|
||||
```bash
|
||||
wallet account new public
|
||||
|
||||
# Output:
|
||||
Generated new account with account_id Public/4X9kAcnCZ1Ukkbm3nywW9xfCNPK8XaMWCk3zfs1sP4J7
|
||||
```
|
||||
|
||||
```bash
|
||||
wallet account new public
|
||||
|
||||
# Output:
|
||||
Generated new account with account_id Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw
|
||||
```
|
||||
|
||||
2. Create the token (Token A):
|
||||
|
||||
```bash
|
||||
wallet token new \
|
||||
--name TOKENA \
|
||||
--total-supply 1337 \
|
||||
--definition-account-id Public/4X9kAcnCZ1Ukkbm3nywW9xfCNPK8XaMWCk3zfs1sP4J7 \
|
||||
--supply-account-id Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw
|
||||
```
|
||||
|
||||
3. Inspect the initialized accounts:
|
||||
|
||||
```bash
|
||||
wallet account get --account-id Public/4X9kAcnCZ1Ukkbm3nywW9xfCNPK8XaMWCk3zfs1sP4J7
|
||||
|
||||
# Output:
|
||||
Definition account owned by token program
|
||||
{"account_type":"Token definition","name":"TOKENA","total_supply":1337}
|
||||
```
|
||||
|
||||
```bash
|
||||
wallet account get --account-id Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw
|
||||
|
||||
# Output:
|
||||
Holding account owned by token program
|
||||
{"account_type":"Token holding","definition_id":"4X9kAcnCZ1Ukkbm3nywW9xfCNPK8XaMWCk3zfs1sP4J7","balance":1337}
|
||||
```
|
||||
|
||||
### b. Public definition account and private supply account
|
||||
|
||||
1. Create fresh accounts for this example:
|
||||
|
||||
> [!Important]
|
||||
> You cannot reuse the accounts from the previous example. Create new ones here.
|
||||
|
||||
```bash
|
||||
wallet account new public
|
||||
|
||||
# Output:
|
||||
Generated new account with account_id Public/GQ3C8rbprTtQUCvkuVBRu3v9wvUvjafCMFqoSPvTEVii
|
||||
```
|
||||
|
||||
```bash
|
||||
wallet account new private
|
||||
|
||||
# Output:
|
||||
Generated new account with account_id Private/HMRHZdPw4pbyPVZHNGrV6K5AA95wACFsHTRST84fr3CF
|
||||
With npk 6a2dfe433cf28e525aa0196d719be3c16146f7ee358ca39595323f94fde38f93
|
||||
With vpk 03d59abf4bee974cc12ddb44641c19f0b5441fef39191f047c988c29a77252a577
|
||||
```
|
||||
|
||||
2. Create the token (Token B):
|
||||
|
||||
```bash
|
||||
wallet token new \
|
||||
--name TOKENB \
|
||||
--total-supply 7331 \
|
||||
--definition-account-id Public/GQ3C8rbprTtQUCvkuVBRu3v9wvUvjafCMFqoSPvTEVii \
|
||||
--supply-account-id Private/HMRHZdPw4pbyPVZHNGrV6K5AA95wACFsHTRST84fr3CF
|
||||
```
|
||||
|
||||
3. Inspect the accounts:
|
||||
|
||||
```bash
|
||||
wallet account get --account-id Public/GQ3C8rbprTtQUCvkuVBRu3v9wvUvjafCMFqoSPvTEVii
|
||||
|
||||
# Output:
|
||||
Definition account owned by token program
|
||||
{"account_type":"Token definition","name":"TOKENB","total_supply":7331}
|
||||
```
|
||||
|
||||
```bash
|
||||
wallet account get --account-id Private/HMRHZdPw4pbyPVZHNGrV6K5AA95wACFsHTRST84fr3CF
|
||||
|
||||
# Output:
|
||||
Holding account owned by token program
|
||||
{"account_type":"Token holding","definition_id":"GQ3C8rbprTtQUCvkuVBRu3v9wvUvjafCMFqoSPvTEVii","balance":7331}
|
||||
```
|
||||
|
||||
> [!Important]
|
||||
> As a private account, the supply account is visible only in your local wallet storage.
|
||||
|
||||
## 2. Custom token transfers
|
||||
|
||||
The Token program can move balances between token holding accounts. If the recipient account is uninitialized, the token program will automatically claim it. Use `wallet token send` to execute a transfer.
|
||||
|
||||
### a. Create a recipient account
|
||||
|
||||
```bash
|
||||
wallet account new public
|
||||
|
||||
# Output:
|
||||
Generated new account with account_id Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6
|
||||
```
|
||||
|
||||
### b. Send 1000 TOKENB to the recipient
|
||||
|
||||
```bash
|
||||
wallet token send \
|
||||
--from Private/HMRHZdPw4pbyPVZHNGrV6K5AA95wACFsHTRST84fr3CF \
|
||||
--to Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \
|
||||
--amount 1000
|
||||
```
|
||||
|
||||
### c. Inspect the recipient account
|
||||
|
||||
```bash
|
||||
wallet account get --account-id Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6
|
||||
|
||||
# Output:
|
||||
Holding account owned by token program
|
||||
{"account_type":"Token holding","definition_id":"GQ3C8rbprTtQUCvkuVBRu3v9wvUvjafCMFqoSPvTEVii","balance":1000}
|
||||
```
|
||||
250
docs/LEZ testnet v0.1 tutorials/token-transfer.md
Normal file
250
docs/LEZ testnet v0.1 tutorials/token-transfer.md
Normal file
@ -0,0 +1,250 @@
|
||||
This tutorial walks through native token transfers between public and private accounts using the Authenticated-Transfers program. You will create and initialize accounts, fund them with the Pinata program, and run transfers across different privacy combinations. By the end, you will have practiced:
|
||||
1. Public account creation and initialization.
|
||||
2. Account funding through the Pinata program.
|
||||
3. Native token transfers between public accounts.
|
||||
4. Private account creation.
|
||||
5. Native token transfer from a public account to a private account.
|
||||
6. Native token transfer from a public account to a private account owned by someone else.
|
||||
|
||||
---
|
||||
|
||||
The CLI provides commands to manage accounts. Run `wallet account` to see the options available:
|
||||
```bash
|
||||
Commands:
|
||||
get Get account data
|
||||
new Produce new public or private account
|
||||
sync-private Sync private accounts
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
```
|
||||
|
||||
## 1. Public account creation and initialization
|
||||
> [!Important]
|
||||
> Public accounts live on-chain and are identified by a 32-byte Account ID. Running `wallet account new public` generates a fresh keypair for the signature scheme used in LEZ.
|
||||
> The account ID is derived from the public key, and the private key signs transactions and authorizes program executions.
|
||||
> The CLI can create both public and private accounts.
|
||||
|
||||
### a. New public account creation
|
||||
```bash
|
||||
wallet account new public
|
||||
|
||||
# Output:
|
||||
Generated new account with account_id Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ
|
||||
```
|
||||
> [!Tip]
|
||||
> Save this account ID. You will use it in later commands.
|
||||
|
||||
### b. Account initialization
|
||||
|
||||
To query the account’s current status, run:
|
||||
|
||||
```bash
|
||||
# Replace the id with yours
|
||||
wallet account get --account-id Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ
|
||||
|
||||
# Output:
|
||||
Account is Uninitialized
|
||||
```
|
||||
|
||||
In this example, we initialize the account for the authenticated-transfer program, which manages native token transfers and enforces authenticated debits.
|
||||
|
||||
1. Initialize the account:
|
||||
```bash
|
||||
# This command submits a public transaction executing the `init` function of the
|
||||
# authenticated-transfer program. The wallet polls the sequencer until the
|
||||
# transaction is included in a block, which may take several seconds.
|
||||
wallet auth-transfer init --account-id Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ
|
||||
```
|
||||
|
||||
2. Check the updated account status:
|
||||
```bash
|
||||
wallet account get --account-id Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ
|
||||
|
||||
# Output:
|
||||
Account owned by authenticated-transfer program
|
||||
{"balance":0}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> New accounts start uninitialized, meaning no program owns them yet. Any program may claim an uninitialized account; once claimed, that program owns it.
|
||||
> Owned accounts can only be modified through executions of the owning program. The only exception is native-token credits: any program may credit native tokens to any account.
|
||||
> Debiting native tokens must always be performed by the owning program.
|
||||
|
||||
## 2. Account funding through the Piñata program
|
||||
Now that the account is initialized under the authenticated-tansfer program, fund it using the testnet Piñata program.
|
||||
|
||||
```bash
|
||||
# Replace with your id
|
||||
wallet pinata claim --to Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ
|
||||
```
|
||||
|
||||
After the claim succeeds, the account is funded:
|
||||
|
||||
```bash
|
||||
wallet account get --account-id Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ
|
||||
|
||||
# Output:
|
||||
Account owned by authenticated-transfer program
|
||||
{"balance":150}
|
||||
```
|
||||
|
||||
## 3. Native token transfers between public accounts
|
||||
LEZ includes a program for managing native tokens. Run `wallet auth-transfer` to see the available commands:
|
||||
```bash
|
||||
Commands:
|
||||
init Initialize account under the authenticated-transfer program
|
||||
send Send native tokens from one account to another with variable privacy
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
```
|
||||
|
||||
We already used `init`. Now use `send` to execute a transfer.
|
||||
|
||||
### a. Create a recipient account
|
||||
```bash
|
||||
wallet account new public
|
||||
|
||||
# Output:
|
||||
Generated new account with account_id Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> The new account is uninitialized. The authenticated-transfer program will claim any uninitialized account used in a transfer, so manual initialization isn’t required.
|
||||
|
||||
### b. Send 37 tokens to the new account
|
||||
```bash
|
||||
wallet auth-transfer send \
|
||||
--from Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ \
|
||||
--to Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS \
|
||||
--amount 37
|
||||
```
|
||||
|
||||
### c. Check both accounts
|
||||
```bash
|
||||
# Sender account (use your sender ID)
|
||||
wallet account get --account-id Public/HrA8TVjBS8UVf9akV7LRhyh6k4c7F6PS7PvqgtPmKAT8
|
||||
|
||||
# Output:
|
||||
Account owned by authenticated-transfer program
|
||||
{"balance":113}
|
||||
```
|
||||
|
||||
```bash
|
||||
# Recipient account
|
||||
wallet account get --account-id Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS
|
||||
|
||||
# Output:
|
||||
Account owned by authenticated-transfer program
|
||||
{"balance":37}
|
||||
```
|
||||
|
||||
## 4. Private account creation
|
||||
|
||||
> [!Important]
|
||||
> Private accounts are structurally identical to public accounts, but their values are stored off-chain. On-chain, only a 32-byte commitment is recorded.
|
||||
> Transactions include encrypted private values so the owner can recover them, and the decryption keys are never shared.
|
||||
> Private accounts use two keypairs: nullifier keys for privacy-preserving executions and viewing keys for encrypting and decrypting values.
|
||||
> The private account ID is derived from the nullifier public key.
|
||||
> Private accounts can be initialized by anyone, but once initialized they can only be modified by the owner’s keys.
|
||||
> Updates include a new commitment and a nullifier for the old state, which prevents linkage between versions.
|
||||
|
||||
### a. Create a private account
|
||||
|
||||
```bash
|
||||
wallet account new private
|
||||
|
||||
# Output:
|
||||
Generated new account with account_id Private/HacPU3hakLYzWtSqUPw6TUr8fqoMieVWovsUR6sJf7cL
|
||||
With npk e6366f79d026c8bd64ae6b3d601f0506832ec682ab54897f205fffe64ec0d951
|
||||
With vpk 02ddc96d0eb56e00ce14994cfdaec5ae1f76244180a919545983156e3519940a17
|
||||
```
|
||||
|
||||
> [!Tip]
|
||||
> Focus on the account ID for now. The `npk` and `vpk` values are stored locally and used to build privacy-preserving transactions. The private account ID is derived from `npk`.
|
||||
|
||||
Just like public accounts, new private accounts start out uninitialized:
|
||||
|
||||
```bash
|
||||
wallet account get --account-id Private/HacPU3hakLYzWtSqUPw6TUr8fqoMieVWovsUR6sJf7cL
|
||||
|
||||
# Output:
|
||||
Account is Uninitialized
|
||||
```
|
||||
|
||||
> [!Important]
|
||||
> Private accounts are never visible to the network. They exist only in your local wallet storage.
|
||||
|
||||
## 5. Native token transfer from a public account to a private account
|
||||
|
||||
> [!Important]
|
||||
> Sending tokens to an uninitialized private account causes the authenticated-transfer program to claim it, just like with public accounts. Program logic is the same regardless of account type.
|
||||
|
||||
### a. Send 17 tokens to the private account
|
||||
|
||||
> [!Note]
|
||||
> The syntax matches public-to-public transfers, but the recipient is a private ID. This runs locally, generates a proof, and submits it to the sequencer. It may take 30 seconds to 4 minutes.
|
||||
|
||||
```bash
|
||||
wallet auth-transfer send \
|
||||
--from Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS \
|
||||
--to Private/HacPU3hakLYzWtSqUPw6TUr8fqoMieVWovsUR6sJf7cL \
|
||||
--amount 17
|
||||
```
|
||||
|
||||
### b. Check both accounts
|
||||
|
||||
```bash
|
||||
# Public sender account
|
||||
wallet account get --account-id Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS
|
||||
|
||||
# Output:
|
||||
Account owned by authenticated-transfer program
|
||||
{"balance":20}
|
||||
```
|
||||
|
||||
```bash
|
||||
# Private recipient account
|
||||
wallet account get --account-id Private/HacPU3hakLYzWtSqUPw6TUr8fqoMieVWovsUR6sJf7cL
|
||||
|
||||
# Output:
|
||||
Account owned by authenticated-transfer program
|
||||
{"balance":17}
|
||||
```
|
||||
|
||||
> [!Note]
|
||||
> The last command does not query the network. It works offline because private account data is stored locally. Other users cannot read your private balances.
|
||||
|
||||
> [!Caution]
|
||||
> Private accounts can only be modified by their owner’s keys. The exception is initialization: any user can initialize an uninitialized private account. This enables transfers to a private account owned by someone else, as long as that account is uninitialized.
|
||||
|
||||
## 6. Native token transfer from a public account to a private account owned by someone else
|
||||
|
||||
> [!Important]
|
||||
> We’ll simulate transferring to someone else by creating a new private account we own and treating it as if it belonged to another user.
|
||||
|
||||
### a. Create a new uninitialized private account
|
||||
|
||||
```bash
|
||||
wallet account new private
|
||||
|
||||
# Output:
|
||||
Generated new account with account_id Private/AukXPRBmrYVqoqEW2HTs7N3hvTn3qdNFDcxDHVr5hMm5
|
||||
With npk 0c95ebc4b3830f53da77bb0b80a276a776cdcf6410932acc718dcdb3f788a00e
|
||||
With vpk 039fd12a3674a880d3e917804129141e4170d419d1f9e28a3dcf979c1f2369cb72
|
||||
```
|
||||
|
||||
> [!Tip]
|
||||
> Ignore the private account ID here and use the `npk` and `vpk` values to send to a foreign private account.
|
||||
|
||||
```bash
|
||||
wallet auth-transfer send \
|
||||
--from Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS \
|
||||
--to-npk 0c95ebc4b3830f53da77bb0b80a276a776cdcf6410932acc718dcdb3f788a00e \
|
||||
--to-vpk 039fd12a3674a880d3e917804129141e4170d419d1f9e28a3dcf979c1f2369cb72 \
|
||||
--amount 3
|
||||
```
|
||||
|
||||
> [!Warning]
|
||||
> This command creates a privacy-preserving transaction, which may take a few minutes. The updated values are encrypted and included in the transaction.
|
||||
> Once accepted, the recipient must run `wallet account sync-private` to scan the chain for their encrypted updates and refresh local state.
|
||||
|
||||
> [!Note]
|
||||
> You have seen transfers between two public accounts and from a public sender to a private recipient. Transfers from a private sender, whether to a public account or to another private account, follow the same pattern.
|
||||
26
docs/LEZ testnet v0.1 tutorials/wallet-setup.md
Normal file
26
docs/LEZ testnet v0.1 tutorials/wallet-setup.md
Normal file
@ -0,0 +1,26 @@
|
||||
This repository includes a CLI for interacting with the Logos Blockchain. To install it, run the following command from the root of the repository:
|
||||
|
||||
```bash
|
||||
cargo install --path wallet --force
|
||||
```
|
||||
|
||||
To check that everythin is working, run `wallet help`.
|
||||
|
||||
## Available Wallet Commands
|
||||
|
||||
| Command | Description |
|
||||
|------------------------|-------------------------------------------------------------|
|
||||
| `wallet auth-transfer` | Authenticated transfer (init, send) |
|
||||
| `wallet chain-info` | Chain info queries (current-block-id, block, transaction) |
|
||||
| `wallet account` | Account management (get, list, new, sync-private) |
|
||||
| `wallet pinata` | Piñata faucet (claim) |
|
||||
| `wallet token` | Token operations (new, send) |
|
||||
| `wallet amm` | AMM operations (new, swap, add-liquidity, remove-liquidity) |
|
||||
| `wallet check-health` | Health checks that the wallet is connected to the node |
|
||||
| `wallet config` | Config Setup (get, set) |
|
||||
| `wallet restore-keys ` | Keys restore from a given password at given `depth` |
|
||||
| `wallet deploy-program`| Program deployment |
|
||||
| `wallet help` | Help |
|
||||
|
||||
Some completion scripts exists, see the [completions](./completions/README.md) folder.
|
||||
|
||||
18
examples/program_deployment/Cargo.toml
Normal file
18
examples/program_deployment/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "program_deployment"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
common.workspace = true
|
||||
nssa.workspace = true
|
||||
nssa_core.workspace = true
|
||||
sequencer_service_rpc = { workspace = true, features = ["client"] }
|
||||
wallet.workspace = true
|
||||
|
||||
tokio = { workspace = true, features = ["macros"] }
|
||||
clap.workspace = true
|
||||
660
examples/program_deployment/README.md
Normal file
660
examples/program_deployment/README.md
Normal file
@ -0,0 +1,660 @@
|
||||
# Program deployment tutorial
|
||||
|
||||
This guide walks you through running the sequencer, compiling example programs, deploying a Hello World program, and interacting with accounts.
|
||||
|
||||
You'll find:
|
||||
- Programs: example LEZ programs under `methods/guest/src/bin`.
|
||||
- Runners: scripts to create and submit transactions to invoke these programs publicly and privately under `src/bin`.
|
||||
|
||||
# 0. Install the wallet
|
||||
From the project’s root directory:
|
||||
```bash
|
||||
cargo install --path wallet --force
|
||||
```
|
||||
|
||||
# 1. Run the sequencer
|
||||
From the project’s root directory, start the sequencer by following [these instructions](https://github.com/logos-blockchain/lssa#run-the-sequencer-and-node).
|
||||
|
||||
## Checking and setting up the wallet
|
||||
For sanity let's check that the wallet can connect to it.
|
||||
|
||||
```bash
|
||||
wallet check-health
|
||||
```
|
||||
|
||||
If this is your first time, the wallet will ask for a password. This is used as seed to deterministically generate all account keys (public and private).
|
||||
For this tutorial, use: `program-tutorial`
|
||||
|
||||
You should see `✅All looks good!` if everything went well.
|
||||
|
||||
# 2. Compile the example programs
|
||||
In a second terminal, from the `lssa` root directory, compile the example Risc0 programs:
|
||||
```bash
|
||||
cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
|
||||
```
|
||||
Because this repository is organized as a Cargo workspace, build artifacts are written to the
|
||||
shared `target/` directory at the workspace root by default. The compiled `.bin` files will
|
||||
appear under:
|
||||
```
|
||||
target/riscv32im-risc0-zkvm-elf/docker/
|
||||
```
|
||||
For convenience, export this path:
|
||||
```bash
|
||||
export EXAMPLE_PROGRAMS_BUILD_DIR=$(pwd)/target/riscv32im-risc0-zkvm-elf/docker
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **All remaining commands must be run from the `examples/program_deployment` directory.**
|
||||
|
||||
# 3. Hello world example
|
||||
|
||||
The Hello world program reads an arbitrary sequence of bytes from its instruction and appends them to the data field of the input account.
|
||||
Execution succeeds only if the account is:
|
||||
|
||||
- Uninitialized, or
|
||||
- Already owned by this program
|
||||
|
||||
If uninitialized, the program will claim the account and emit the updated state.
|
||||
|
||||
## Navigate to the example directory
|
||||
All remaining commands must be run from:
|
||||
```bash
|
||||
cd examples/program_deployment
|
||||
```
|
||||
|
||||
## Deploy the Program
|
||||
|
||||
Use the wallet’s built-in program deployment command:
|
||||
```bash
|
||||
wallet deploy-program $EXAMPLE_PROGRAMS_BUILD_DIR/hello_world.bin
|
||||
```
|
||||
|
||||
# 4. Public execution of the Hello world example
|
||||
|
||||
## Create a Public Account
|
||||
|
||||
Generate a new public account:
|
||||
```bash
|
||||
wallet account new public
|
||||
```
|
||||
|
||||
You'll see an output similar to:
|
||||
```bash
|
||||
Generated new account with account_id Public/BzdBoL4JRa5M873cuWb9rbYgASr1pXyaAZ1YW9ertWH9 at path /0
|
||||
```
|
||||
The relevant part is the account id `BzdBoL4JRa5M873cuWb9rbYgASr1pXyaAZ1YW9ertWH9`
|
||||
|
||||
> [!NOTE]
|
||||
> You can optionally assign a label to the account for easier identification using the `--label` option: `wallet account new public --label "my-account"`. Labels must be unique across all accounts.
|
||||
|
||||
## Check the account state
|
||||
New accounts are always Uninitialized. Verify:
|
||||
```bash
|
||||
wallet account get --account-id Public/BzdBoL4JRa5M873cuWb9rbYgASr1pXyaAZ1YW9ertWH9
|
||||
```
|
||||
Expected output:
|
||||
```
|
||||
Account is Uninitialized
|
||||
```
|
||||
The `Public/` prefix tells the wallet to query the public state.
|
||||
|
||||
## Execute the Hello world program
|
||||
Run the example:
|
||||
```bash
|
||||
cargo run --bin run_hello_world \
|
||||
$EXAMPLE_PROGRAMS_BUILD_DIR/hello_world.bin \
|
||||
BzdBoL4JRa5M873cuWb9rbYgASr1pXyaAZ1YW9ertWH9
|
||||
```
|
||||
> [!NOTE]
|
||||
> - Passing the `.bin` lets the script compute the program ID and build the transaction.
|
||||
> - Because this program executes publicly, the node performs the execution.
|
||||
> - The program will claim the account and write data into it.
|
||||
|
||||
Monitor the sequencer terminal to confirm execution.
|
||||
|
||||
## Inspect the updated account
|
||||
After the transaction is processed, check the new state:
|
||||
```bash
|
||||
wallet account get --account-id Public/BzdBoL4JRa5M873cuWb9rbYgASr1pXyaAZ1YW9ertWH9
|
||||
```
|
||||
Example output:
|
||||
```json
|
||||
{
|
||||
"balance": 0,
|
||||
"program_owner_b64": "o6C6/bbjDmN9VUC51McBpPrta8lxrx2X0iHExhX0yNU=",
|
||||
"data_b64": "SG9sYSBtdW5kbyE=",
|
||||
"nonce": 0
|
||||
}
|
||||
```
|
||||
The `data_b64` field contains de data in Base64.
|
||||
Decode it:
|
||||
```bash
|
||||
echo -n SG9sYSBtdW5kbyE= | base64 -d
|
||||
```
|
||||
You should see `Hola mundo!`.
|
||||
|
||||
# 5. Understanding the code in `hello_world.rs`.
|
||||
The Hello world example demonstrates the minimal structure of an NSSA program.
|
||||
Its purpose is very simple: append the instruction bytes to the data field of a single account.
|
||||
|
||||
### What this program does in a nutshell
|
||||
1. Reads the program inputs
|
||||
- The list of pre-state accounts (`pre_states`)
|
||||
- The instruction bytes (`instruction`)
|
||||
- The raw instruction data (used again when writing outputs)
|
||||
2. Checks that there is exactly one input account: this example operates on a single account, so it expects `pre_states` to contain exactly one entry.
|
||||
3. Builds the post-state: It clones the input account and appends the instruction bytes to its data field.
|
||||
4. Handles account claiming logic: If the account is uninitialized (i.e. not yet claimed by any program), its program_owner will equal `DEFAULT_PROGRAM_ID`. In that case, the program issues a claim request, meaning: "This program now owns this account."
|
||||
5. Outputs the proposed state transition: `write_nssa_outputs` emits:
|
||||
- The original instruction data
|
||||
- The original pre-states
|
||||
- The new post-states
|
||||
|
||||
## Code walkthrough
|
||||
1. Reading inputs:
|
||||
```rust
|
||||
let (ProgramInput { pre_states, instruction: greeting }, instruction_data)
|
||||
= read_nssa_inputs::<Instruction>();
|
||||
```
|
||||
2. Extracting the single account:
|
||||
```rust
|
||||
let [pre_state] = pre_states
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| panic!("Input pre states should consist of a single account"));
|
||||
```
|
||||
3. Constructing the updated account post state
|
||||
```rust
|
||||
let mut this = pre_state.account.clone();
|
||||
let mut bytes = this.data.into_inner();
|
||||
bytes.extend_from_slice(&greeting);
|
||||
this.data = bytes.try_into().expect("Data should fit within the allowed limits");
|
||||
```
|
||||
4. Instantiating the `AccountPostState` with a claiming request only if the account pre state is uninitialized:
|
||||
```rust
|
||||
let post_state = if post_account.program_owner == DEFAULT_PROGRAM_ID {
|
||||
AccountPostState::new_claimed(post_account)
|
||||
} else {
|
||||
AccountPostState::new(post_account)
|
||||
};
|
||||
```
|
||||
5. Emmiting the output
|
||||
```rust
|
||||
write_nssa_outputs(instruction_data, vec![pre_state], vec![post_state]);
|
||||
```
|
||||
|
||||
# 6. Understanding the runner script `run_hello_world.rs`
|
||||
The `run_hello_world.rs` example demonstrates how to construct and submit a public transaction that executes the `hello_world` program. Below is a breakdown of what the file does and how the pieces fit together.
|
||||
|
||||
### 1. Wallet initialization
|
||||
```rust
|
||||
let wallet_config = fetch_config().await.unwrap();
|
||||
let wallet_core = WalletCore::start_from_config_update_chain(wallet_config)
|
||||
.await
|
||||
.unwrap();
|
||||
```
|
||||
The example loads the wallet configuration and initializes `WalletCore`.
|
||||
This gives access to:
|
||||
- the sequencer client,
|
||||
- the wallet’s account storage.
|
||||
|
||||
### 2. Parsing inputs
|
||||
```rust
|
||||
let program_path = std::env::args_os().nth(1).unwrap().into_string().unwrap();
|
||||
let account_id: AccountId = std::env::args_os().nth(2).unwrap().into_string().unwrap().parse().unwrap();
|
||||
```
|
||||
The program expects two arguments:
|
||||
- Path to the guest binary
|
||||
- AccountId of the public account to operate on
|
||||
|
||||
This is the account that the program will claim and write data into.
|
||||
|
||||
### 3. Loading the program bytecode
|
||||
```rust
|
||||
let bytecode: Vec<u8> = std::fs::read(program_path).unwrap();
|
||||
let program = Program::new(bytecode).unwrap();
|
||||
```
|
||||
The Risc0 ELF is read from disk and wrapped in a Program object, which can be used to compute the program ID. The ID is used by the node to identify which program is invoked by the transaction.
|
||||
|
||||
|
||||
### 4. Preparing the instruction data
|
||||
```rust
|
||||
let greeting: Vec<u8> = vec![72,111,108,97,32,109,117,110,100,111,33];
|
||||
```
|
||||
The example hardcodes the ASCII bytes for `Hola mundo!`. These bytes are passed to the program as its “instruction,” which the Hello World program simply appends to the account’s data field.
|
||||
|
||||
### 5. Creating the public transaction
|
||||
|
||||
```rust
|
||||
let nonces = vec![];
|
||||
let signing_keys = [];
|
||||
let message = Message::try_new(program.id(), vec![account_id], nonces, greeting).unwrap();
|
||||
let witness_set = WitnessSet::for_message(&message, &signing_keys);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
```
|
||||
|
||||
A public transaction consists of:
|
||||
- a `Message`
|
||||
- a corresponding `WitnessSet`
|
||||
|
||||
For this simple example, no signing or nonces are required. The transaction includes only the program ID, the target account, and the instruction bytes. The Hello World program allows this because it does not explicitly require authorization. In the next example, we’ll see how authorization requirements are enforced and how to construct a transaction that includes signatures and nonces.
|
||||
|
||||
### 6. Submitting the transaction
|
||||
```rust
|
||||
let response = wallet_core.sequencer_client.send_tx_public(tx).await.unwrap();
|
||||
```
|
||||
The transaction is sent to the sequencer, which processes it and updates the public state accordingly.
|
||||
|
||||
Once executed, you’ll be able to query the updated account to see the newly written "Hola mundo!" data.
|
||||
|
||||
# 7. Private execution of the Hello world example
|
||||
|
||||
This section is very similar to the previous case:
|
||||
|
||||
## Create a private account
|
||||
|
||||
Generate a new private account:
|
||||
```bash
|
||||
wallet account new private
|
||||
```
|
||||
|
||||
You'll see an output similar to:
|
||||
```bash
|
||||
Generated new account with account_id Private/7EDHyxejuynBpmbLuiEym9HMUyCYxZDuF8X3B89ADeMr at path /0
|
||||
```
|
||||
The relevant part for this tutorial is the account id `7EDHyxejuynBpmbLuiEym9HMUyCYxZDuF8X3B89ADeMr`
|
||||
|
||||
> [!NOTE]
|
||||
> As with public accounts, you can use the `--label` option to assign a label: `wallet account new private --label "my-private-account"`.
|
||||
|
||||
You can check it's uninitialized with
|
||||
|
||||
```bash
|
||||
wallet account get --account-id Private/7EDHyxejuynBpmbLuiEym9HMUyCYxZDuF8X3B89ADeMr
|
||||
```
|
||||
|
||||
## Privately executing the Hello world program
|
||||
|
||||
### Execute the Hello world program
|
||||
Run the example:
|
||||
```bash
|
||||
cargo run --bin run_hello_world_private \
|
||||
$EXAMPLE_PROGRAMS_BUILD_DIR/hello_world.bin \
|
||||
7EDHyxejuynBpmbLuiEym9HMUyCYxZDuF8X3B89ADeMr
|
||||
```
|
||||
> [!NOTE]
|
||||
> - This command may take a few minutes to complete. A ZK proof of the Hello world program execution and the privacy preserving circuit are being generated. Depending on the machine this can take from 30 seconds to 4 minutes.
|
||||
> - We are passing the same `hello_world.bin` binary as in the previous case with public executions. This is because the program is the same, it is the privacy context of the input account that's different.
|
||||
> - Because this program executes privately, the local machine runs the program and generate the proof of execution.
|
||||
> - The program will claim the private account and write data into it.
|
||||
|
||||
### Syncing the new private account values
|
||||
The `run_hello_world` script submitted a transaction and it was (hopefully) accepted by the node. On chain there is now a commitment to the new private account values, and the account data is stored encrypted. However, the local client hasn’t updated its private state yet. That’s why, if you try to get the private account values now, it still reads the old values from local storage instead.
|
||||
|
||||
```bash
|
||||
wallet account get --account-id Private/7EDHyxejuynBpmbLuiEym9HMUyCYxZDuF8X3B89ADeMr
|
||||
```
|
||||
|
||||
This will still show `Account is Uninitialized`. To see the new values locally, you need to run the wallet sync command. Once the client syncs, the local store will reflect the updated account data.
|
||||
|
||||
To sync private accounts run:
|
||||
```bash
|
||||
wallet account sync-private
|
||||
```
|
||||
> [!NOTE]
|
||||
> - This queries the node for transactions and goes throught the encrypted accounts. Whenever a new value is found for one of the owned private accounts, the local storage is updated.
|
||||
|
||||
After this completes, running
|
||||
```bash
|
||||
wallet account get --account-id Private/7EDHyxejuynBpmbLuiEym9HMUyCYxZDuF8X3B89ADeMr
|
||||
```
|
||||
should show something similar to
|
||||
```json
|
||||
{
|
||||
"balance":0,
|
||||
"program_owner_b64":"dWgtNRixwjC0C8aA0NL0Iuss3Q26Dw6ECk7bzExW4bI=",
|
||||
"data_b64":"SG9sYSBtdW5kbyE=",
|
||||
"nonce":236788677072686551559312843688143377080
|
||||
}
|
||||
```
|
||||
|
||||
## The `run_hello_world_private.rs` runner
|
||||
This example extends the public `run_hello_world.rs` flow by constructing a privacy-preserving transaction instead of a public one.
|
||||
Both runners load a guest program, prepare a transaction, and submit it. But the private version handles encrypted account data, nullifiers, ephemeral keys, and zk proofs.
|
||||
|
||||
Unlike the public version, `run_hello_world_private.rs` must:
|
||||
- prepare the private account pre-state (nullifier keys, membership proof, encrypted values)
|
||||
- derive a shared secret to encrypt the post-state
|
||||
- compute the correct visibility mask (initialized vs. uninitialized private account)
|
||||
- execute the guest program inside the zkVM and produce a proof
|
||||
- build a PrivacyPreservingTransaction composed of:
|
||||
- a Message encoding commitments + encrypted post-state
|
||||
- a WitnessSet embedding the zk proof
|
||||
|
||||
Luckily all that complexity is hidden behind the `wallet_core.send_privacy_preserving_tx` function:
|
||||
```rust
|
||||
let accounts = vec![PrivacyPreservingAccount::PrivateOwned(account_id)];
|
||||
|
||||
// Construct and submit the privacy-preserving transaction
|
||||
wallet_core
|
||||
.send_privacy_preserving_tx(
|
||||
accounts,
|
||||
&Program::serialize_instruction(greeting).unwrap(),
|
||||
&program.into(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
```
|
||||
Check the `run_hello_world_private.rs` file to see how it is used.
|
||||
|
||||
# 8. Account authorization mechanism
|
||||
The Hello world example does not enforce any authorization on the input account. This means any user can execute it on any account, regardless of ownership.
|
||||
NSSA provides a mechanism for programs to enforce proper authorization before an execution can succeed. The meaning of authorization differs between public and private accounts:
|
||||
- Public accounts: authorization requires that the transaction is signed with the account’s signing key.
|
||||
- Private accounts: authorization requires that the circuit verifies knowledge of the account’s nullifier secret key.
|
||||
|
||||
From the program development perspective it is very simple: input accounts come with a flag indicating whether they has been properly authorized. And so, the only difference between the program `hello_world.rs` and `hello_world_with_authorization.rs` is in the lines
|
||||
|
||||
```rust
|
||||
// #### Difference with `hello_world` example here:
|
||||
// Fail if the input account is not authorized
|
||||
// The `is_authorized` field will be correctly populated or verified by the system if
|
||||
// authorization is provided.
|
||||
if !pre_state.is_authorized {
|
||||
panic!("Missing required authorization");
|
||||
}
|
||||
// ####
|
||||
```
|
||||
|
||||
Which just checks the `is_authorized` flag and fails if it is set to false.
|
||||
|
||||
# 9. Public execution of the Hello world with authorization example
|
||||
The workflow to execute it publicly is very similar:
|
||||
|
||||
### Deploy the program
|
||||
```bash
|
||||
wallet deploy-program $EXAMPLE_PROGRAMS_BUILD_DIR/hello_world_with_authorization.bin
|
||||
```
|
||||
|
||||
### Create a new public account
|
||||
Our previous public account is already claimed by the simple Hello world program. So we need a new one to work with this other version of the hello program
|
||||
```bash
|
||||
wallet account new public
|
||||
```
|
||||
|
||||
Outupt:
|
||||
```
|
||||
Generated new account with account_id Public/9Ppqqf8NeCX58pnr8ZqKoHvSoYGqH79dSikZAtLxKgXE at path /1
|
||||
```
|
||||
|
||||
### Run the program
|
||||
|
||||
```bash
|
||||
cargo run --bin run_hello_world_with_authorization \
|
||||
$EXAMPLE_PROGRAMS_BUILD_DIR/hello_world_with_authorization.bin \
|
||||
9Ppqqf8NeCX58pnr8ZqKoHvSoYGqH79dSikZAtLxKgXE
|
||||
```
|
||||
|
||||
# 10. Understanding `run_hello_world_with_authorization.rs`
|
||||
From the runner script perspective, the only difference is that the signing keys are passed to the `WitnessSet` constructor for it to sign it. You can see this in the following parts of the code:
|
||||
|
||||
1. Loading the sigining keys from the wallet storage
|
||||
```rust
|
||||
// Load signing keys to provide authorization
|
||||
let signing_key = wallet_core
|
||||
.storage
|
||||
.user_data
|
||||
.get_pub_account_signing_key(&account_id)
|
||||
.expect("Input account should be a self owned public account");
|
||||
```
|
||||
2. Fetching the current public nonce.
|
||||
```rust
|
||||
// Construct the public transaction
|
||||
// Query the current nonce from the node
|
||||
let nonces = wallet_core
|
||||
.get_accounts_nonces(vec![account_id])
|
||||
.await
|
||||
.expect("Node should be reachable to query account data");
|
||||
```
|
||||
2. Instantiate the witness set using the signing keys
|
||||
```rust
|
||||
let signing_keys = [signing_key];
|
||||
let message = Message::try_new(program.id(), vec![account_id], nonces, greeting).unwrap();
|
||||
// Pass the signing key to sign the message. This will be used by the node
|
||||
// to flag the pre_state as `is_authorized` when executing the program
|
||||
let witness_set = WitnessSet::for_message(&message, &signing_keys);
|
||||
```
|
||||
|
||||
## Seeing the mechanism in action
|
||||
If everything went well you won't notice any difference with the first Hello world, because the runner takes care of signing the transaction to provide authorization and the program just succeeds.
|
||||
Try using the `run_hello_world.rs` runner with the `hello_world_with_authorization.bin` program. This will fail because the runner will submit the transaction without the corresponding signature.
|
||||
```bash
|
||||
cargo run --bin run_hello_world \
|
||||
$EXAMPLE_PROGRAMS_BUILD_DIR/hello_world_with_authorization.bin \
|
||||
9Ppqqf8NeCX58pnr8ZqKoHvSoYGqH79dSikZAtLxKgXE
|
||||
```
|
||||
|
||||
You should see something like the following **on the node logs**.
|
||||
```bash
|
||||
[2025-12-11T13:43:22Z WARN sequencer_core] Error at transition ProgramExecutionFailed(
|
||||
"Guest panicked: Missing required authorization",
|
||||
)
|
||||
```
|
||||
|
||||
# 11. Public and private account interaction example
|
||||
Previous examples only operated on public or private accounts independently. Those minimal programs were useful to introduce basic concepts, but they couldn't demonstrate how different types of accounts interact within a single program invocation.
|
||||
The "Hello world with move function" introduces two operations that require one or two input accounts:
|
||||
- `write`: appends arbitrary bytes to a single account. This is what we already had.
|
||||
- `move_data`: reads all bytes from one account, clears it, and appends those bytes to another account.
|
||||
Because these operations may involve multiple accounts, we'll see how public and private accounts can participate together in one execution. It highlights how ownership checks work, when an account needs to be claimed, and how multiple post-states are emitted when several accounts are modified.
|
||||
|
||||
> [!NOTE]
|
||||
> The program logic is completely agnostic to whether input accounts are public or private. It always executes the same way.
|
||||
> See `methods/guest/src/bin/hello_world_with_move_function.rs`. The program just reads the instruction bytes and updates the accounts state.
|
||||
> All privacy handling happens on the runner side. When constructing the transaction, the runner decides which accounts are public or private and prepares the appropriate proofs. The program itself can't differentiate between privacy modes.
|
||||
|
||||
Let's start by deploying the program
|
||||
```bash
|
||||
wallet deploy-program $EXAMPLE_PROGRAMS_BUILD_DIR/hello_world_with_move_function.bin
|
||||
```
|
||||
|
||||
Let's also create a new public account
|
||||
```bash
|
||||
wallet account new public
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
Generated new account with account_id Public/95iNQMbmxMRY6jULiHYkCzCkYKPEuysvBh5kEHayDxLs at path /0/0
|
||||
```
|
||||
|
||||
Let's execute the write function
|
||||
|
||||
```bash
|
||||
cargo run --bin run_hello_world_with_move_function \
|
||||
$EXAMPLE_PROGRAMS_BUILD_DIR/hello_world_with_move_function.bin \
|
||||
write-public 95iNQMbmxMRY6jULiHYkCzCkYKPEuysvBh5kEHayDxLs mundo!
|
||||
```
|
||||
|
||||
Let's crate a new private account.
|
||||
|
||||
```bash
|
||||
wallet account new private
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
Generated new account with account_id Private/8vzkK7vsdrS2gdPhLk72La8X4FJkgJ5kJLUBRbEVkReU at path /1
|
||||
```
|
||||
|
||||
Let's execute the write function
|
||||
|
||||
```bash
|
||||
cargo run --bin run_hello_world_with_move_function \
|
||||
$EXAMPLE_PROGRAMS_BUILD_DIR/hello_world_with_move_function.bin \
|
||||
write-private 8vzkK7vsdrS2gdPhLk72La8X4FJkgJ5kJLUBRbEVkReU Hola
|
||||
```
|
||||
|
||||
To check the values of the accounts are as expected run:
|
||||
```bash
|
||||
wallet account get --account-id Public/95iNQMbmxMRY6jULiHYkCzCkYKPEuysvBh5kEHayDxLs
|
||||
```
|
||||
and
|
||||
|
||||
```bash
|
||||
wallet account sync-private
|
||||
wallet account get --account-id Private/8vzkK7vsdrS2gdPhLk72La8X4FJkgJ5kJLUBRbEVkReU
|
||||
```
|
||||
|
||||
and check the (base64 encoded) data values are `mundo!` and `Hola` respectively.
|
||||
|
||||
Now we can execute the move function to clear the data on the public account and move it to the private account.
|
||||
|
||||
```bash
|
||||
cargo run --bin run_hello_world_with_move_function \
|
||||
$EXAMPLE_PROGRAMS_BUILD_DIR/hello_world_with_move_function.bin \
|
||||
move-data-public-to-private 95iNQMbmxMRY6jULiHYkCzCkYKPEuysvBh5kEHayDxLs 8vzkK7vsdrS2gdPhLk72La8X4FJkgJ5kJLUBRbEVkReU
|
||||
```
|
||||
|
||||
After succeeding, re run the get and sync commands and check that the public account has empty data and the private account data is `Holamundo!`.
|
||||
|
||||
# 12. Program composition: tail calls
|
||||
Programs can chain calls to other programs when they return. This is the tail call or chained call mechanism. It is used by programs that depend on other programs.
|
||||
|
||||
The examples include a `guest/src/bin/simple_tail_call.rs` program that shows how to trigger this mechanism. It internally calls the first Hello World program with a fixed greeting: `Hello from tail call`.
|
||||
|
||||
> [!NOTE]
|
||||
> This program hardcodes the ID of the Hello World program. If something fails, check that this ID matches the one produced when building the Hello World program. You can see it in the output of `cargo risczero build` from the earlier sections of this tutorial. If it differs, update the ID in `simple_tail_call.rs` and build again.
|
||||
|
||||
As before, let's start by deploying the program
|
||||
|
||||
```bash
|
||||
wallet deploy-program $EXAMPLE_PROGRAMS_BUILD_DIR/simple_tail_call.bin
|
||||
```
|
||||
|
||||
We'll use the first public account of this tutorial. The one with account id `BzdBoL4JRa5M873cuWb9rbYgASr1pXyaAZ1YW9ertWH9`. This account is already owned by the Hello world program and its data reads `Hola mundo!`.
|
||||
|
||||
Let's run the tail call program
|
||||
|
||||
```bash
|
||||
cargo run --bin run_hello_world_through_tail_call \
|
||||
$EXAMPLE_PROGRAMS_BUILD_DIR/simple_tail_call.bin \
|
||||
BzdBoL4JRa5M873cuWb9rbYgASr1pXyaAZ1YW9ertWH9
|
||||
```
|
||||
|
||||
Once the transaction is processed, query the account values with:
|
||||
|
||||
```bash
|
||||
wallet account get --account-id Public/BzdBoL4JRa5M873cuWb9rbYgASr1pXyaAZ1YW9ertWH9
|
||||
```
|
||||
|
||||
You should se an output similar to
|
||||
|
||||
```json
|
||||
{
|
||||
"balance":0,
|
||||
"program_owner_b64":"fpnW4tFY9N6llZcBHaXRwu7xe+7WZnZX9RWzhwNbk1o=",
|
||||
"data_b64":"SG9sYSBtdW5kbyFIZWxsbyBmcm9tIHRhaWwgY2FsbA==",
|
||||
"nonce":0
|
||||
}
|
||||
```
|
||||
|
||||
Decoding the (base64 encoded) data
|
||||
```bash
|
||||
echo -n SG9sYSBtdW5kbyFIZWxsbyBmcm9tIHRhaWwgY2FsbA== | base64 -d
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
Hola mundo!Hello from tail call
|
||||
```
|
||||
## Private tail-calls
|
||||
There's support for tail calls in privacy preserving executions too. The `run_hello_world_through_tail_call_private.rs` runner walks you through the process of invoking such an execution.
|
||||
The only difference is that, since the execution is local, the runner will need both programs: the `simple_tail_call` and it's dependency `hello_world`.
|
||||
|
||||
Let's use our existing private account with id `8vzkK7vsdrS2gdPhLk72La8X4FJkgJ5kJLUBRbEVkReU`. This one is already owned by the `hello_world` program.
|
||||
|
||||
You can test the privacy tail calls with
|
||||
```bash
|
||||
cargo run --bin run_hello_world_through_tail_call_private \
|
||||
$EXAMPLE_PROGRAMS_BUILD_DIR/simple_tail_call.bin \
|
||||
$EXAMPLE_PROGRAMS_BUILD_DIR/hello_world.bin \
|
||||
8vzkK7vsdrS2gdPhLk72La8X4FJkgJ5kJLUBRbEVkReU
|
||||
```
|
||||
|
||||
>[!NOTE]
|
||||
> The above command may take longer than the previous privacy executions because needs to generate proofs of execution of both the `simple_tail_call` and the `hello_world` programs.
|
||||
|
||||
Once finished run the following to see the changes
|
||||
```bash
|
||||
wallet account sync-private
|
||||
wallet account get --account-id Private/8vzkK7vsdrS2gdPhLk72La8X4FJkgJ5kJLUBRbEVkReU
|
||||
```
|
||||
|
||||
# 13. Program derived accounts: authorizing accounts through tail calls
|
||||
|
||||
## Digression: account authority vs account program ownership
|
||||
|
||||
In NSSA there are two distinct concepts that control who can modify an account:
|
||||
**Program Ownership:** Each account has a field: `program_owner: ProgramId`.
|
||||
This indicates which program is allowed to update the account’s state during execution.
|
||||
- If a program is the program_owner of an account, it can freely mutate its fields.
|
||||
- If the account is uninitialized (`program_owner = DEFAULT_PROGRAM_ID`), a program may claim it and become its owner.
|
||||
- If a program is not the owner and the account is not claimable, any attempt to modify it will cause the transition to fail.
|
||||
Program ownership is about mutation rights during program execution.
|
||||
|
||||
**Account authority**: Independent from program ownership, each account also has an authority. The entity that is allowed to set: `is_authorized = true`. This flag indicates that the account has been authorized for use in a transaction.
|
||||
Who can act as authority?
|
||||
- User-defined accounts: The user is the authority. They can mark an account as authorized by:
|
||||
- Signing the transaction (public accounts)
|
||||
- Providing a valid nullifiers secret key ownership proof (private accounts)
|
||||
- Program derived accounts: Programs are automatically the authority of a dedicated namespace of public accounts.
|
||||
|
||||
Each program owns a non-overlapping space of 2^256 **public** account IDs. They do not overlap with:
|
||||
- User accounts (public or private)
|
||||
- Other program’s PDAs
|
||||
|
||||
> [!NOTE]
|
||||
> Currently PDAs are restricted to the public state.
|
||||
|
||||
A program can be the authority of an account owned by another program, which is the most common case.
|
||||
During a chained call, a program can mark its PDA accounts as `is_authorized=true` without requiring any user signatures or nullifier secret keys. This enables programs to safely authorize accounts during program composition. Importantly, these flags can only be set to true for PDA accounts through an execution of the program that is their authority. No user and no other program can execute any transition that requires authorization of PDA accounts belonging to a different program.
|
||||
|
||||
## Running the example
|
||||
This tutorial includes an example of PDA usage in `methods/guest/src/bin/tail_call_with_pda.rs.`. That program’s sole purpose is to forward one of its own PDA accounts, an account for which it is the authority, to the "Hello World with authorization" program via a chained call. The Hello World program will then claim the account and become its program owner, but the `tail_call_with_pda` program remains the authority. This means it is still the only entity capable of marking that account as `is_authorized=true`.
|
||||
|
||||
Deploy the program:
|
||||
```bash
|
||||
wallet deploy-program $EXAMPLE_PROGRAMS_BUILD_DIR/tail_call_with_pda.bin
|
||||
```
|
||||
|
||||
There is no need to create a new account for this example, because we simply use one of the PDA accounts belonging to the `tail_call_with_pda` program.
|
||||
|
||||
Execute the program
|
||||
```bash
|
||||
cargo run --bin run_hello_world_with_authorization_through_tail_call_with_pda $EXAMPLE_PROGRAMS_BUILD_DIR/tail_call_with_pda.bin
|
||||
```
|
||||
|
||||
You'll see an output like the following:
|
||||
|
||||
```bash
|
||||
The program derived account ID is: 3tfTPPuxj3eSE1cLVuNBEk8eSHzpnYS1oqEdeH3Nfsks
|
||||
```
|
||||
|
||||
Then check the status of that account
|
||||
|
||||
```bash
|
||||
wallet account get --account-id Public/3tfTPPuxj3eSE1cLVuNBEk8eSHzpnYS1oqEdeH3Nfsks
|
||||
```
|
||||
|
||||
Output:
|
||||
```bash
|
||||
{
|
||||
"balance":0,
|
||||
"program_owner_b64":"HZXHYRaKf6YusVo8x00/B15uyY5sGsJb1bzH4KlCY5g=",
|
||||
"data_b64": "SGVsbG8gZnJvbSB0YWlsIGNhbGwgd2l0aCBQcm9ncmFtIERlcml2ZWQgQWNjb3VudCBJRA==",
|
||||
"nonce":0"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
14
examples/program_deployment/methods/Cargo.toml
Normal file
14
examples/program_deployment/methods/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "example_program_deployment_methods"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
risc0-build.workspace = true
|
||||
|
||||
[package.metadata.risc0]
|
||||
methods = ["guest"]
|
||||
15
examples/program_deployment/methods/guest/Cargo.toml
Normal file
15
examples/program_deployment/methods/guest/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "example_program_deployment_programs"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nssa_core.workspace = true
|
||||
|
||||
hex.workspace = true
|
||||
bytemuck.workspace = true
|
||||
risc0-zkvm.workspace = true
|
||||
@ -0,0 +1,60 @@
|
||||
use nssa_core::program::{
|
||||
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs,
|
||||
};
|
||||
|
||||
// Hello-world example program.
|
||||
//
|
||||
// This program reads an arbitrary sequence of bytes as its instruction
|
||||
// and appends those bytes to the `data` field of the single input account.
|
||||
//
|
||||
// Execution succeeds only if the input account is either:
|
||||
// - uninitialized, or
|
||||
// - already owned by this program.
|
||||
//
|
||||
// In case the input account is uninitialized, the program claims it.
|
||||
//
|
||||
// The updated account is emitted as the sole post-state.
|
||||
|
||||
type Instruction = Vec<u8>;
|
||||
|
||||
fn main() {
|
||||
// Read inputs
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction: greeting,
|
||||
},
|
||||
instruction_data,
|
||||
) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
// Unpack the input account pre state
|
||||
let [pre_state] = pre_states
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| panic!("Input pre states should consist of a single account"));
|
||||
|
||||
// Construct the post state account values
|
||||
let post_account = {
|
||||
let mut this = pre_state.account.clone();
|
||||
let mut bytes = this.data.into_inner();
|
||||
bytes.extend_from_slice(&greeting);
|
||||
this.data = bytes
|
||||
.try_into()
|
||||
.expect("Data should fit within the allowed limits");
|
||||
this
|
||||
};
|
||||
|
||||
// Wrap the post state account values inside a `AccountPostState` instance.
|
||||
// This is used to forward the account claiming request if any
|
||||
let post_state = if post_account.program_owner == DEFAULT_PROGRAM_ID {
|
||||
// This produces a claim request
|
||||
AccountPostState::new_claimed(post_account)
|
||||
} else {
|
||||
// This doesn't produce a claim request
|
||||
AccountPostState::new(post_account)
|
||||
};
|
||||
|
||||
// The output is a proposed state difference. It will only succeed if the pre states coincide
|
||||
// with the previous values of the accounts, and the transition to the post states conforms
|
||||
// with the NSSA program rules.
|
||||
write_nssa_outputs(instruction_data, vec![pre_state], vec![post_state]);
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
use nssa_core::program::{
|
||||
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs,
|
||||
};
|
||||
|
||||
// Hello-world with authorization example program.
|
||||
//
|
||||
// This program reads an arbitrary sequence of bytes as its instruction
|
||||
// and appends those bytes to the `data` field of the single input account.
|
||||
//
|
||||
// Execution succeeds only if the input account **is authorized** and is either:
|
||||
// - uninitialized, or
|
||||
// - already owned by this program.
|
||||
//
|
||||
// In case the input account is uninitialized, the program claims it.
|
||||
//
|
||||
// The updated account is emitted as the sole post-state.
|
||||
|
||||
type Instruction = Vec<u8>;
|
||||
|
||||
fn main() {
|
||||
// Read inputs
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction: greeting,
|
||||
},
|
||||
instruction_data,
|
||||
) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
// Unpack the input account pre state
|
||||
let [pre_state] = pre_states
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| panic!("Input pre states should consist of a single account"));
|
||||
|
||||
// #### Difference with `hello_world` example here:
|
||||
// Fail if the input account is not authorized
|
||||
// The `is_authorized` field will be correctly populated or verified by the system if
|
||||
// authorization is provided.
|
||||
assert!(pre_state.is_authorized, "Missing required authorization");
|
||||
// ####
|
||||
|
||||
// Construct the post state account values
|
||||
let post_account = {
|
||||
let mut this = pre_state.account.clone();
|
||||
let mut bytes = this.data.into_inner();
|
||||
bytes.extend_from_slice(&greeting);
|
||||
this.data = bytes
|
||||
.try_into()
|
||||
.expect("Data should fit within the allowed limits");
|
||||
this
|
||||
};
|
||||
|
||||
// Wrap the post state account values inside a `AccountPostState` instance.
|
||||
// This is used to forward the account claiming request if any
|
||||
let post_state = if post_account.program_owner == DEFAULT_PROGRAM_ID {
|
||||
// This produces a claim request
|
||||
AccountPostState::new_claimed(post_account)
|
||||
} else {
|
||||
// This doesn't produce a claim request
|
||||
AccountPostState::new(post_account)
|
||||
};
|
||||
|
||||
// The output is a proposed state difference. It will only succeed if the pre states coincide
|
||||
// with the previous values of the accounts, and the transition to the post states conforms
|
||||
// with the NSSA program rules.
|
||||
write_nssa_outputs(instruction_data, vec![pre_state], vec![post_state]);
|
||||
}
|
||||
@ -0,0 +1,99 @@
|
||||
use nssa_core::{
|
||||
account::{Account, AccountWithMetadata, Data},
|
||||
program::{
|
||||
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs,
|
||||
},
|
||||
};
|
||||
|
||||
// Hello-world with write + move_data example program.
|
||||
//
|
||||
// This program reads an instruction of the form `(function_id, data)` and
|
||||
// dispatches to either:
|
||||
//
|
||||
// - `write`: appends `data` to the `data` field of a single input account.
|
||||
// - `move_data`: moves all bytes from one account to another. The source account is cleared and the
|
||||
// destination account receives the appended bytes.
|
||||
//
|
||||
// Execution succeeds only if:
|
||||
// - the accounts involved are either uninitialized, or
|
||||
// - already owned by this program.
|
||||
//
|
||||
// In case an input account is uninitialized, the program will claim it when
|
||||
// producing the post-state.
|
||||
|
||||
const WRITE_FUNCTION_ID: u8 = 0;
|
||||
const MOVE_DATA_FUNCTION_ID: u8 = 1;
|
||||
|
||||
type Instruction = (u8, Vec<u8>);
|
||||
|
||||
fn build_post_state(post_account: Account) -> AccountPostState {
|
||||
if post_account.program_owner == DEFAULT_PROGRAM_ID {
|
||||
// This produces a claim request
|
||||
AccountPostState::new_claimed(post_account)
|
||||
} else {
|
||||
// This doesn't produce a claim request
|
||||
AccountPostState::new(post_account)
|
||||
}
|
||||
}
|
||||
|
||||
fn write(pre_state: AccountWithMetadata, greeting: &[u8]) -> AccountPostState {
|
||||
// Construct the post state account values
|
||||
let post_account = {
|
||||
let mut this = pre_state.account;
|
||||
let mut bytes = this.data.into_inner();
|
||||
bytes.extend_from_slice(greeting);
|
||||
this.data = bytes
|
||||
.try_into()
|
||||
.expect("Data should fit within the allowed limits");
|
||||
this
|
||||
};
|
||||
|
||||
build_post_state(post_account)
|
||||
}
|
||||
|
||||
fn move_data(from_pre: AccountWithMetadata, to_pre: AccountWithMetadata) -> Vec<AccountPostState> {
|
||||
// Construct the post state account values
|
||||
let from_data: Vec<u8> = from_pre.account.data.clone().into();
|
||||
|
||||
let from_post = {
|
||||
let mut this = from_pre.account;
|
||||
this.data = Data::default();
|
||||
build_post_state(this)
|
||||
};
|
||||
|
||||
let to_post = {
|
||||
let mut this = to_pre.account;
|
||||
let mut bytes = this.data.into_inner();
|
||||
bytes.extend_from_slice(&from_data);
|
||||
this.data = bytes
|
||||
.try_into()
|
||||
.expect("Data should fit within the allowed limits");
|
||||
build_post_state(this)
|
||||
};
|
||||
|
||||
vec![from_post, to_post]
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Read input accounts.
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction: (function_id, data),
|
||||
},
|
||||
instruction_words,
|
||||
) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let post_states = match (pre_states.as_slice(), function_id, data.len()) {
|
||||
([account_pre], WRITE_FUNCTION_ID, _) => {
|
||||
let post = write(account_pre.clone(), &data);
|
||||
vec![post]
|
||||
}
|
||||
([account_from_pre, account_to_pre], MOVE_DATA_FUNCTION_ID, 0) => {
|
||||
move_data(account_from_pre.clone(), account_to_pre.clone())
|
||||
}
|
||||
_ => panic!("invalid params"),
|
||||
};
|
||||
|
||||
write_nssa_outputs(instruction_words, pre_states, post_states);
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
use nssa_core::program::{
|
||||
AccountPostState, ChainedCall, ProgramId, ProgramInput, read_nssa_inputs,
|
||||
write_nssa_outputs_with_chained_call,
|
||||
};
|
||||
|
||||
// Tail Call example program.
|
||||
//
|
||||
// This program shows how to chain execution to another program using `ChainedCall`.
|
||||
// It reads a single account, emits it unchanged, and then triggers a tail call
|
||||
// to the Hello World program with a fixed greeting.
|
||||
|
||||
/// This needs to be set to the ID of the Hello world program.
|
||||
/// To get the ID run **from the root directoy of the repository**:
|
||||
/// `cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml`
|
||||
/// This compiles the programs and outputs the IDs in hex that can be used to copy here.
|
||||
const HELLO_WORLD_PROGRAM_ID_HEX: &str =
|
||||
"e9dfc5a5d03c9afa732adae6e0edfce4bbb44c7a2afb9f148f4309917eb2de6f";
|
||||
|
||||
fn hello_world_program_id() -> ProgramId {
|
||||
let hello_world_program_id_bytes: [u8; 32] = hex::decode(HELLO_WORLD_PROGRAM_ID_HEX)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
bytemuck::cast(hello_world_program_id_bytes)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Read inputs
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction: (),
|
||||
},
|
||||
instruction_data,
|
||||
) = read_nssa_inputs::<()>();
|
||||
|
||||
// Unpack the input account pre state
|
||||
let [pre_state] = pre_states
|
||||
.clone()
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| panic!("Input pre states should consist of a single account"));
|
||||
|
||||
// Create the (unchanged) post state
|
||||
let post_state = AccountPostState::new(pre_state.account.clone());
|
||||
|
||||
// Create the chained call
|
||||
let chained_call_greeting: Vec<u8> = b"Hello from tail call".to_vec();
|
||||
let chained_call_instruction_data = risc0_zkvm::serde::to_vec(&chained_call_greeting).unwrap();
|
||||
let chained_call = ChainedCall {
|
||||
program_id: hello_world_program_id(),
|
||||
instruction_data: chained_call_instruction_data,
|
||||
pre_states,
|
||||
pda_seeds: vec![],
|
||||
};
|
||||
|
||||
// Write the outputs
|
||||
write_nssa_outputs_with_chained_call(
|
||||
instruction_data,
|
||||
vec![pre_state],
|
||||
vec![post_state],
|
||||
vec![chained_call],
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
use nssa_core::program::{
|
||||
AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, read_nssa_inputs,
|
||||
write_nssa_outputs_with_chained_call,
|
||||
};
|
||||
|
||||
// Tail Call with PDA example program.
|
||||
//
|
||||
// Demonstrates how to chain execution to another program using `ChainedCall`
|
||||
// while authorizing program-derived accounts.
|
||||
//
|
||||
// Expects a single input account whose Account ID is derived from this
|
||||
// program’s ID and the fixed PDA seed below (as defined by the
|
||||
// `<AccountId as From<(&ProgramId, &PdaSeed)>>` implementation).
|
||||
//
|
||||
// Emits this account unchanged, then performs a tail call to the
|
||||
// Hello-World-with-Authorization program with a fixed greeting. The same
|
||||
// account is passed along but marked with `is_authorized = true`.
|
||||
|
||||
const HELLO_WORLD_WITH_AUTHORIZATION_PROGRAM_ID_HEX: &str =
|
||||
"1d95c761168a7fa62eb15a3cc74d3f075e6ec98e6c1ac25bd5bcc7e0a9426398";
|
||||
const PDA_SEED: PdaSeed = PdaSeed::new([37; 32]);
|
||||
|
||||
fn hello_world_program_id() -> ProgramId {
|
||||
let hello_world_program_id_bytes: [u8; 32] =
|
||||
hex::decode(HELLO_WORLD_WITH_AUTHORIZATION_PROGRAM_ID_HEX)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
bytemuck::cast(hello_world_program_id_bytes)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Read inputs
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction: (),
|
||||
},
|
||||
instruction_data,
|
||||
) = read_nssa_inputs::<()>();
|
||||
|
||||
// Unpack the input account pre state
|
||||
let [pre_state] = pre_states
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| panic!("Input pre states should consist of a single account"));
|
||||
|
||||
// Create the (unchanged) post state
|
||||
let post_state = AccountPostState::new(pre_state.account.clone());
|
||||
|
||||
// Create the chained call
|
||||
let chained_call_greeting: Vec<u8> =
|
||||
b"Hello from tail call with Program Derived Account ID".to_vec();
|
||||
let chained_call_instruction_data = risc0_zkvm::serde::to_vec(&chained_call_greeting).unwrap();
|
||||
|
||||
// Flip the `is_authorized` flag to true
|
||||
let pre_state_for_chained_call = {
|
||||
let mut this = pre_state.clone();
|
||||
this.is_authorized = true;
|
||||
this
|
||||
};
|
||||
let chained_call = ChainedCall {
|
||||
program_id: hello_world_program_id(),
|
||||
instruction_data: chained_call_instruction_data,
|
||||
pre_states: vec![pre_state_for_chained_call],
|
||||
pda_seeds: vec![PDA_SEED],
|
||||
};
|
||||
|
||||
// Write the outputs
|
||||
write_nssa_outputs_with_chained_call(
|
||||
instruction_data,
|
||||
vec![pre_state],
|
||||
vec![post_state],
|
||||
vec![chained_call],
|
||||
);
|
||||
}
|
||||
66
examples/program_deployment/src/bin/run_hello_world.rs
Normal file
66
examples/program_deployment/src/bin/run_hello_world.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use common::transaction::NSSATransaction;
|
||||
use nssa::{
|
||||
AccountId, PublicTransaction,
|
||||
program::Program,
|
||||
public_transaction::{Message, WitnessSet},
|
||||
};
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use wallet::WalletCore;
|
||||
|
||||
// Before running this example, compile the `hello_world.rs` guest program with:
|
||||
//
|
||||
// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
|
||||
//
|
||||
// Note: you must run the above command from the root of the `lssa` repository.
|
||||
// Note: The compiled binary file is stored in
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/hello_world.bin
|
||||
//
|
||||
//
|
||||
// Usage:
|
||||
// cargo run --bin run_hello_world /path/to/guest/binary <account_id>
|
||||
//
|
||||
// Example:
|
||||
// cargo run --bin run_hello_world \
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/hello_world.bin \
|
||||
// Ds8q5PjLcKwwV97Zi7duhRVF9uwA2PuYMoLL7FwCzsXE
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Initialize wallet
|
||||
let wallet_core = WalletCore::from_env().unwrap();
|
||||
|
||||
// Parse arguments
|
||||
// First argument is the path to the program binary
|
||||
let program_path = std::env::args_os().nth(1).unwrap().into_string().unwrap();
|
||||
// Second argument is the account_id
|
||||
let account_id: AccountId = std::env::args_os()
|
||||
.nth(2)
|
||||
.unwrap()
|
||||
.into_string()
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
// Load the program
|
||||
let bytecode: Vec<u8> = std::fs::read(program_path).unwrap();
|
||||
let program = Program::new(bytecode).unwrap();
|
||||
|
||||
// Define the desired greeting in ASCII
|
||||
let greeting: Vec<u8> = vec![72, 111, 108, 97, 32, 109, 117, 110, 100, 111, 33];
|
||||
|
||||
// Construct the public transaction
|
||||
// No nonces nor signing keys are needed for this example. Check out the
|
||||
// `run_hello_world_with_authorization` on how to use them.
|
||||
let nonces = vec![];
|
||||
let signing_keys = [];
|
||||
let message = Message::try_new(program.id(), vec![account_id], nonces, greeting).unwrap();
|
||||
let witness_set = WitnessSet::for_message(&message, &signing_keys);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
|
||||
// Submit the transaction
|
||||
let _response = wallet_core
|
||||
.sequencer_client
|
||||
.send_transaction(NSSATransaction::Public(tx))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
use nssa::{AccountId, program::Program};
|
||||
use wallet::{PrivacyPreservingAccount, WalletCore};
|
||||
|
||||
// Before running this example, compile the `hello_world.rs` guest program with:
|
||||
//
|
||||
// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
|
||||
//
|
||||
// Note: you must run the above command from the root of the `lssa` repository.
|
||||
// Note: The compiled binary file is stored in
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/hello_world.bin
|
||||
//
|
||||
//
|
||||
// Usage:
|
||||
// cargo run --bin run_hello_world_private /path/to/guest/binary <account_id>
|
||||
//
|
||||
// Note: the provided account_id needs to be of a private self owned account
|
||||
//
|
||||
// Example:
|
||||
// cargo run --bin run_hello_world_private \
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/hello_world.bin \
|
||||
// Ds8q5PjLcKwwV97Zi7duhRVF9uwA2PuYMoLL7FwCzsXE
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Initialize wallet
|
||||
let wallet_core = WalletCore::from_env().unwrap();
|
||||
|
||||
// Parse arguments
|
||||
// First argument is the path to the program binary
|
||||
let program_path = std::env::args_os().nth(1).unwrap().into_string().unwrap();
|
||||
// Second argument is the account_id
|
||||
let account_id: AccountId = std::env::args_os()
|
||||
.nth(2)
|
||||
.unwrap()
|
||||
.into_string()
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
// Load the program
|
||||
let bytecode: Vec<u8> = std::fs::read(program_path).unwrap();
|
||||
let program = Program::new(bytecode).unwrap();
|
||||
|
||||
// Define the desired greeting in ASCII
|
||||
let greeting: Vec<u8> = vec![72, 111, 108, 97, 32, 109, 117, 110, 100, 111, 33];
|
||||
|
||||
let accounts = vec![PrivacyPreservingAccount::PrivateOwned(account_id)];
|
||||
|
||||
// Construct and submit the privacy-preserving transaction
|
||||
wallet_core
|
||||
.send_privacy_preserving_tx(
|
||||
accounts,
|
||||
Program::serialize_instruction(greeting).unwrap(),
|
||||
&program.into(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
use common::transaction::NSSATransaction;
|
||||
use nssa::{
|
||||
AccountId, PublicTransaction,
|
||||
program::Program,
|
||||
public_transaction::{Message, WitnessSet},
|
||||
};
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use wallet::WalletCore;
|
||||
|
||||
// Before running this example, compile the `simple_tail_call.rs` guest program with:
|
||||
//
|
||||
// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
|
||||
//
|
||||
// Note: you must run the above command from the root of the `lssa` repository.
|
||||
// Note: The compiled binary file is stored in
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/simple_tail_call.bin
|
||||
//
|
||||
//
|
||||
// Usage:
|
||||
// cargo run --bin run_hello_world_through_tail_call /path/to/guest/binary <account_id>
|
||||
//
|
||||
// Example:
|
||||
// cargo run --bin run_hello_world_through_tail_call \
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/simple_tail_call.bin \
|
||||
// Ds8q5PjLcKwwV97Zi7duhRVF9uwA2PuYMoLL7FwCzsXE
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Initialize wallet
|
||||
let wallet_core = WalletCore::from_env().unwrap();
|
||||
|
||||
// Parse arguments
|
||||
// First argument is the path to the program binary
|
||||
let program_path = std::env::args_os().nth(1).unwrap().into_string().unwrap();
|
||||
// Second argument is the account_id
|
||||
let account_id: AccountId = std::env::args_os()
|
||||
.nth(2)
|
||||
.unwrap()
|
||||
.into_string()
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
// Load the program
|
||||
let bytecode: Vec<u8> = std::fs::read(program_path).unwrap();
|
||||
let program = Program::new(bytecode).unwrap();
|
||||
|
||||
let instruction_data = ();
|
||||
let nonces = vec![];
|
||||
let signing_keys = [];
|
||||
let message =
|
||||
Message::try_new(program.id(), vec![account_id], nonces, instruction_data).unwrap();
|
||||
let witness_set = WitnessSet::for_message(&message, &signing_keys);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
|
||||
// Submit the transaction
|
||||
let _response = wallet_core
|
||||
.sequencer_client
|
||||
.send_transaction(NSSATransaction::Public(tx))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use nssa::{
|
||||
AccountId, ProgramId, privacy_preserving_transaction::circuit::ProgramWithDependencies,
|
||||
program::Program,
|
||||
};
|
||||
use wallet::{PrivacyPreservingAccount, WalletCore};
|
||||
|
||||
// Before running this example, compile the `simple_tail_call.rs` guest program with:
|
||||
//
|
||||
// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
|
||||
//
|
||||
// Note: you must run the above command from the root of the `lssa` repository.
|
||||
// Note: The compiled binary file is stored in
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/simple_tail_call.bin
|
||||
//
|
||||
//
|
||||
// Usage:
|
||||
// cargo run --bin run_hello_world_through_tail_call_private /path/to/guest/binary <account_id>
|
||||
//
|
||||
// Example:
|
||||
// cargo run --bin run_hello_world_through_tail_call \
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/simple_tail_call.bin \
|
||||
// Ds8q5PjLcKwwV97Zi7duhRVF9uwA2PuYMoLL7FwCzsXE
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Initialize wallet
|
||||
let wallet_core = WalletCore::from_env().unwrap();
|
||||
|
||||
// Parse arguments
|
||||
// First argument is the path to the simple_tail_call program binary
|
||||
let simple_tail_call_path = std::env::args_os().nth(1).unwrap().into_string().unwrap();
|
||||
// Second argument is the path to the hello_world program binary
|
||||
let hello_world_path = std::env::args_os().nth(2).unwrap().into_string().unwrap();
|
||||
// Third argument is the account_id
|
||||
let account_id: AccountId = std::env::args_os()
|
||||
.nth(3)
|
||||
.unwrap()
|
||||
.into_string()
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
// Load the program and its dependencies (the hellow world program)
|
||||
let simple_tail_call_bytecode: Vec<u8> = std::fs::read(simple_tail_call_path).unwrap();
|
||||
let simple_tail_call = Program::new(simple_tail_call_bytecode).unwrap();
|
||||
let hello_world_bytecode: Vec<u8> = std::fs::read(hello_world_path).unwrap();
|
||||
let hello_world = Program::new(hello_world_bytecode).unwrap();
|
||||
let dependencies: HashMap<ProgramId, Program> =
|
||||
std::iter::once((hello_world.id(), hello_world)).collect();
|
||||
let program_with_dependencies = ProgramWithDependencies::new(simple_tail_call, dependencies);
|
||||
|
||||
let accounts = vec![PrivacyPreservingAccount::PrivateOwned(account_id)];
|
||||
|
||||
// Construct and submit the privacy-preserving transaction
|
||||
let instruction = ();
|
||||
wallet_core
|
||||
.send_privacy_preserving_tx(
|
||||
accounts,
|
||||
Program::serialize_instruction(instruction).unwrap(),
|
||||
&program_with_dependencies,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
use common::transaction::NSSATransaction;
|
||||
use nssa::{
|
||||
AccountId, PublicTransaction,
|
||||
program::Program,
|
||||
public_transaction::{Message, WitnessSet},
|
||||
};
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use wallet::WalletCore;
|
||||
|
||||
// Before running this example, compile the `hello_world_with_authorization.rs` guest program with:
|
||||
//
|
||||
// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
|
||||
//
|
||||
// Note: you must run the above command from the root of the `lssa` repository.
|
||||
// Note: The compiled binary file is stored in
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/hello_world_with_authorization.bin
|
||||
//
|
||||
//
|
||||
// Usage:
|
||||
// ./run_hello_world_with_authorization /path/to/guest/binary <account_id>
|
||||
//
|
||||
// Note: the provided account_id needs to be of a public self owned account
|
||||
//
|
||||
// Example:
|
||||
// cargo run --bin run_hello_world_with_authorization \
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/hello_world_with_authorization.bin \
|
||||
// Ds8q5PjLcKwwV97Zi7duhRVF9uwA2PuYMoLL7FwCzsXE
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Initialize wallet
|
||||
let wallet_core = WalletCore::from_env().unwrap();
|
||||
|
||||
// Parse arguments
|
||||
// First argument is the path to the program binary
|
||||
let program_path = std::env::args_os().nth(1).unwrap().into_string().unwrap();
|
||||
// Second argument is the account_id
|
||||
let account_id: AccountId = std::env::args_os()
|
||||
.nth(2)
|
||||
.unwrap()
|
||||
.into_string()
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
// Load the program
|
||||
let bytecode: Vec<u8> = std::fs::read(program_path).unwrap();
|
||||
let program = Program::new(bytecode).unwrap();
|
||||
|
||||
// Load signing keys to provide authorization
|
||||
let signing_key = wallet_core
|
||||
.storage()
|
||||
.user_data
|
||||
.get_pub_account_signing_key(account_id)
|
||||
.expect("Input account should be a self owned public account");
|
||||
|
||||
// Define the desired greeting in ASCII
|
||||
let greeting: Vec<u8> = vec![72, 111, 108, 97, 32, 109, 117, 110, 100, 111, 33];
|
||||
|
||||
// Construct the public transaction
|
||||
// Query the current nonce from the node
|
||||
let nonces = wallet_core
|
||||
.get_accounts_nonces(vec![account_id])
|
||||
.await
|
||||
.expect("Node should be reachable to query account data");
|
||||
let signing_keys = [signing_key];
|
||||
let message = Message::try_new(program.id(), vec![account_id], nonces, greeting).unwrap();
|
||||
// Pass the signing key to sign the message. This will be used by the node
|
||||
// to flag the pre_state as `is_authorized` when executing the program
|
||||
let witness_set = WitnessSet::for_message(&message, &signing_keys);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
|
||||
// Submit the transaction
|
||||
let _response = wallet_core
|
||||
.sequencer_client
|
||||
.send_transaction(NSSATransaction::Public(tx))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
#![expect(
|
||||
clippy::print_stdout,
|
||||
reason = "This is an example program, it's fine to print to stdout"
|
||||
)]
|
||||
|
||||
use common::transaction::NSSATransaction;
|
||||
use nssa::{
|
||||
AccountId, PublicTransaction,
|
||||
program::Program,
|
||||
public_transaction::{Message, WitnessSet},
|
||||
};
|
||||
use nssa_core::program::PdaSeed;
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use wallet::WalletCore;
|
||||
|
||||
// Before running this example, compile the `simple_tail_call.rs` guest program with:
|
||||
//
|
||||
// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
|
||||
//
|
||||
// Note: you must run the above command from the root of the `lssa` repository.
|
||||
// Note: The compiled binary file is stored in
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/simple_tail_call.bin
|
||||
//
|
||||
//
|
||||
// Usage:
|
||||
// cargo run --bin run_hello_world_with_authorization_through_tail_call_with_pda
|
||||
// /path/to/guest/binary <account_id>
|
||||
//
|
||||
// Example:
|
||||
// cargo run --bin run_hello_world_with_authorization_through_tail_call_with_pda \
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/tail_call_with_pda.bin
|
||||
|
||||
const PDA_SEED: PdaSeed = PdaSeed::new([37; 32]);
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Initialize wallet
|
||||
let wallet_core = WalletCore::from_env().unwrap();
|
||||
|
||||
// Parse arguments
|
||||
// First argument is the path to the program binary
|
||||
let program_path = std::env::args_os().nth(1).unwrap().into_string().unwrap();
|
||||
|
||||
// Load the program
|
||||
let bytecode: Vec<u8> = std::fs::read(program_path).unwrap();
|
||||
let program = Program::new(bytecode).unwrap();
|
||||
|
||||
// Compute the PDA to pass it as input account to the public execution
|
||||
let pda = AccountId::from((&program.id(), &PDA_SEED));
|
||||
let account_ids = vec![pda];
|
||||
let instruction_data = ();
|
||||
let nonces = vec![];
|
||||
let signing_keys = [];
|
||||
let message = Message::try_new(program.id(), account_ids, nonces, instruction_data).unwrap();
|
||||
let witness_set = WitnessSet::for_message(&message, &signing_keys);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
|
||||
// Submit the transaction
|
||||
let _response = wallet_core
|
||||
.sequencer_client
|
||||
.send_transaction(NSSATransaction::Public(tx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("The program derived account id is: {pda}");
|
||||
}
|
||||
@ -0,0 +1,155 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
use common::transaction::NSSATransaction;
|
||||
use nssa::{PublicTransaction, program::Program, public_transaction};
|
||||
use sequencer_service_rpc::RpcClient as _;
|
||||
use wallet::{PrivacyPreservingAccount, WalletCore};
|
||||
|
||||
// Before running this example, compile the `hello_world_with_move_function.rs` guest program with:
|
||||
//
|
||||
// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml
|
||||
//
|
||||
// Note: you must run the above command from the root of the `lssa` repository.
|
||||
// Note: The compiled binary file is stored in
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/hello_world_with_move_function.bin
|
||||
//
|
||||
//
|
||||
// Usage:
|
||||
// cargo run --bin run_hello_world_with_move_function /path/to/guest/binary <function> <params>
|
||||
//
|
||||
// Example:
|
||||
// cargo run --bin run_hello_world_with_move_function \
|
||||
// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/hello_world_with_move_function.bin \
|
||||
// write-public Ds8q5PjLcKwwV97Zi7duhRVF9uwA2PuYMoLL7FwCzsXE Hola
|
||||
|
||||
const WRITE_FUNCTION_ID: u8 = 0;
|
||||
const MOVE_DATA_FUNCTION_ID: u8 = 1;
|
||||
|
||||
type Instruction = (u8, Vec<u8>);
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Cli {
|
||||
/// Path to program binary.
|
||||
program_path: String,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Command {
|
||||
/// Write instruction into one account.
|
||||
WritePublic {
|
||||
account_id: String,
|
||||
greeting: String,
|
||||
},
|
||||
WritePrivate {
|
||||
account_id: String,
|
||||
greeting: String,
|
||||
},
|
||||
/// Move data between two accounts.
|
||||
MoveDataPublicToPublic {
|
||||
from: String,
|
||||
to: String,
|
||||
},
|
||||
MoveDataPublicToPrivate {
|
||||
from: String,
|
||||
to: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
// Load the program
|
||||
let bytecode: Vec<u8> = std::fs::read(cli.program_path).unwrap();
|
||||
let program = Program::new(bytecode).unwrap();
|
||||
|
||||
// Initialize wallet
|
||||
let wallet_core = WalletCore::from_env().unwrap();
|
||||
|
||||
match cli.command {
|
||||
Command::WritePublic {
|
||||
account_id,
|
||||
greeting,
|
||||
} => {
|
||||
let instruction: Instruction = (WRITE_FUNCTION_ID, greeting.into_bytes());
|
||||
let account_id = account_id.parse().unwrap();
|
||||
let nonces = vec![];
|
||||
let message = public_transaction::Message::try_new(
|
||||
program.id(),
|
||||
vec![account_id],
|
||||
nonces,
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
|
||||
// Submit the transaction
|
||||
let _response = wallet_core
|
||||
.sequencer_client
|
||||
.send_transaction(NSSATransaction::Public(tx))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
Command::WritePrivate {
|
||||
account_id,
|
||||
greeting,
|
||||
} => {
|
||||
let instruction: Instruction = (WRITE_FUNCTION_ID, greeting.into_bytes());
|
||||
let account_id = account_id.parse().unwrap();
|
||||
let accounts = vec![PrivacyPreservingAccount::PrivateOwned(account_id)];
|
||||
|
||||
wallet_core
|
||||
.send_privacy_preserving_tx(
|
||||
accounts,
|
||||
Program::serialize_instruction(instruction).unwrap(),
|
||||
&program.into(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
Command::MoveDataPublicToPublic { from, to } => {
|
||||
let instruction: Instruction = (MOVE_DATA_FUNCTION_ID, vec![]);
|
||||
let from = from.parse().unwrap();
|
||||
let to = to.parse().unwrap();
|
||||
let nonces = vec![];
|
||||
let message = public_transaction::Message::try_new(
|
||||
program.id(),
|
||||
vec![from, to],
|
||||
nonces,
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
|
||||
// Submit the transaction
|
||||
let _response = wallet_core
|
||||
.sequencer_client
|
||||
.send_transaction(NSSATransaction::Public(tx))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
Command::MoveDataPublicToPrivate { from, to } => {
|
||||
let instruction: Instruction = (MOVE_DATA_FUNCTION_ID, vec![]);
|
||||
let from = from.parse().unwrap();
|
||||
let to = to.parse().unwrap();
|
||||
|
||||
let accounts = vec![
|
||||
PrivacyPreservingAccount::Public(from),
|
||||
PrivacyPreservingAccount::PrivateOwned(to),
|
||||
];
|
||||
|
||||
wallet_core
|
||||
.send_privacy_preserving_tx(
|
||||
accounts,
|
||||
Program::serialize_instruction(instruction).unwrap(),
|
||||
&program.into(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
explorer_service/.gitignore
vendored
Normal file
11
explorer_service/.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
# Leptos build outputs
|
||||
/target
|
||||
/pkg
|
||||
/site
|
||||
|
||||
# WASM artifacts
|
||||
*.wasm
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
76
explorer_service/Cargo.toml
Normal file
76
explorer_service/Cargo.toml
Normal file
@ -0,0 +1,76 @@
|
||||
[package]
|
||||
name = "explorer_service"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
indexer_service_protocol.workspace = true
|
||||
|
||||
# Leptos framework
|
||||
leptos = "0.8.15"
|
||||
leptos_meta = "0.8.5"
|
||||
leptos_router = "0.8.11"
|
||||
|
||||
# Serialization
|
||||
serde.workspace = true
|
||||
|
||||
# Logging
|
||||
log.workspace = true
|
||||
console_error_panic_hook = "0.1"
|
||||
console_log = "1.0"
|
||||
|
||||
# Date/Time
|
||||
chrono.workspace = true
|
||||
|
||||
# URL encoding
|
||||
urlencoding = "2.1"
|
||||
|
||||
# WASM-specific
|
||||
wasm-bindgen = "0.2"
|
||||
web-sys = { version = "0.3", features = [
|
||||
"Window",
|
||||
"Document",
|
||||
"Location",
|
||||
"HtmlInputElement",
|
||||
] }
|
||||
|
||||
# Server-side dependencies (optional, enabled by features)
|
||||
indexer_service_rpc = { workspace = true, features = [
|
||||
"client",
|
||||
], optional = true }
|
||||
jsonrpsee = { workspace = true, features = ["http-client"], optional = true }
|
||||
tokio = { workspace = true, optional = true }
|
||||
axum = { version = "0.8.8", optional = true }
|
||||
leptos_axum = { version = "0.8.7", optional = true }
|
||||
clap = { workspace = true, features = ["derive"], optional = true }
|
||||
url = { workspace = true, optional = true }
|
||||
env_logger = { workspace = true, optional = true }
|
||||
|
||||
# Mandatory server side dependencies
|
||||
itertools.workspace = true
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"leptos/ssr",
|
||||
"dep:indexer_service_rpc",
|
||||
"dep:jsonrpsee",
|
||||
"dep:tokio",
|
||||
"dep:axum",
|
||||
"dep:leptos_axum",
|
||||
"dep:clap",
|
||||
"dep:url",
|
||||
"dep:env_logger",
|
||||
]
|
||||
|
||||
[package.metadata.leptos]
|
||||
bin-features = ["ssr"]
|
||||
lib-features = ["hydrate"]
|
||||
assets-dir = "public"
|
||||
58
explorer_service/Dockerfile
Normal file
58
explorer_service/Dockerfile
Normal file
@ -0,0 +1,58 @@
|
||||
FROM rust:1.94.0-trixie AS builder
|
||||
|
||||
# Install cargo-binstall, which makes it easier to install other
|
||||
# cargo extensions like cargo-leptos
|
||||
RUN wget https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz
|
||||
RUN tar -xvf cargo-binstall-x86_64-unknown-linux-musl.tgz
|
||||
RUN cp cargo-binstall /usr/local/cargo/bin
|
||||
|
||||
# Install required tools
|
||||
RUN apt-get update -y \
|
||||
&& apt-get install -y --no-install-recommends clang
|
||||
|
||||
# Install cargo-leptos
|
||||
RUN cargo binstall cargo-leptos -y
|
||||
|
||||
# Add the WASM target
|
||||
RUN rustup target add wasm32-unknown-unknown
|
||||
|
||||
# Make an /explorer_service dir, which everything will eventually live in
|
||||
RUN mkdir -p /explorer_service
|
||||
WORKDIR /explorer_service
|
||||
COPY . .
|
||||
|
||||
# Build the app
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry/index \
|
||||
--mount=type=cache,target=/usr/local/cargo/registry/cache \
|
||||
--mount=type=cache,target=/usr/local/cargo/git \
|
||||
--mount=type=cache,target=/explorer_service/target \
|
||||
cargo leptos build --release -vv \
|
||||
&& cp /explorer_service/target/release/explorer_service /usr/local/bin/explorer_service \
|
||||
&& cp -r /explorer_service/target/site /explorer_service/site_output
|
||||
|
||||
FROM debian:trixie-slim AS runtime
|
||||
WORKDIR /explorer_service
|
||||
RUN apt-get update -y \
|
||||
&& apt-get install -y --no-install-recommends openssl ca-certificates \
|
||||
&& apt-get autoremove -y \
|
||||
&& apt-get clean -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy the server binary to the /explorer_service directory
|
||||
COPY --from=builder /usr/local/bin/explorer_service /explorer_service/
|
||||
|
||||
# /target/site contains our JS/WASM/CSS, etc.
|
||||
COPY --from=builder /explorer_service/site_output /explorer_service/site
|
||||
|
||||
# Copy Cargo.toml as it’s needed at runtime
|
||||
COPY --from=builder /explorer_service/Cargo.toml /explorer_service/
|
||||
|
||||
# Set any required env variables
|
||||
ENV RUST_LOG="info"
|
||||
ENV LEPTOS_SITE_ADDR="0.0.0.0:8080"
|
||||
ENV LEPTOS_SITE_ROOT="site"
|
||||
ENV INDEXER_RPC_URL="http://localhost:8779"
|
||||
EXPOSE 8080
|
||||
|
||||
# Run the server
|
||||
CMD ["/explorer_service/explorer_service"]
|
||||
71
explorer_service/README.md
Normal file
71
explorer_service/README.md
Normal file
@ -0,0 +1,71 @@
|
||||
# LEE Blockchain Explorer
|
||||
|
||||
A web-based UI for exploring the blockchain state, built with Rust and Leptos framework.
|
||||
|
||||
## Features
|
||||
|
||||
- **Main Page**: Search for blocks, transactions, or accounts by hash/ID. View recent blocks.
|
||||
- **Block Page**: View detailed block information and all transactions within a block.
|
||||
- **Transaction Page**: View transaction details including type, accounts involved, and proofs.
|
||||
- **Account Page**: View account state and transaction history.
|
||||
|
||||
## Architecture
|
||||
|
||||
- **Framework**: Leptos 0.8 with SSR (Server-Side Rendering) and hydration
|
||||
- **Data Source**: Indexer Service JSON-RPC API
|
||||
- **Components**: Reusable BlockPreview, TransactionPreview, and AccountPreview components
|
||||
- **Styling**: Custom CSS with responsive design
|
||||
|
||||
## Development
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Rust (stable or nightly)
|
||||
- `cargo-leptos` tool: `cargo install cargo-leptos`
|
||||
- Running indexer service at `http://localhost:8080/rpc` (or configure via `INDEXER_RPC_URL`)
|
||||
|
||||
### Build and Run
|
||||
|
||||
```bash
|
||||
# Development mode (with hot-reload)
|
||||
cargo leptos watch
|
||||
|
||||
# Production build
|
||||
cargo leptos build --release
|
||||
|
||||
# Run production build
|
||||
cargo leptos serve --release
|
||||
```
|
||||
|
||||
The explorer will be available at `http://localhost:3000` by default.
|
||||
|
||||
### Configuration
|
||||
|
||||
Set the `INDEXER_RPC_URL` environment variable to point to your indexer service:
|
||||
|
||||
```bash
|
||||
export INDEXER_RPC_URL=http://localhost:8080/rpc
|
||||
cargo leptos watch
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### Search
|
||||
|
||||
The search bar supports:
|
||||
- Block IDs (numeric)
|
||||
- Block hashes (64-character hex)
|
||||
- Transaction hashes (64-character hex)
|
||||
- Account IDs (64-character hex)
|
||||
|
||||
### Real-time Updates
|
||||
|
||||
The main page loads recent blocks and can be extended to subscribe to new blocks via WebSocket.
|
||||
|
||||
### Responsive Design
|
||||
|
||||
The UI is mobile-friendly and adapts to different screen sizes.
|
||||
|
||||
## License
|
||||
|
||||
See LICENSE file in the repository root.
|
||||
11
explorer_service/docker-compose.yml
Normal file
11
explorer_service/docker-compose.yml
Normal file
@ -0,0 +1,11 @@
|
||||
services:
|
||||
explorer_service:
|
||||
image: lssa/explorer_service
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: explorer_service/Dockerfile
|
||||
container_name: explorer_service
|
||||
environment:
|
||||
INDEXER_RPC_URL: ${INDEXER_RPC_URL:-http://localhost:8779}
|
||||
ports:
|
||||
- "8080:8080"
|
||||
516
explorer_service/public/explorer.css
Normal file
516
explorer_service/public/explorer.css
Normal file
@ -0,0 +1,516 @@
|
||||
/* Reset and base styles */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
background-color: #f5f7fa;
|
||||
color: #2c3e50;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* App layout */
|
||||
.app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
background-color: #2c3e50;
|
||||
color: white;
|
||||
padding: 1rem 2rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.app-nav {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.nav-logo {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nav-logo:hover {
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
.app-main {
|
||||
flex: 1;
|
||||
max-width: 1200px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.app-footer {
|
||||
background-color: #34495e;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
/* Page headers */
|
||||
.page-header h1 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
/* Search section */
|
||||
.search-section {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
padding: 0.75rem 1rem;
|
||||
border: 2px solid #dde4ed;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
outline: none;
|
||||
border-color: #3498db;
|
||||
}
|
||||
|
||||
.search-button {
|
||||
padding: 0.75rem 2rem;
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.search-button:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
/* Block preview */
|
||||
.block-preview {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.block-preview:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.block-preview-link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.block-preview-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.block-id .label {
|
||||
color: #7f8c8d;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.block-id .value {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.block-status {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 20px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background-color: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.status-safe {
|
||||
background-color: #d1ecf1;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
.status-finalized {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.block-preview-body {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.block-field {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
color: #7f8c8d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.field-value {
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.hash {
|
||||
font-family: "Courier New", monospace;
|
||||
font-size: 0.9rem;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* Transaction preview */
|
||||
.transaction-preview {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.transaction-preview:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.transaction-preview-link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.transaction-preview-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.tx-type {
|
||||
padding: 0.4rem 0.8rem;
|
||||
border-radius: 16px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
border: 2px solid;
|
||||
}
|
||||
|
||||
.tx-type-public {
|
||||
background-color: #e3f2fd;
|
||||
color: #0d47a1;
|
||||
border-color: #1976d2;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.tx-type-private {
|
||||
background-color: #ffe0f0;
|
||||
color: #880e4f;
|
||||
border-color: #c2185b;
|
||||
border-style: dashed;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.tx-type-deployment {
|
||||
background-color: #fff3e0;
|
||||
color: #e65100;
|
||||
border-color: #ff9800;
|
||||
border-style: dotted;
|
||||
}
|
||||
|
||||
.tx-hash {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.transaction-preview-body {
|
||||
color: #7f8c8d;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Account preview */
|
||||
.account-preview {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.account-preview:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.account-preview-link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.account-preview-header {
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.account-id .label {
|
||||
color: #7f8c8d;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.account-id .value {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.account-preview-body {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.account-field {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.account-not-found {
|
||||
color: #e74c3c;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Detail pages */
|
||||
.block-detail,
|
||||
.transaction-detail,
|
||||
.account-detail {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.block-info,
|
||||
.transaction-info,
|
||||
.account-info,
|
||||
.transaction-details {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.block-info h2,
|
||||
.transaction-info h2,
|
||||
.account-info h2,
|
||||
.transaction-details h2 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
padding: 0.75rem;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #7f8c8d;
|
||||
font-weight: 600;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #2c3e50;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.signature {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
/* Transactions list */
|
||||
.block-transactions,
|
||||
.account-transactions {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.block-transactions h2,
|
||||
.account-transactions h2 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.transactions-list {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.no-transactions {
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
color: #7f8c8d;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* Accounts list */
|
||||
.accounts-list {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.account-item {
|
||||
padding: 0.75rem;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.account-item a {
|
||||
color: #3498db;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.account-item a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.nonce {
|
||||
color: #7f8c8d;
|
||||
font-size: 0.9rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
/* Loading and error states */
|
||||
.loading,
|
||||
.loading-more {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: #7f8c8d;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.error,
|
||||
.error-page {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.not-found,
|
||||
.not-found-page {
|
||||
text-align: center;
|
||||
padding: 3rem;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
.not-found-page h1 {
|
||||
font-size: 4rem;
|
||||
color: #e74c3c;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.not-found-page a {
|
||||
color: #3498db;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.not-found-page a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Load more button */
|
||||
.load-more-button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
margin-top: 1rem;
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.load-more-button:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.app-main {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.search-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.block-preview-header,
|
||||
.transaction-preview-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
161
explorer_service/src/api.rs
Normal file
161
explorer_service/src/api.rs
Normal file
@ -0,0 +1,161 @@
|
||||
use indexer_service_protocol::{Account, AccountId, Block, BlockId, HashType, Transaction};
|
||||
use leptos::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Search results structure.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct SearchResults {
|
||||
pub blocks: Vec<Block>,
|
||||
pub transactions: Vec<Transaction>,
|
||||
pub accounts: Vec<(AccountId, Account)>,
|
||||
}
|
||||
|
||||
/// RPC client type.
|
||||
#[cfg(feature = "ssr")]
|
||||
pub type IndexerRpcClient = jsonrpsee::http_client::HttpClient;
|
||||
|
||||
/// Get account information by ID
|
||||
#[server]
|
||||
pub async fn get_account(account_id: AccountId) -> Result<Account, ServerFnError> {
|
||||
use indexer_service_rpc::RpcClient as _;
|
||||
let client = expect_context::<IndexerRpcClient>();
|
||||
client
|
||||
.get_account(account_id)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
|
||||
}
|
||||
|
||||
/// Search for a block, transaction, or account by query string
|
||||
#[server]
|
||||
pub async fn search(query: String) -> Result<SearchResults, ServerFnError> {
|
||||
use std::str::FromStr as _;
|
||||
|
||||
use indexer_service_rpc::RpcClient as _;
|
||||
|
||||
let client = expect_context::<IndexerRpcClient>();
|
||||
|
||||
let mut blocks = Vec::new();
|
||||
let mut transactions = Vec::new();
|
||||
let mut accounts = Vec::new();
|
||||
|
||||
// Try as hash
|
||||
if let Ok(hash) = HashType::from_str(&query) {
|
||||
// Try as block hash
|
||||
if let Ok(Some(block)) = client.get_block_by_hash(hash).await {
|
||||
blocks.push(block);
|
||||
}
|
||||
|
||||
// Try as transaction hash
|
||||
if let Ok(Some(tx)) = client.get_transaction(hash).await {
|
||||
transactions.push(tx);
|
||||
}
|
||||
}
|
||||
|
||||
// Try as account ID
|
||||
if let Ok(account_id) = AccountId::from_str(&query)
|
||||
&& let Ok(account) = client.get_account(account_id).await
|
||||
{
|
||||
accounts.push((account_id, account));
|
||||
}
|
||||
|
||||
// Try as block ID
|
||||
if let Ok(block_id) = query.parse::<u64>()
|
||||
&& let Ok(Some(block)) = client.get_block_by_id(block_id).await
|
||||
{
|
||||
blocks.push(block);
|
||||
}
|
||||
|
||||
Ok(SearchResults {
|
||||
blocks,
|
||||
transactions,
|
||||
accounts,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get block by ID
|
||||
#[server]
|
||||
pub async fn get_block_by_id(block_id: BlockId) -> Result<Block, ServerFnError> {
|
||||
use indexer_service_rpc::RpcClient as _;
|
||||
let client = expect_context::<IndexerRpcClient>();
|
||||
client
|
||||
.get_block_by_id(block_id)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
|
||||
.and_then(|opt| opt.ok_or_else(|| ServerFnError::ServerError("Block not found".to_owned())))
|
||||
}
|
||||
|
||||
/// Get latest block ID
|
||||
#[server]
|
||||
pub async fn get_latest_block_id() -> Result<BlockId, ServerFnError> {
|
||||
use indexer_service_rpc::RpcClient as _;
|
||||
let client = expect_context::<IndexerRpcClient>();
|
||||
client
|
||||
.get_last_finalized_block_id()
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
|
||||
}
|
||||
|
||||
/// Get block by hash
|
||||
#[server]
|
||||
pub async fn get_block_by_hash(block_hash: HashType) -> Result<Block, ServerFnError> {
|
||||
use indexer_service_rpc::RpcClient as _;
|
||||
let client = expect_context::<IndexerRpcClient>();
|
||||
client
|
||||
.get_block_by_hash(block_hash)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
|
||||
.and_then(|opt| opt.ok_or_else(|| ServerFnError::ServerError("Block not found".to_owned())))
|
||||
}
|
||||
|
||||
/// Get transaction by hash
|
||||
#[server]
|
||||
pub async fn get_transaction(tx_hash: HashType) -> Result<Transaction, ServerFnError> {
|
||||
use indexer_service_rpc::RpcClient as _;
|
||||
let client = expect_context::<IndexerRpcClient>();
|
||||
client
|
||||
.get_transaction(tx_hash)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
|
||||
.and_then(|opt| {
|
||||
opt.ok_or_else(|| ServerFnError::ServerError("Transaction not found".to_owned()))
|
||||
})
|
||||
}
|
||||
|
||||
/// Get blocks with pagination
|
||||
#[server]
|
||||
pub async fn get_blocks(before: Option<BlockId>, limit: u64) -> Result<Vec<Block>, ServerFnError> {
|
||||
use indexer_service_rpc::RpcClient as _;
|
||||
let client = expect_context::<IndexerRpcClient>();
|
||||
client
|
||||
.get_blocks(before, limit)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
|
||||
}
|
||||
|
||||
/// Get transactions by account
|
||||
#[server]
|
||||
pub async fn get_transactions_by_account(
|
||||
account_id: AccountId,
|
||||
offset: u64,
|
||||
limit: u64,
|
||||
) -> Result<Vec<Transaction>, ServerFnError> {
|
||||
use indexer_service_rpc::RpcClient as _;
|
||||
let client = expect_context::<IndexerRpcClient>();
|
||||
client
|
||||
.get_transactions_by_account(account_id, offset, limit)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
|
||||
}
|
||||
|
||||
/// Create the RPC client for the indexer service (server-side only).
|
||||
#[cfg(feature = "ssr")]
|
||||
pub fn create_indexer_rpc_client(url: &url::Url) -> Result<IndexerRpcClient, String> {
|
||||
use jsonrpsee::http_client::HttpClientBuilder;
|
||||
use log::info;
|
||||
|
||||
info!("Connecting to Indexer RPC on URL: {url}");
|
||||
|
||||
HttpClientBuilder::default()
|
||||
.build(url.as_str())
|
||||
.map_err(|e| format!("Failed to create RPC client: {e}"))
|
||||
}
|
||||
50
explorer_service/src/components/account_preview.rs
Normal file
50
explorer_service/src/components/account_preview.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use indexer_service_protocol::{Account, AccountId};
|
||||
use leptos::prelude::*;
|
||||
use leptos_router::components::A;
|
||||
|
||||
/// Account preview component
|
||||
#[component]
|
||||
pub fn AccountPreview(account_id: AccountId, account: Account) -> impl IntoView {
|
||||
let account_id_str = account_id.to_string();
|
||||
|
||||
view! {
|
||||
<div class="account-preview">
|
||||
<A href=format!("/account/{}", account_id_str) attr:class="account-preview-link">
|
||||
<div class="account-preview-header">
|
||||
<div class="account-id">
|
||||
<span class="label">"Account "</span>
|
||||
<span class="value hash">{account_id_str.clone()}</span>
|
||||
</div>
|
||||
</div>
|
||||
{move || {
|
||||
let Account { program_owner, balance, data, nonce } = &account;
|
||||
let program_id = program_owner.to_string();
|
||||
view! {
|
||||
<div class="account-preview-body">
|
||||
<div class="account-field">
|
||||
<span class="field-label">"Balance: "</span>
|
||||
<span class="field-value">{balance.to_string()}</span>
|
||||
</div>
|
||||
<div class="account-field">
|
||||
<span class="field-label">"Program: "</span>
|
||||
<span class="field-value hash">{program_id}</span>
|
||||
</div>
|
||||
<div class="account-field">
|
||||
<span class="field-label">"Nonce: "</span>
|
||||
<span class="field-value">{nonce.to_string()}</span>
|
||||
</div>
|
||||
<div class="account-field">
|
||||
<span class="field-label">"Data: "</span>
|
||||
<span class="field-value">
|
||||
{format!("{} bytes", data.0.len())}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
.into_any()
|
||||
}}
|
||||
|
||||
</A>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user