mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-17 21:08:33 +00:00
Compare commits
996 Commits
ximinez/fi
...
timothyban
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87de33a86f | ||
|
|
f81c79925d | ||
|
|
567266896a | ||
|
|
68339d8b92 | ||
|
|
cbf86602c8 | ||
|
|
cd8d68d4ae | ||
|
|
824997df60 | ||
|
|
becfa96f64 | ||
|
|
2841b795f6 | ||
|
|
ba968988fe | ||
|
|
d6757ce1a5 | ||
|
|
b68a2276f5 | ||
|
|
4eac5dbd3b | ||
|
|
97b272be23 | ||
|
|
09c4e35c51 | ||
|
|
9403736199 | ||
|
|
646c7ed494 | ||
|
|
028783661d | ||
|
|
e1b1a010f5 | ||
|
|
2d7e92ffc5 | ||
|
|
c9b365315f | ||
|
|
c1133a7443 | ||
|
|
21890d9daf | ||
|
|
dd09ec2c2f | ||
|
|
060957ed39 | ||
|
|
a4d1bd1a55 | ||
|
|
1381483c7a | ||
|
|
4aa695f7d9 | ||
|
|
c9f7e39822 | ||
|
|
9867cc295e | ||
|
|
0ba8b306e3 | ||
|
|
daecad43c9 | ||
|
|
e9074726cd | ||
|
|
3e4bdf2782 | ||
|
|
e3c8996e44 | ||
|
|
4208f14a91 | ||
|
|
d5bfe94f15 | ||
|
|
2ad4def35f | ||
|
|
f7f50caa6e | ||
|
|
3e54e7d00b | ||
|
|
827b50f169 | ||
|
|
58a59c37ed | ||
|
|
986065c16f | ||
|
|
37cb4cdbe3 | ||
|
|
491b930ebb | ||
|
|
2ddf6ee148 | ||
|
|
8ce4f71427 | ||
|
|
6e1eb88e6e | ||
|
|
422b8d0a43 | ||
|
|
49cdc105de | ||
|
|
a1ea8d3a6e | ||
|
|
636d2d4851 | ||
|
|
7d7275847d | ||
|
|
f0fd6ad85e | ||
|
|
5d8fd9824e | ||
|
|
346ea40f69 | ||
|
|
c061cdb49e | ||
|
|
5f41471b7d | ||
|
|
2e78c260a9 | ||
|
|
05a81b3974 | ||
|
|
8809bdf3f0 | ||
|
|
ae87e88690 | ||
|
|
deaf596494 | ||
|
|
279ef458c8 | ||
|
|
f9527b90da | ||
|
|
7863ac8cf6 | ||
|
|
0ffeb86491 | ||
|
|
549d0f5757 | ||
|
|
fe38635dd0 | ||
|
|
b3b38e4416 | ||
|
|
ccd5dc5e06 | ||
|
|
b2453b626e | ||
|
|
de6e5d3a94 | ||
|
|
fac20a06f3 | ||
|
|
74ec538296 | ||
|
|
b050dbba83 | ||
|
|
86c781f945 | ||
|
|
05a1497ebc | ||
|
|
5e48fe0b33 | ||
|
|
5973d60665 | ||
|
|
9a7c5ea593 | ||
|
|
7281e0606a | ||
|
|
71f5555873 | ||
|
|
3967ed6d54 | ||
|
|
e0151229b6 | ||
|
|
2787af8cd0 | ||
|
|
abc888707a | ||
|
|
e29e24dd50 | ||
|
|
b6a899583b | ||
|
|
37128fb8bd | ||
|
|
dc3bd9cf00 | ||
|
|
1e8b136bfb | ||
|
|
3c47af779c | ||
|
|
76aa7b1910 | ||
|
|
222ca8f3ce | ||
|
|
c28d389e0e | ||
|
|
d83a84510e | ||
|
|
f8fba079fe | ||
|
|
42502e4263 | ||
|
|
36c165f74d | ||
|
|
f7ea645bf4 | ||
|
|
50527485d3 | ||
|
|
072c7cb501 | ||
|
|
8320dd4982 | ||
|
|
421af6db79 | ||
|
|
7e3ac18345 | ||
|
|
e850e10651 | ||
|
|
6b8c28fddd | ||
|
|
d1cf8c2214 | ||
|
|
20750084ab | ||
|
|
e101ef7923 | ||
|
|
3f66957fd2 | ||
|
|
fee4bfc22e | ||
|
|
b6cb3c9591 | ||
|
|
5e3d20b3ed | ||
|
|
ef7f6025e4 | ||
|
|
b268a25f24 | ||
|
|
9e2aaf6f60 | ||
|
|
ec042fefee | ||
|
|
be2fbd4bf7 | ||
|
|
a8316180e4 | ||
|
|
71d0f26ed5 | ||
|
|
45e4b8899d | ||
|
|
433955f022 | ||
|
|
3d14bce9d8 | ||
|
|
03eabd537e | ||
|
|
c5dc408596 | ||
|
|
473fe44a85 | ||
|
|
cabbb15c6c | ||
|
|
31b2fa5b37 | ||
|
|
135e395e8d | ||
|
|
17a00ed449 | ||
|
|
0fdaf69e2c | ||
|
|
49049f9592 | ||
|
|
7898a5f402 | ||
|
|
9d41b1bd1c | ||
|
|
f137d71510 | ||
|
|
8bc6e81c5f | ||
|
|
a0794738a6 | ||
|
|
764cbe7c29 | ||
|
|
520650081b | ||
|
|
a097ccebae | ||
|
|
31d8267c2e | ||
|
|
bb90d20511 | ||
|
|
b57ead1a8a | ||
|
|
27859c1f6f | ||
|
|
e4baece501 | ||
|
|
86c09d8acc | ||
|
|
21f1a3e1f7 | ||
|
|
fe4ccdf750 | ||
|
|
57ae858f14 | ||
|
|
e3ceab3f49 | ||
|
|
4300c5d7d6 | ||
|
|
cc0a6ef77f | ||
|
|
804b7d2dcd | ||
|
|
046d4dd4af | ||
|
|
ddaa958754 | ||
|
|
3530a869cf | ||
|
|
9f5e08de66 | ||
|
|
d27beef500 | ||
|
|
85512541ad | ||
|
|
3ab5288ef2 | ||
|
|
d0dbf9163c | ||
|
|
a1478fac39 | ||
|
|
cc76708563 | ||
|
|
422e5245e4 | ||
|
|
2851ff46ab | ||
|
|
e3ba569187 | ||
|
|
000fcf88bd | ||
|
|
36e6dfaf62 | ||
|
|
0c759f6cbd | ||
|
|
35fab72e97 | ||
|
|
ee7ac58ffb | ||
|
|
959a186a0f | ||
|
|
3b1c9e4320 | ||
|
|
a3a2c85c41 | ||
|
|
b1ac891210 | ||
|
|
82876ca47c | ||
|
|
1761d83b8a | ||
|
|
579c44bb22 | ||
|
|
17d7bceadd | ||
|
|
da57183e0c | ||
|
|
f370289733 | ||
|
|
d1dc7a6ccf | ||
|
|
5639863715 | ||
|
|
1be4868875 | ||
|
|
368ff1afce | ||
|
|
3adf2d40b5 | ||
|
|
a6983f8bf3 | ||
|
|
4113b105a5 | ||
|
|
7442ff2dec | ||
|
|
666e77b22c | ||
|
|
8c12de6c56 | ||
|
|
b21fd86f6e | ||
|
|
ec64f23736 | ||
|
|
dc0326a2a0 | ||
|
|
80d8e827d7 | ||
|
|
b84d25fbed | ||
|
|
fda9c6995e | ||
|
|
c6c68090f2 | ||
|
|
f5f47f1cf5 | ||
|
|
ca39bff3c8 | ||
|
|
dd0edc19a0 | ||
|
|
820ca5b332 | ||
|
|
1b226c8b2e | ||
|
|
ca6121c5b3 | ||
|
|
6c32ca3538 | ||
|
|
7122bb8a43 | ||
|
|
c5524e4881 | ||
|
|
37e2f23ee6 | ||
|
|
c49789086a | ||
|
|
5337d028a2 | ||
|
|
e863db5061 | ||
|
|
43d842926a | ||
|
|
2adffaef72 | ||
|
|
bd87edfc75 | ||
|
|
d34aa37b3c | ||
|
|
a0074f83d3 | ||
|
|
028ccea7a1 | ||
|
|
df85d43d8a | ||
|
|
d469fc2cdf | ||
|
|
362ea7a5d1 | ||
|
|
8e9b1791c5 | ||
|
|
1b4fede15b | ||
|
|
946827b9bd | ||
|
|
91360c5126 | ||
|
|
af36890c11 | ||
|
|
1281c7a222 | ||
|
|
153b7839a7 | ||
|
|
2605b4a78b | ||
|
|
00488bf0b5 | ||
|
|
ecbfb8ea06 | ||
|
|
e91a30d004 | ||
|
|
694fbb7ce3 | ||
|
|
b67a896bff | ||
|
|
9288432da5 | ||
|
|
952450255f | ||
|
|
26cc683ec1 | ||
|
|
6ca2fb84d4 | ||
|
|
00dd93e77d | ||
|
|
4526a97c54 | ||
|
|
ecc2f07ea3 | ||
|
|
c5d25e3055 | ||
|
|
0749043d09 | ||
|
|
95526371b0 | ||
|
|
97f32869df | ||
|
|
454c651c44 | ||
|
|
91a23fc92c | ||
|
|
7d52867e3c | ||
|
|
a3147740f2 | ||
|
|
d43e5acaa7 | ||
|
|
c74724a719 | ||
|
|
0a572833ea | ||
|
|
98cf3a0532 | ||
|
|
98abdef208 | ||
|
|
7e7014c7ca | ||
|
|
d6a66d7249 | ||
|
|
6eab6c7c28 | ||
|
|
ca560ed6b0 | ||
|
|
58e47b0101 | ||
|
|
5f6f367b23 | ||
|
|
f52eb08d8a | ||
|
|
2821cc3e8e | ||
|
|
c1008c473c | ||
|
|
50623665c7 | ||
|
|
78c8128c98 | ||
|
|
c5605b6bcd | ||
|
|
639943123c | ||
|
|
a321a5dbfb | ||
|
|
3e113db4f5 | ||
|
|
9c423d2743 | ||
|
|
e60029d5a0 | ||
|
|
13196b839e | ||
|
|
17fb37871c | ||
|
|
3caaecff07 | ||
|
|
909cc5bba9 | ||
|
|
6f5de9067a | ||
|
|
60291c3ed6 | ||
|
|
229377abd9 | ||
|
|
4173f7e499 | ||
|
|
e23f8e266a | ||
|
|
1d9485c926 | ||
|
|
930ba88921 | ||
|
|
c619ae0263 | ||
|
|
93db40a25e | ||
|
|
8cae773691 | ||
|
|
fe325ea96a | ||
|
|
881d040a22 | ||
|
|
ce5e724b93 | ||
|
|
1656a19fe6 | ||
|
|
3a2cf64a69 | ||
|
|
accd0cac6c | ||
|
|
9a6efde771 | ||
|
|
6abd492ebb | ||
|
|
8da36db515 | ||
|
|
9c292fbe4f | ||
|
|
b19c3c64f2 | ||
|
|
a0e1e578a0 | ||
|
|
07aa97fda4 | ||
|
|
4f8819565a | ||
|
|
6580b200db | ||
|
|
2967f1f0cc | ||
|
|
71e972cbed | ||
|
|
152406b698 | ||
|
|
a24caaa6ea | ||
|
|
b5a90e76ee | ||
|
|
07b9c59b89 | ||
|
|
63d8772f69 | ||
|
|
5ae09518b8 | ||
|
|
3ab800bf47 | ||
|
|
fe1436fb3e | ||
|
|
abfa572370 | ||
|
|
d8e44687bf | ||
|
|
8a3363752f | ||
|
|
226ec160bb | ||
|
|
ef438aec26 | ||
|
|
94bccb3a5a | ||
|
|
0fb92c3194 | ||
|
|
798e889ec4 | ||
|
|
abf5511d07 | ||
|
|
a0e78b286f | ||
|
|
6bc7a9a858 | ||
|
|
ba7bf92725 | ||
|
|
8f0eff4dc4 | ||
|
|
9859e5ceda | ||
|
|
7f9ece3891 | ||
|
|
00a178fb92 | ||
|
|
cb425647a4 | ||
|
|
5bf24c4045 | ||
|
|
8d7524f03b | ||
|
|
41d6bb5f73 | ||
|
|
39c8c293b3 | ||
|
|
54cfdda00b | ||
|
|
15d08770bf | ||
|
|
a75488e5ff | ||
|
|
ffb2d37d17 | ||
|
|
6c02c45cbe | ||
|
|
c3ee602002 | ||
|
|
eb946d23af | ||
|
|
994967521e | ||
|
|
52d2008797 | ||
|
|
4d685d4bad | ||
|
|
3f159d624b | ||
|
|
f8f8a6e059 | ||
|
|
5b2fc952d3 | ||
|
|
e0de716ee6 | ||
|
|
06488c1318 | ||
|
|
8ecb77dcfb | ||
|
|
f2271ecc03 | ||
|
|
b8451ffa32 | ||
|
|
765babb20d | ||
|
|
97f35add2e | ||
|
|
8461ded0d8 | ||
|
|
0bf4739efa | ||
|
|
0df034a685 | ||
|
|
047a3f5cb8 | ||
|
|
e484a2902c | ||
|
|
d4c1359921 | ||
|
|
a88ba66fce | ||
|
|
4bd1d1ca2f | ||
|
|
0cce5a06d9 | ||
|
|
32a9cc4038 | ||
|
|
587505ef18 | ||
|
|
ecdd457f35 | ||
|
|
85e73cbd32 | ||
|
|
ef0b5dd1ac | ||
|
|
041869ff3d | ||
|
|
4eece4003d | ||
|
|
3ad6ce236e | ||
|
|
25afc04420 | ||
|
|
21cd615407 | ||
|
|
ed6f0f3019 | ||
|
|
b61b18a92c | ||
|
|
011235f9a7 | ||
|
|
3c0659d26b | ||
|
|
532506541f | ||
|
|
8a5eded4f1 | ||
|
|
6ddad54985 | ||
|
|
ccb9db0bc7 | ||
|
|
24b6dad287 | ||
|
|
be7532e3f7 | ||
|
|
14e7dea7ed | ||
|
|
9e28519e56 | ||
|
|
2cc8b87c87 | ||
|
|
d8d1ec46dc | ||
|
|
e290005db5 | ||
|
|
641ecb47bd | ||
|
|
94ed8e2f49 | ||
|
|
c4ce52c810 | ||
|
|
86832edc70 | ||
|
|
ff7fff2cf2 | ||
|
|
2db631c915 | ||
|
|
6c9c7f0555 | ||
|
|
75fac65e27 | ||
|
|
be3d98e8ff | ||
|
|
7915202cbc | ||
|
|
7ebd92ad3e | ||
|
|
f458c0f981 | ||
|
|
abcdeab11e | ||
|
|
55911cf526 | ||
|
|
9466ecb5c2 | ||
|
|
e0aa50c4be | ||
|
|
ecae6b6346 | ||
|
|
29120dfcbd | ||
|
|
20801d98ac | ||
|
|
b878818e80 | ||
|
|
6f4fe178dd | ||
|
|
6668b7e8d0 | ||
|
|
a5cc339d7b | ||
|
|
083986ab82 | ||
|
|
c145201538 | ||
|
|
fecfc0cf3f | ||
|
|
4fbbd6ff59 | ||
|
|
b7059deb9f | ||
|
|
ea0a6904f0 | ||
|
|
11fd30b02a | ||
|
|
39c95fee3b | ||
|
|
29d74142ae | ||
|
|
1b52b6519a | ||
|
|
db6fe77959 | ||
|
|
45e3d3f73e | ||
|
|
fcf376d3a9 | ||
|
|
ab97f4be20 | ||
|
|
9afa1cf4d1 | ||
|
|
7908aec2ec | ||
|
|
4c0180b3db | ||
|
|
be58ec0cd5 | ||
|
|
4acccfeda8 | ||
|
|
b89d75a2d5 | ||
|
|
4073838f66 | ||
|
|
1653f0c80f | ||
|
|
e227e2ce8b | ||
|
|
c50edf507c | ||
|
|
40cdf49d15 | ||
|
|
9cd531659a | ||
|
|
38c54c3f36 | ||
|
|
383a2f8819 | ||
|
|
e64703b151 | ||
|
|
95e1ffea6e | ||
|
|
c91640e3ac | ||
|
|
0ceee3a013 | ||
|
|
974b17f427 | ||
|
|
91de196312 | ||
|
|
ac71553abe | ||
|
|
9a8ec56d42 | ||
|
|
7a3bd8ace2 | ||
|
|
012f894b0a | ||
|
|
cb6dad0bc4 | ||
|
|
d54ca4cd3b | ||
|
|
74cfb3586a | ||
|
|
0072ced94c | ||
|
|
5a0269aca7 | ||
|
|
0774ca32a9 | ||
|
|
8feb75011e | ||
|
|
088c5b3b03 | ||
|
|
1c4abe8c5e | ||
|
|
12ed506565 | ||
|
|
3122de86bf | ||
|
|
7edf39e622 | ||
|
|
91a5747bf9 | ||
|
|
bc892f0bd3 | ||
|
|
ba01b05f33 | ||
|
|
c414e1877d | ||
|
|
f56b79f00d | ||
|
|
4918f3bb20 | ||
|
|
ee0a3dfad7 | ||
|
|
8950530b04 | ||
|
|
5cbebf3c58 | ||
|
|
c634c75aee | ||
|
|
9801c23bad | ||
|
|
f6472347e9 | ||
|
|
042c9660cd | ||
|
|
60b49f3fe0 | ||
|
|
4c869ee16c | ||
|
|
a41889df31 | ||
|
|
aeee77f6ec | ||
|
|
35583fcf97 | ||
|
|
b2f45813e0 | ||
|
|
982bf36dd8 | ||
|
|
faca302adf | ||
|
|
6b3eaf091b | ||
|
|
68a765d929 | ||
|
|
033dca2f0e | ||
|
|
bf65e5fa7b | ||
|
|
90b2a68da8 | ||
|
|
06a9b1b617 | ||
|
|
7d3611df2a | ||
|
|
7877ee42a0 | ||
|
|
1dcaf4b54e | ||
|
|
5ab95748d4 | ||
|
|
4a9ee54c88 | ||
|
|
981c256933 | ||
|
|
a5af6b4e4a | ||
|
|
e1dae5f775 | ||
|
|
6c793edf72 | ||
|
|
d60955e2fc | ||
|
|
846369cbe7 | ||
|
|
5ce0b1c2c7 | ||
|
|
701311f27e | ||
|
|
d569f7db9e | ||
|
|
b1a670c46e | ||
|
|
a12ab0496c | ||
|
|
18e311e1e2 | ||
|
|
69b70d7a0d | ||
|
|
b42cde3e85 | ||
|
|
cd38c0e800 | ||
|
|
433e5f6896 | ||
|
|
781ab723af | ||
|
|
a24e543af3 | ||
|
|
a0fd1cce54 | ||
|
|
4277c806e4 | ||
|
|
2e48bb4b7f | ||
|
|
9e427ae3ba | ||
|
|
8022fc33cf | ||
|
|
465fa8ec4b | ||
|
|
b5990be624 | ||
|
|
9767d86de4 | ||
|
|
ca2d999618 | ||
|
|
4a01fe5aa6 | ||
|
|
f60a9f1c64 | ||
|
|
5ee903befc | ||
|
|
68f0dcae53 | ||
|
|
d582ae7990 | ||
|
|
0dbe51c740 | ||
|
|
63fff4b518 | ||
|
|
d3550fac60 | ||
|
|
10ef15fff1 | ||
|
|
2307cfdc5a | ||
|
|
3b328cfc45 | ||
|
|
e20b2430ba | ||
|
|
99072fbddf | ||
|
|
059cefa35a | ||
|
|
a2205190a6 | ||
|
|
4c657bc92e | ||
|
|
ac481a0bd7 | ||
|
|
7eacd9d735 | ||
|
|
1f9675d22f | ||
|
|
029fa435ee | ||
|
|
c92ca426bf | ||
|
|
ff896b226a | ||
|
|
3c53c94e7a | ||
|
|
d85bf722ea | ||
|
|
b664989cfb | ||
|
|
e77934302a | ||
|
|
ef7aeca6bf | ||
|
|
eec1d29b92 | ||
|
|
971ba2281e | ||
|
|
90357eeae1 | ||
|
|
1dce793acc | ||
|
|
597202a6f0 | ||
|
|
1600b3e7f3 | ||
|
|
5be406e2df | ||
|
|
bc483b2a1d | ||
|
|
175259df28 | ||
|
|
8bb8c3b24b | ||
|
|
abb2ef3bec | ||
|
|
d009ef221f | ||
|
|
5fdedd7e99 | ||
|
|
ffc55797b2 | ||
|
|
ecee732187 | ||
|
|
ce2586c039 | ||
|
|
36fb84926e | ||
|
|
56decaf852 | ||
|
|
d562129e7c | ||
|
|
1d9fe9dbaf | ||
|
|
8cc2169939 | ||
|
|
74beb5f80d | ||
|
|
a4dfdaf77a | ||
|
|
826f613ad8 | ||
|
|
1259c1d5ca | ||
|
|
4bae4d781f | ||
|
|
01a5e07830 | ||
|
|
34522bc668 | ||
|
|
d2641d85bd | ||
|
|
75f66bd9fe | ||
|
|
7cd71cb659 | ||
|
|
9917f96166 | ||
|
|
e1cc82587b | ||
|
|
2cc9439fde | ||
|
|
52af9582e2 | ||
|
|
46e88dc732 | ||
|
|
bc24f2e211 | ||
|
|
7a7c993b15 | ||
|
|
9733ca8f91 | ||
|
|
18d5e3e226 | ||
|
|
b30b4e1d65 | ||
|
|
d435893602 | ||
|
|
00b0cf50f6 | ||
|
|
fe2e5ce649 | ||
|
|
6afa51142d | ||
|
|
22054a573d | ||
|
|
7ef256499c | ||
|
|
1338062be7 | ||
|
|
4fc1778ec8 | ||
|
|
65322d9e78 | ||
|
|
0acfa1c4f2 | ||
|
|
309fdfc3f2 | ||
|
|
63bd5fc4ee | ||
|
|
82e7f7eeec | ||
|
|
c5598a4284 | ||
|
|
63d09f9c51 | ||
|
|
0deb6bcadf | ||
|
|
d606f88e84 | ||
|
|
9b013b559b | ||
|
|
1d4a3c00b8 | ||
|
|
4b34102e8e | ||
|
|
d006433579 | ||
|
|
a7ab8ee923 | ||
|
|
e0073a4402 | ||
|
|
2930ef217f | ||
|
|
9dbb301699 | ||
|
|
531e8b6ebd | ||
|
|
90397e1a52 | ||
|
|
888ca2e6d9 | ||
|
|
f8a149c675 | ||
|
|
51458a92e2 | ||
|
|
b6514b680f | ||
|
|
913e4b919e | ||
|
|
196e6a1b27 | ||
|
|
27468ddbcf | ||
|
|
bce5d91e45 | ||
|
|
780380da7e | ||
|
|
654338fa66 | ||
|
|
fdfdf4fceb | ||
|
|
a76bd834b6 | ||
|
|
864d88a3c2 | ||
|
|
9c25d18851 | ||
|
|
3a825a41e1 | ||
|
|
a9ebf786c6 | ||
|
|
5afe8cc321 | ||
|
|
bc5ec3c962 | ||
|
|
7004d216ed | ||
|
|
1775251e90 | ||
|
|
61bcb7621f | ||
|
|
a3f71b1774 | ||
|
|
4df7d1a4bb | ||
|
|
50a74b899d | ||
|
|
00eeab6d44 | ||
|
|
125df7a425 | ||
|
|
b08bcf5d21 | ||
|
|
dc413aef0c | ||
|
|
77dfd56ace | ||
|
|
953b9a3500 | ||
|
|
1d9ec84350 | ||
|
|
0392846a17 | ||
|
|
5148098079 | ||
|
|
1b4a564369 | ||
|
|
fd524c4be9 | ||
|
|
495dda7f58 | ||
|
|
9c3c0280b1 | ||
|
|
f73d8a6cf2 | ||
|
|
4fe508cb92 | ||
|
|
6728ab52b7 | ||
|
|
446ad36cbb | ||
|
|
eb2d44d442 | ||
|
|
77673663ca | ||
|
|
c1381f8ddd | ||
|
|
bd16f7989d | ||
|
|
fd14054f17 | ||
|
|
65f9cf80c0 | ||
|
|
4318b2ebf7 | ||
|
|
e6ee492822 | ||
|
|
d4510147d1 | ||
|
|
a9a94fbf1a | ||
|
|
719ba392db | ||
|
|
cd46b5d999 | ||
|
|
de55a5ebfc | ||
|
|
2ec4a1114e | ||
|
|
f01ac563a9 | ||
|
|
13707dda05 | ||
|
|
ba03a8a9d2 | ||
|
|
7c8279ec83 | ||
|
|
a1844086d7 | ||
|
|
f625fb993a | ||
|
|
0418ffb26a | ||
|
|
b2627039f6 | ||
|
|
8f97ec3bde | ||
|
|
e85e7b1b1a | ||
|
|
ac173b6827 | ||
|
|
69c61b2235 | ||
|
|
6ae0e860ff | ||
|
|
c077e7f073 | ||
|
|
803a344c65 | ||
|
|
4eb34f381a | ||
|
|
72fffb6e51 | ||
|
|
f7ee580f01 | ||
|
|
122d405750 | ||
|
|
c1c1b4ea67 | ||
|
|
977caea0a5 | ||
|
|
d7ed6d6512 | ||
|
|
f1f2e2629f | ||
|
|
917c610f96 | ||
|
|
317e533d81 | ||
|
|
4160677878 | ||
|
|
430696682d | ||
|
|
4621e4eda3 | ||
|
|
df98db1452 | ||
|
|
981ac7abf4 | ||
|
|
57d2a91ad5 | ||
|
|
673476ef1b | ||
|
|
8bc6f9cd70 | ||
|
|
ba5debfecd | ||
|
|
f4a27c9b6d | ||
|
|
fd1cb318e3 | ||
|
|
94b35a234e | ||
|
|
b5d0078927 | ||
|
|
43c80edaf4 | ||
|
|
8c3544a58c | ||
|
|
ed5139d4e3 | ||
|
|
42494dd4cf | ||
|
|
ce84cc8b44 | ||
|
|
9a9a7aab01 | ||
|
|
209a1a6ffa | ||
|
|
9538e9b34c | ||
|
|
384b3608d7 | ||
|
|
fc35a9f9c8 | ||
|
|
c5e50aa221 | ||
|
|
074b1f00d5 | ||
|
|
7a9d245950 | ||
|
|
1809fe07f2 | ||
|
|
409c67494a | ||
|
|
fb97f7b596 | ||
|
|
c626b6403a | ||
|
|
81cbc91927 | ||
|
|
845c503ea6 | ||
|
|
e1513570df | ||
|
|
1c812a6c4d | ||
|
|
0724927799 | ||
|
|
ff39fa59d9 | ||
|
|
d83ec96848 | ||
|
|
f0d0739528 | ||
|
|
375dd50b35 | ||
|
|
419d53ec4c | ||
|
|
d4d70d5675 | ||
|
|
6ab15f8377 | ||
|
|
91f3d51f3d | ||
|
|
9ed60b45f8 | ||
|
|
d5c53dcfd2 | ||
|
|
8015088340 | ||
|
|
103379836a | ||
|
|
e94321fb41 | ||
|
|
bbc28b3b1c | ||
|
|
843e981c8a | ||
|
|
5aab274b7a | ||
|
|
2c30e41191 | ||
|
|
8ea5106b0b | ||
|
|
f57f67a8ae | ||
|
|
61b2fe4f64 | ||
|
|
7ee964f514 | ||
|
|
397bc8781e | ||
|
|
a98269f049 | ||
|
|
8bb8c2e38b | ||
|
|
b66bc47ca9 | ||
|
|
0e9c7458bb | ||
|
|
36ecd3b52b | ||
|
|
1d89940653 | ||
|
|
1a1a6806ec | ||
|
|
1977df9c2e | ||
|
|
6ffbef09c2 | ||
|
|
e05f907788 | ||
|
|
9d1f51b01a | ||
|
|
6c95548df5 | ||
|
|
69ab39d658 | ||
|
|
b9eb66eecc | ||
|
|
e916416642 | ||
|
|
827ecc6e3a | ||
|
|
6a54ed7f14 | ||
|
|
881087dd3d | ||
|
|
90e0bbd0fc | ||
|
|
b57df290de | ||
|
|
8a403f1241 | ||
|
|
1e0741690d | ||
|
|
6d2640871d | ||
|
|
c5d178f152 | ||
|
|
5a17940e2a | ||
|
|
27ac30208d | ||
|
|
c145598ff9 | ||
|
|
50e5608d86 | ||
|
|
abfcc4ef67 | ||
|
|
e40a4df777 | ||
|
|
dba187f8c5 | ||
|
|
7a7b96107c | ||
|
|
8f2f8d53b4 | ||
|
|
49acc61961 | ||
|
|
500bb68831 | ||
|
|
95d78a8600 | ||
|
|
53eb0f60bc | ||
|
|
41205ae928 | ||
|
|
c33b0ae463 | ||
|
|
16087c9680 | ||
|
|
56bc6d58f6 | ||
|
|
ef5d335e09 | ||
|
|
25c3060fef | ||
|
|
ce9f0b38a4 | ||
|
|
35f7cbf772 | ||
|
|
def7758a23 | ||
|
|
58e5b4ad25 | ||
|
|
578413859c | ||
|
|
0db564d261 | ||
|
|
427b7ea104 | ||
|
|
fa8aa49376 | ||
|
|
3195eb16b2 | ||
|
|
7bf6878b4b | ||
|
|
a891b49c67 | ||
|
|
eed280d169 | ||
|
|
0bc1a115ff | ||
|
|
334bcfa5ef | ||
|
|
106dea4559 | ||
|
|
3ffdcf8114 | ||
|
|
4021a7eb28 | ||
|
|
0690fda0f1 | ||
|
|
d0cc48c6d3 | ||
|
|
d66e3c949e | ||
|
|
7e2e10f02c | ||
|
|
aebec5378c | ||
|
|
0c65a386b5 | ||
|
|
22ca691e75 | ||
|
|
fc6ff69752 | ||
|
|
079e251aca | ||
|
|
67e2c1b563 | ||
|
|
29f5430881 | ||
|
|
b6bd268be2 | ||
|
|
209ee25c32 | ||
|
|
101f285bcd | ||
|
|
566b85b3d6 | ||
|
|
af6beb1d7c | ||
|
|
7d22fe804d | ||
|
|
ce19c13059 | ||
|
|
286dc6322b | ||
|
|
c9346cd40d | ||
|
|
9cfb7ac340 | ||
|
|
3a0e9aab4f | ||
|
|
43caa1ef29 | ||
|
|
1c5683ec78 | ||
|
|
9bee155d59 | ||
|
|
f34b05f4de | ||
|
|
97ce25f4ce | ||
|
|
3b6cd22e32 | ||
|
|
c9c35780d2 | ||
|
|
17f401f374 | ||
|
|
c6c54b3282 | ||
|
|
91455b6860 | ||
|
|
9e14c14a26 | ||
|
|
f16f243c22 | ||
|
|
fe601308e7 | ||
|
|
c507880d8f | ||
|
|
3f8328bbf8 | ||
|
|
e41f6a71b7 | ||
|
|
51f1be7f5b | ||
|
|
c10a5f9ef6 | ||
|
|
3c141de695 | ||
|
|
db263b696c | ||
|
|
f57b855d74 | ||
|
|
da2b9455f2 | ||
|
|
86525d8583 | ||
|
|
c41e52f57a | ||
|
|
55772a0d07 | ||
|
|
965a9e89ac | ||
|
|
51ee06429b | ||
|
|
cb622488c0 | ||
|
|
32f971fec6 | ||
|
|
ca85d09f02 | ||
|
|
8dea76baa4 | ||
|
|
299fbe04c4 | ||
|
|
57fc1df7d7 | ||
|
|
8d266d3941 | ||
|
|
a865b4da1c | ||
|
|
e59f5f3b01 | ||
|
|
8729688feb | ||
|
|
c8b06e7de1 | ||
|
|
eaba76f9e6 | ||
|
|
cb702cc238 | ||
|
|
f1f798bb85 | ||
|
|
c3fd52c177 | ||
|
|
b69b4a0a4a | ||
|
|
50d6072a73 | ||
|
|
d24cd50e61 | ||
|
|
85bff20ae5 | ||
|
|
737fab5471 | ||
|
|
e6592e93a9 | ||
|
|
5a6c4e8ae0 | ||
|
|
9f5875158c | ||
|
|
c3dc33c861 | ||
|
|
7420f47658 | ||
|
|
6be8f2124c | ||
|
|
edfed06001 | ||
|
|
1c646dba91 | ||
|
|
6781068058 | ||
|
|
cfe57c1dfe | ||
|
|
c34d09a971 | ||
|
|
ebd90c4742 | ||
|
|
ba52d34828 | ||
|
|
2f869b3cfc | ||
|
|
ffa21c27a7 | ||
|
|
1b6312afb3 | ||
|
|
bf32dc2e72 | ||
|
|
a15d65f7a2 | ||
|
|
2de8488855 | ||
|
|
129aa4bfaa | ||
|
|
b1d70db63b | ||
|
|
f03c3aafe4 | ||
|
|
51a9f106d1 | ||
|
|
bfc048e3fe | ||
|
|
83418644f7 | ||
|
|
dbc9dd5bfc | ||
|
|
5c480cf883 | ||
|
|
45ab15d4b5 | ||
|
|
adc64e7866 | ||
|
|
4d4a1cfe82 | ||
|
|
f2c7da3705 | ||
|
|
3ab0a82cd3 | ||
|
|
a46d772147 | ||
|
|
f3c50318e8 | ||
|
|
e7aa924c0e | ||
|
|
5266f04970 | ||
|
|
db957cf191 | ||
|
|
8ac514363d | ||
|
|
c2ea68cca4 | ||
|
|
3d86881ce7 | ||
|
|
697d1470f4 | ||
|
|
0b5f8f4051 | ||
|
|
0fed78fbcc | ||
|
|
8c38ef726b | ||
|
|
2399d90334 | ||
|
|
6367d68d1e | ||
|
|
155a84c8a3 | ||
|
|
10558c9eff | ||
|
|
dd30d811e6 | ||
|
|
293d8e4ddb | ||
|
|
77875c9133 | ||
|
|
647b47567e | ||
|
|
b0a1ad3b06 | ||
|
|
1d141bf2e8 | ||
|
|
0d0e279ae2 | ||
|
|
5dc0cee28a | ||
|
|
c15947da56 | ||
|
|
9bc04244e7 | ||
|
|
38c7a27010 | ||
|
|
58741d2791 | ||
|
|
8426470506 | ||
|
|
ccc3280b1a | ||
|
|
2847075705 | ||
|
|
3108ca0549 | ||
|
|
3b849ff497 | ||
|
|
66776b6a85 | ||
|
|
c8c241b50d | ||
|
|
44cb588371 | ||
|
|
6f91b8f8d1 | ||
|
|
3d93379132 | ||
|
|
84fd7d0126 | ||
|
|
7f52287aae | ||
|
|
98b8986868 | ||
|
|
250f2842ee | ||
|
|
9eca1a3a0c | ||
|
|
24b7a03224 | ||
|
|
9007097d24 | ||
|
|
bc445ec6a2 | ||
|
|
4fa0ae521e | ||
|
|
7bdf5fa8b8 | ||
|
|
65b0b976d9 | ||
|
|
a0d275feec | ||
|
|
ece3a8d7be | ||
|
|
463acf51b5 | ||
|
|
1cd16fab87 | ||
|
|
add55c4f33 | ||
|
|
51a9c0ff59 | ||
|
|
6e8a5f0f4e | ||
|
|
8a33702f26 | ||
|
|
a072d49802 | ||
|
|
a0aeeb8e07 | ||
|
|
383b225690 | ||
|
|
ace2247800 | ||
|
|
6a6fed5dce | ||
|
|
1f8aece8cd | ||
|
|
6c6f8cd4f9 | ||
|
|
fb1311e013 | ||
|
|
ce31acf030 | ||
|
|
31ad5ac63b | ||
|
|
1ede0bdec4 | ||
|
|
aef32ead2c | ||
|
|
5b43ec7f73 | ||
|
|
1e9ff88a00 | ||
|
|
bb9bb5f5c5 | ||
|
|
c533abd8b6 | ||
|
|
bb9bc764bc | ||
|
|
b4b53a6cb7 | ||
|
|
9c0204906c | ||
|
|
4670b373c1 | ||
|
|
f03b5883bd | ||
|
|
f8b2fe4dd5 | ||
|
|
be4a0c9c2b | ||
|
|
f37d52d8e9 | ||
|
|
177cdaf550 | ||
|
|
1573a443b7 | ||
|
|
911c0466c0 | ||
|
|
b6a95f9970 |
@@ -75,6 +75,8 @@ Checks: "-*,
|
||||
# readability-static-accessed-through-instance, # this check is probably unnecessary. It makes the code less readable
|
||||
# ---
|
||||
|
||||
FormatStyle: file
|
||||
|
||||
CheckOptions:
|
||||
bugprone-unsafe-functions.ReportMoreUnsafeFunctions: true
|
||||
bugprone-unused-return-value.CheckedReturnTypes: ::std::error_code;::std::error_condition;::std::errc
|
||||
@@ -83,6 +85,8 @@ CheckOptions:
|
||||
|
||||
readability-braces-around-statements.ShortStatementLines: 2
|
||||
readability-identifier-naming.MacroDefinitionCase: UPPER_CASE
|
||||
readability-identifier-naming.NamespaceCase: lower_case
|
||||
readability-identifier-naming.InlineNamespaceCase: lower_case
|
||||
readability-identifier-naming.ClassCase: CamelCase
|
||||
readability-identifier-naming.StructCase: CamelCase
|
||||
readability-identifier-naming.UnionCase: CamelCase
|
||||
|
||||
24
.codecov.yml
24
.codecov.yml
@@ -1,10 +1,32 @@
|
||||
codecov:
|
||||
require_ci_to_pass: true
|
||||
# The C++ and Rust uploads land minutes apart; without this gate Codecov
|
||||
# publishes a near-zero total from whichever one arrives first.
|
||||
notify:
|
||||
after_n_builds: 2
|
||||
wait_for_ci: true
|
||||
|
||||
comment:
|
||||
behavior: default
|
||||
layout: reach,diff,flags,tree,reach
|
||||
show_carryforward_flags: false
|
||||
show_carryforward_flags: true
|
||||
after_n_builds: 2
|
||||
|
||||
# C++ and Rust coverage upload from independent workflows under the `cpp` and
|
||||
# `rust` flags; carryforward keeps one language's total when only the other reran.
|
||||
flag_management:
|
||||
default_rules:
|
||||
carryforward: true
|
||||
individual_flags:
|
||||
- name: cpp
|
||||
carryforward: true
|
||||
paths:
|
||||
- include/
|
||||
- src/
|
||||
- name: rust
|
||||
carryforward: true
|
||||
paths:
|
||||
- crates/
|
||||
|
||||
coverage:
|
||||
range: "70..85"
|
||||
|
||||
@@ -7,6 +7,7 @@ ignorePaths:
|
||||
- cmake/**
|
||||
- LICENSE.md
|
||||
- .clang-tidy
|
||||
- nix/check-tools/*.txt # generated, and full of Nix store hashes
|
||||
language: en
|
||||
allowCompoundWords: true # TODO (#6334)
|
||||
ignoreRandomStrings: true
|
||||
@@ -30,7 +31,9 @@ ignoreRegExpList:
|
||||
- ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
- ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
|
||||
overrides:
|
||||
- filename: "**/*_test.cpp" # all test files
|
||||
- filename:
|
||||
- "**/*_test.cpp" # legacy boost.test files
|
||||
- "src/tests/**/*.cpp" # gtest test files
|
||||
ignoreRegExpList:
|
||||
- /"[^"]*"/g # double-quoted strings
|
||||
- /'[^']*'/g # single-quoted strings
|
||||
@@ -65,7 +68,9 @@ words:
|
||||
- Btrfs
|
||||
- Buildx
|
||||
- canonicality
|
||||
- cdylib
|
||||
- canonicalised
|
||||
- cctools
|
||||
- changespq
|
||||
- checkme
|
||||
- choco
|
||||
@@ -101,12 +106,14 @@ words:
|
||||
- deleteme
|
||||
- demultiplexer
|
||||
- deserializaton
|
||||
- desugars
|
||||
- desync
|
||||
- desynced
|
||||
- determ
|
||||
- disablerepo
|
||||
- distro
|
||||
- doxyfile
|
||||
- dsymutil
|
||||
- dxrpl
|
||||
- elgamal
|
||||
- enabled
|
||||
@@ -126,15 +133,19 @@ words:
|
||||
- gcov
|
||||
- gcovr
|
||||
- ghead
|
||||
- gmock
|
||||
- Gnutella
|
||||
- godexsoft
|
||||
- gpgcheck
|
||||
- gpgkey
|
||||
- Hinnant
|
||||
- hotwallet
|
||||
- hwaddress
|
||||
- hwrap
|
||||
- ifndef
|
||||
- impls
|
||||
- inequation
|
||||
- initialiser
|
||||
- insuf
|
||||
- insuff
|
||||
- invasively
|
||||
@@ -163,6 +174,8 @@ words:
|
||||
- llection
|
||||
- LOCALGOOD
|
||||
- logwstream
|
||||
- Lombrozo
|
||||
- lresolv
|
||||
- lseq
|
||||
- lsmf
|
||||
- ltype
|
||||
@@ -172,7 +185,6 @@ words:
|
||||
- MPTAMM
|
||||
- MPTDEX
|
||||
- Merkle
|
||||
- Metafuncton
|
||||
- misprediction
|
||||
- missingok
|
||||
- mptbalance
|
||||
@@ -200,6 +212,7 @@ words:
|
||||
- nftokens
|
||||
- nftpage
|
||||
- nikb
|
||||
- Nikolaos
|
||||
- nixfmt
|
||||
- nixos
|
||||
- nixpkgs
|
||||
@@ -216,6 +229,8 @@ words:
|
||||
- Nyffenegger
|
||||
- onlatest
|
||||
- ostr
|
||||
- otool
|
||||
- oxalica
|
||||
- pargs
|
||||
- partitioner
|
||||
- paychan
|
||||
@@ -240,15 +255,20 @@ words:
|
||||
- pyparsing
|
||||
- qalloc
|
||||
- qbsprofile
|
||||
- qself
|
||||
- queuable
|
||||
- Raphson
|
||||
- rcflags
|
||||
- replayer
|
||||
- repodata
|
||||
- repomd
|
||||
- rerandomize
|
||||
- rerandomization
|
||||
- rerandomized
|
||||
- rerandomizes
|
||||
- rerere
|
||||
- retargeted
|
||||
- retargets
|
||||
- retriable
|
||||
- RIPD
|
||||
- ripdtop
|
||||
@@ -262,6 +282,8 @@ words:
|
||||
- Rohrs
|
||||
- roundings
|
||||
- rustc
|
||||
- rustfmt
|
||||
- rustup
|
||||
- sahyadri
|
||||
- Satoshi
|
||||
- scons
|
||||
@@ -282,6 +304,7 @@ words:
|
||||
- sles
|
||||
- soci
|
||||
- socidb
|
||||
- Sonatype
|
||||
- sponsee
|
||||
- sponsees
|
||||
- SRPMS
|
||||
@@ -290,6 +313,7 @@ words:
|
||||
- STATSDCOLLECTOR
|
||||
- stissue
|
||||
- stnum
|
||||
- stnumber
|
||||
- stobj
|
||||
- stobject
|
||||
- stpath
|
||||
@@ -301,10 +325,12 @@ words:
|
||||
- summands
|
||||
- superpeer
|
||||
- superpeers
|
||||
- Swatinem
|
||||
- takergets
|
||||
- takerpays
|
||||
- ters
|
||||
- TMEndpointv2
|
||||
- toolchain
|
||||
- tparam
|
||||
- trixie
|
||||
- tx
|
||||
@@ -328,6 +354,7 @@ words:
|
||||
- unflatten
|
||||
- unfund
|
||||
- unimpair
|
||||
- unmetered
|
||||
- unroutable
|
||||
- unscalable
|
||||
- unserviced
|
||||
@@ -337,6 +364,7 @@ words:
|
||||
- unsquelch
|
||||
- unsquelched
|
||||
- unsquelching
|
||||
- unsuffixed
|
||||
- unvalidated
|
||||
- unveto
|
||||
- unvetoed
|
||||
@@ -347,6 +375,8 @@ words:
|
||||
- vfalco
|
||||
- vinnie
|
||||
- wasmi
|
||||
- wasmparser
|
||||
- Werror
|
||||
- wextra
|
||||
- wptr
|
||||
- writeme
|
||||
@@ -354,12 +384,17 @@ words:
|
||||
- wthread
|
||||
- xbridge
|
||||
- xchain
|
||||
- xcrun
|
||||
- xfloat
|
||||
- ximinez
|
||||
- XMACRO
|
||||
- xored
|
||||
- xrpkuwait
|
||||
- xrpl
|
||||
- xrpld
|
||||
- xrplf
|
||||
- xxhash
|
||||
- xxhasher
|
||||
- zstdio
|
||||
- CGNAT
|
||||
- ungated
|
||||
|
||||
9
.envrc
9
.envrc
@@ -1 +1,10 @@
|
||||
watch_file nix/*.nix
|
||||
|
||||
# Pinned Rust toolchain, read by nix/packages.nix via fromRustupToolchainFile.
|
||||
watch_file rust-toolchain.toml
|
||||
|
||||
# The dev shell derivation includes all of conan/ (see nix/devshell.nix), so any
|
||||
# change in there has to invalidate direnv's cached environment.
|
||||
watch_dir conan
|
||||
|
||||
use flake
|
||||
|
||||
@@ -11,6 +11,9 @@ endfunction()
|
||||
function(create_symbolic_link target link)
|
||||
endfunction()
|
||||
|
||||
function(xrpl_add_benchmark name)
|
||||
endfunction()
|
||||
|
||||
macro(exclude_from_default target_)
|
||||
endmacro()
|
||||
|
||||
|
||||
39
.github/actions/cargo-cache/action.yml
vendored
Normal file
39
.github/actions/cargo-cache/action.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Use cargo artifacts cache
|
||||
description: >
|
||||
Cache the cargo build artifacts with rust-cache. Never caches ~/.cargo/bin:
|
||||
when saving the cache, rust-cache deletes all binaries that were already
|
||||
present there, which on persistent self-hosted runners wipes the tools
|
||||
installed by prepare-runner. Harmless on ephemeral runners, but kept
|
||||
consistent everywhere.
|
||||
|
||||
inputs:
|
||||
workspaces:
|
||||
description: "Workspaces to cache, as 'workspace -> target' lines."
|
||||
required: false
|
||||
default: crates
|
||||
key:
|
||||
description: "Additional part of the cache key."
|
||||
required: false
|
||||
default: ""
|
||||
cache-directories:
|
||||
description: "Additional non-workspace directories to cache."
|
||||
required: false
|
||||
default: ""
|
||||
save-if:
|
||||
description: >
|
||||
Condition for saving the cache after the job. Defaults to save only from develop branch
|
||||
required: false
|
||||
default: ${{ github.ref == 'refs/heads/develop' }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: Use cargo artifacts cache
|
||||
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
cache-bin: "false"
|
||||
cache-directories: ${{ inputs.cache-directories }}
|
||||
key: ${{ inputs.key }}
|
||||
save-if: ${{ inputs.save-if }}
|
||||
workspaces: ${{ inputs.workspaces }}
|
||||
44
.github/actions/generate-version/action.yml
vendored
44
.github/actions/generate-version/action.yml
vendored
@@ -1,44 +0,0 @@
|
||||
name: Generate build version number
|
||||
description: "Generate build version number."
|
||||
|
||||
outputs:
|
||||
version:
|
||||
description: "The generated build version number."
|
||||
value: ${{ steps.version.outputs.version }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
# When a tag is pushed, the version is used as-is.
|
||||
- name: Generate version for tag event
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: ${{ github.ref_name }}
|
||||
run: echo "VERSION=${VERSION}" >>"${GITHUB_ENV}"
|
||||
|
||||
# When a tag is not pushed, then the version (e.g. 1.2.3-b0) is extracted
|
||||
# from the BuildInfo.cpp file and the shortened commit hash appended to it.
|
||||
# We use a plus sign instead of a hyphen because Conan recipe versions do
|
||||
# not support two hyphens.
|
||||
- name: Generate version for non-tag event
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'Extracting version from BuildInfo.cpp.'
|
||||
VERSION="$(cat src/libxrpl/protocol/BuildInfo.cpp | grep "versionString =" | awk -F '"' '{print $2}')"
|
||||
if [[ -z "${VERSION}" ]]; then
|
||||
echo 'Unable to extract version from BuildInfo.cpp.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo 'Appending shortened commit hash to version.'
|
||||
SHA='${{ github.sha }}'
|
||||
VERSION="${VERSION}+${SHA:0:7}"
|
||||
|
||||
echo "VERSION=${VERSION}" >>"${GITHUB_ENV}"
|
||||
|
||||
- name: Output version
|
||||
id: version
|
||||
shell: bash
|
||||
run: echo "version=${VERSION}" >>"${GITHUB_OUTPUT}"
|
||||
44
.github/actions/release-info/action.yml
vendored
Normal file
44
.github/actions/release-info/action.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
name: Release info
|
||||
description: "Derive the version, release channel and package release number for this build."
|
||||
|
||||
outputs:
|
||||
version:
|
||||
description: "The build version number."
|
||||
value: ${{ steps.version.outputs.version }}
|
||||
channel:
|
||||
description: "The release channel this build belongs to."
|
||||
value: ${{ steps.release_info.outputs.channel }}
|
||||
pkg_release:
|
||||
description: "The package release number: 1 for a tag, <run number>.<commit date>git<short commit hash> otherwise."
|
||||
value: ${{ steps.release_info.outputs.pkg_release }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
# A tag names its own version. Anything else takes it from BuildInfo.cpp and
|
||||
# appends the commit hash as build metadata, joined with a plus sign because a
|
||||
# Conan version cannot contain two hyphens.
|
||||
- name: Determine version
|
||||
id: version
|
||||
shell: bash
|
||||
env:
|
||||
IS_TAG: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
SHA: ${{ github.sha }}
|
||||
run: |
|
||||
if [[ "${IS_TAG}" == "true" ]]; then
|
||||
version="${REF_NAME}"
|
||||
else
|
||||
version="$(awk -F'"' '/versionString =/ { print $2 }' src/libxrpl/protocol/BuildInfo.cpp)"
|
||||
if [[ -z "${version}" ]]; then
|
||||
echo "Unable to read versionString from BuildInfo.cpp." >&2
|
||||
exit 1
|
||||
fi
|
||||
version="${version}+${SHA:0:7}"
|
||||
fi
|
||||
|
||||
echo "version=${version}" | tee -a "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Determine release channel and package release
|
||||
id: release_info
|
||||
uses: XRPLF/actions/release-info@7cc0e4a8d9d0b838f92c48d312856b190341bbba
|
||||
70
.github/actions/setup-nix-env/action.yml
vendored
Normal file
70
.github/actions/setup-nix-env/action.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: Setup Nix environment
|
||||
description: "Build the flake's CI environment and put its tools on PATH."
|
||||
|
||||
# The environment from nix/ci-env.nix, the same one the Linux CI images bake in
|
||||
# (see nix/docker). Exported onto PATH rather than entered with `nix develop`:
|
||||
# the composite actions below run plain `bash` and would escape a dev shell.
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: Build the CI environment
|
||||
id: build
|
||||
shell: bash
|
||||
env:
|
||||
# --out-link doubles as a GC root for the length of the job.
|
||||
OUT_LINK: ${{ runner.temp }}/xrpld-ci-env
|
||||
run: |
|
||||
# --extra-experimental-features: flakes may not be on in the runner's nix.conf.
|
||||
nix --extra-experimental-features "nix-command flakes" \
|
||||
build .#default --out-link "${OUT_LINK}" --print-build-logs
|
||||
echo "path=$(readlink -f "${OUT_LINK}")" >>"${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Export the environment
|
||||
shell: bash
|
||||
env:
|
||||
ENV_PATH: ${{ steps.build.outputs.path }}
|
||||
run: |
|
||||
echo "${ENV_PATH}/bin" >>"${GITHUB_PATH}"
|
||||
|
||||
# Already KEY=VALUE per line. See `darwinEnv` in nix/ci-env.nix.
|
||||
ENV_FILE="${ENV_PATH}/share/xrpld-ci-env/env"
|
||||
if [ -f "${ENV_FILE}" ]; then
|
||||
cat "${ENV_FILE}" >>"${GITHUB_ENV}"
|
||||
fi
|
||||
|
||||
# XrplSanity.cmake otherwise rejects a Nix compiler as one that leaked.
|
||||
echo "XRPL_DEVSHELL=ci-env" >>"${GITHUB_ENV}"
|
||||
|
||||
# Unlike the Linux nix images, macOS needs no SSL_CERT_FILE: it has its
|
||||
# own trust store, and pinning would break TLS to hosts relying on it.
|
||||
|
||||
# In RUNNER_TEMP, which the runner empties per job, like the `.conan2`
|
||||
# prepare-runner hands the system toolchain - but under its own name:
|
||||
# that Conan is a different version, and the two would migrate each
|
||||
# other's cache.
|
||||
echo "CONAN_HOME=${RUNNER_TEMP}/.conan2-nix" >>"${GITHUB_ENV}"
|
||||
|
||||
# Config, profiles and remote, exactly as the dev shell sets them up on
|
||||
# entry; the `setup-conan` action is skipped for this toolchain.
|
||||
- name: Setup Conan
|
||||
shell: bash
|
||||
run: ./conan/init.sh
|
||||
|
||||
# `Check tools` runs later but swallows failures; a bad export would just
|
||||
# build with the system toolchain.
|
||||
- name: Verify the toolchain resolves into the Nix store
|
||||
shell: bash
|
||||
run: |
|
||||
for tool in clang clang++ cmake ninja conan; do
|
||||
path="$(command -v "${tool}" || true)"
|
||||
echo "${tool} -> ${path:-<not found>}"
|
||||
case "${path}" in
|
||||
/nix/store/*) ;;
|
||||
*)
|
||||
echo "::error::${tool} does not resolve into the Nix store"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
23
.github/dependabot.yml
vendored
23
.github/dependabot.yml
vendored
@@ -4,7 +4,8 @@ updates:
|
||||
directories:
|
||||
- /
|
||||
- .github/actions/build-deps/
|
||||
- .github/actions/generate-version/
|
||||
- .github/actions/cargo-cache/
|
||||
- .github/actions/release-info/
|
||||
- .github/actions/set-compiler-env/
|
||||
- .github/actions/setup-conan/
|
||||
schedule:
|
||||
@@ -15,3 +16,23 @@ updates:
|
||||
commit-message:
|
||||
prefix: "ci: [DEPENDABOT] "
|
||||
target-branch: develop
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
- package-ecosystem: cargo
|
||||
directory: /crates
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "04:00"
|
||||
timezone: Etc/GMT
|
||||
commit-message:
|
||||
prefix: "chore: [DEPENDABOT] "
|
||||
target-branch: develop
|
||||
open-pull-requests-limit: 10
|
||||
groups:
|
||||
rust-dependencies:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
12
.github/pull_request_template.md
vendored
12
.github/pull_request_template.md
vendored
@@ -1,10 +1,10 @@
|
||||
<!--
|
||||
This PR template helps you to write a good pull request description.
|
||||
This PR template helps you write a good pull request description.
|
||||
Please feel free to include additional useful information even beyond what is requested below.
|
||||
|
||||
If your branch is on a personal fork and has a name that allows it to
|
||||
run CI build/test jobs (e.g. "ci/foo"), remember to rename it BEFORE
|
||||
opening the PR. This avoids unnecessary redundant test runs. Renaming
|
||||
opening the PR. This avoids redundant test runs. Renaming
|
||||
the branch after opening the PR will close the PR.
|
||||
https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-branches-in-your-repository/renaming-a-branch
|
||||
-->
|
||||
@@ -15,10 +15,10 @@ https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-
|
||||
Please include a summary of the changes.
|
||||
This may be a direct input to the release notes.
|
||||
If too broad, please consider splitting into multiple PRs.
|
||||
If a relevant task or issue, please link it here.
|
||||
If there is a relevant task or issue, please link it here.
|
||||
-->
|
||||
|
||||
### Context of Change
|
||||
## Context of Change
|
||||
|
||||
<!--
|
||||
Please include the context of a change.
|
||||
@@ -29,7 +29,7 @@ If a refactor, how is this better than the previous implementation?
|
||||
If there is a spec or design document for this feature, please link it here.
|
||||
-->
|
||||
|
||||
### API Impact
|
||||
## API Impact
|
||||
|
||||
<!--
|
||||
Please check [x] relevant options, delete irrelevant ones.
|
||||
@@ -65,5 +65,5 @@ This section may not be needed if your change includes thoroughly commented unit
|
||||
|
||||
<!--
|
||||
## Future Tasks
|
||||
For future tasks related to PR.
|
||||
For future tasks related to this PR.
|
||||
-->
|
||||
|
||||
18
.github/scripts/levelization/README.md
vendored
18
.github/scripts/levelization/README.md
vendored
@@ -40,18 +40,18 @@ listed later.
|
||||
| 04 | xrpl/protocol |
|
||||
| 05 | xrpl/core xrpl/resource xrpl/server |
|
||||
| 06 | xrpl/ledger xrpl/nodestore xrpl/net |
|
||||
| 07 | xrpl/shamap |
|
||||
| 07 | xrpl/shamap xrpl/consensus |
|
||||
|
||||
## xrpld Modules (Application Implementation)
|
||||
|
||||
| Level / Tier | Module(s) |
|
||||
| ------------ | -------------------------------- |
|
||||
| 05 | xrpld/conditions xrpld/consensus |
|
||||
| 06 | xrpld/core xrpld/peerfinder |
|
||||
| 07 | xrpld/shamap xrpld/overlay |
|
||||
| 08 | xrpld/app |
|
||||
| 09 | xrpld/rpc |
|
||||
| 10 | xrpld/perflog |
|
||||
| Level / Tier | Module(s) |
|
||||
| ------------ | --------------------------- |
|
||||
| 05 | xrpld/conditions |
|
||||
| 06 | xrpld/core xrpld/peerfinder |
|
||||
| 07 | xrpld/shamap xrpld/overlay |
|
||||
| 08 | xrpld/app |
|
||||
| 09 | xrpld/rpc |
|
||||
| 10 | xrpld/perflog |
|
||||
|
||||
## Test Modules
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
Loop: xrpld.app xrpld.overlay
|
||||
xrpld.app > xrpld.overlay
|
||||
|
||||
Loop: xrpld.app xrpld.peerfinder
|
||||
xrpld.peerfinder ~= xrpld.app
|
||||
|
||||
Loop: xrpld.app xrpld.rpc
|
||||
xrpld.rpc > xrpld.app
|
||||
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
benchmarks.libxrpl > xrpl.basics
|
||||
benchmarks.libxrpl > xrpl.config
|
||||
benchmarks.libxrpl > xrpl.nodestore
|
||||
benchmarks.libxrpl > xrpl.protocol
|
||||
benchmarks.libxrpl > xrpl.protocol_autogen
|
||||
benchmarks.libxrpl > xrpl.tx
|
||||
libxrpl.basics > xrpl.basics
|
||||
libxrpl.conditions > xrpl.basics
|
||||
libxrpl.conditions > xrpl.conditions
|
||||
libxrpl.config > xrpl.basics
|
||||
libxrpl.config > xrpl.config
|
||||
libxrpl.consensus > xrpl.basics
|
||||
libxrpl.consensus > xrpl.consensus
|
||||
libxrpl.core > xrpl.basics
|
||||
libxrpl.core > xrpl.core
|
||||
libxrpl.core > xrpl.json
|
||||
@@ -22,6 +30,9 @@ libxrpl.nodestore > xrpl.config
|
||||
libxrpl.nodestore > xrpl.json
|
||||
libxrpl.nodestore > xrpl.nodestore
|
||||
libxrpl.nodestore > xrpl.protocol
|
||||
libxrpl.peerfinder > xrpl.basics
|
||||
libxrpl.peerfinder > xrpl.peerfinder
|
||||
libxrpl.peerfinder > xrpl.protocol
|
||||
libxrpl.protocol > xrpl.basics
|
||||
libxrpl.protocol > xrpl.json
|
||||
libxrpl.protocol > xrpl.protocol
|
||||
@@ -57,9 +68,9 @@ test.app > test.jtx
|
||||
test.app > test.unit_test
|
||||
test.app > xrpl.basics
|
||||
test.app > xrpl.config
|
||||
test.app > xrpl.consensus
|
||||
test.app > xrpl.core
|
||||
test.app > xrpld.app
|
||||
test.app > xrpld.consensus
|
||||
test.app > xrpld.core
|
||||
test.app > xrpld.overlay
|
||||
test.app > xrpld.rpc
|
||||
@@ -80,12 +91,9 @@ test.basics > xrpl.protocol
|
||||
test.beast > xrpl.basics
|
||||
test.conditions > xrpl.basics
|
||||
test.conditions > xrpl.conditions
|
||||
test.consensus > test.csf
|
||||
test.consensus > test.jtx
|
||||
test.consensus > test.unit_test
|
||||
test.consensus > xrpl.basics
|
||||
test.consensus > xrpld.app
|
||||
test.consensus > xrpld.consensus
|
||||
test.consensus > xrpl.ledger
|
||||
test.consensus > xrpl.protocol
|
||||
test.consensus > xrpl.shamap
|
||||
@@ -100,10 +108,6 @@ test.core > xrpl.json
|
||||
test.core > xrpl.protocol
|
||||
test.core > xrpl.rdb
|
||||
test.core > xrpl.server
|
||||
test.csf > xrpl.basics
|
||||
test.csf > xrpld.consensus
|
||||
test.csf > xrpl.json
|
||||
test.csf > xrpl.ledger
|
||||
test.json > test.jtx
|
||||
test.json > xrpl.json
|
||||
test.jtx > test.unit_test
|
||||
@@ -129,12 +133,9 @@ test.ledger > xrpl.json
|
||||
test.ledger > xrpl.ledger
|
||||
test.ledger > xrpl.protocol
|
||||
test.nodestore > test.jtx
|
||||
test.nodestore > test.unit_test
|
||||
test.nodestore > xrpl.basics
|
||||
test.nodestore > xrpl.config
|
||||
test.nodestore > xrpld.core
|
||||
test.nodestore > xrpl.nodestore
|
||||
test.nodestore > xrpl.protocol
|
||||
test.nodestore > xrpl.rdb
|
||||
test.overlay > test.jtx
|
||||
test.overlay > test.unit_test
|
||||
@@ -143,19 +144,13 @@ test.overlay > xrpl.config
|
||||
test.overlay > xrpld.app
|
||||
test.overlay > xrpld.core
|
||||
test.overlay > xrpld.overlay
|
||||
test.overlay > xrpld.peerfinder
|
||||
test.overlay > xrpl.json
|
||||
test.overlay > xrpl.nodestore
|
||||
test.overlay > xrpl.peerfinder
|
||||
test.overlay > xrpl.protocol
|
||||
test.overlay > xrpl.resource
|
||||
test.overlay > xrpl.server
|
||||
test.overlay > xrpl.shamap
|
||||
test.peerfinder > test.beast
|
||||
test.peerfinder > test.unit_test
|
||||
test.peerfinder > xrpl.basics
|
||||
test.peerfinder > xrpld.core
|
||||
test.peerfinder > xrpld.peerfinder
|
||||
test.peerfinder > xrpl.protocol
|
||||
test.protocol > test.jtx
|
||||
test.protocol > test.unit_test
|
||||
test.protocol > xrpl.basics
|
||||
@@ -189,11 +184,13 @@ test.unit_test > xrpl.basics
|
||||
test.unit_test > xrpl.protocol
|
||||
tests.libxrpl > xrpl.basics
|
||||
tests.libxrpl > xrpl.config
|
||||
tests.libxrpl > xrpl.consensus
|
||||
tests.libxrpl > xrpl.core
|
||||
tests.libxrpl > xrpl.json
|
||||
tests.libxrpl > xrpl.ledger
|
||||
tests.libxrpl > xrpl.net
|
||||
tests.libxrpl > xrpl.nodestore
|
||||
tests.libxrpl > xrpl.peerfinder
|
||||
tests.libxrpl > xrpl.protocol
|
||||
tests.libxrpl > xrpl.protocol_autogen
|
||||
tests.libxrpl > xrpl.resource
|
||||
@@ -203,6 +200,10 @@ tests.libxrpl > xrpl.tx
|
||||
xrpl.conditions > xrpl.basics
|
||||
xrpl.conditions > xrpl.protocol
|
||||
xrpl.config > xrpl.basics
|
||||
xrpl.consensus > xrpl.basics
|
||||
xrpl.consensus > xrpl.json
|
||||
xrpl.consensus > xrpl.ledger
|
||||
xrpl.consensus > xrpl.protocol
|
||||
xrpl.core > xrpl.basics
|
||||
xrpl.core > xrpl.json
|
||||
xrpl.core > xrpl.protocol
|
||||
@@ -217,6 +218,8 @@ xrpl.nodestore > xrpl.basics
|
||||
xrpl.nodestore > xrpl.config
|
||||
xrpl.nodestore > xrpl.json
|
||||
xrpl.nodestore > xrpl.protocol
|
||||
xrpl.peerfinder > xrpl.basics
|
||||
xrpl.peerfinder > xrpl.protocol
|
||||
xrpl.protocol > xrpl.basics
|
||||
xrpl.protocol > xrpl.json
|
||||
xrpl.protocol_autogen > xrpl.json
|
||||
@@ -243,23 +246,20 @@ xrpl.tx > xrpl.protocol
|
||||
xrpld.app > test.unit_test
|
||||
xrpld.app > xrpl.basics
|
||||
xrpld.app > xrpl.config
|
||||
xrpld.app > xrpl.consensus
|
||||
xrpld.app > xrpl.core
|
||||
xrpld.app > xrpld.consensus
|
||||
xrpld.app > xrpld.core
|
||||
xrpld.app > xrpl.json
|
||||
xrpld.app > xrpl.ledger
|
||||
xrpld.app > xrpl.net
|
||||
xrpld.app > xrpl.nodestore
|
||||
xrpld.app > xrpl.peerfinder
|
||||
xrpld.app > xrpl.protocol
|
||||
xrpld.app > xrpl.rdb
|
||||
xrpld.app > xrpl.resource
|
||||
xrpld.app > xrpl.server
|
||||
xrpld.app > xrpl.shamap
|
||||
xrpld.app > xrpl.tx
|
||||
xrpld.consensus > xrpl.basics
|
||||
xrpld.consensus > xrpl.json
|
||||
xrpld.consensus > xrpl.ledger
|
||||
xrpld.consensus > xrpl.protocol
|
||||
xrpld.core > xrpl.basics
|
||||
xrpld.core > xrpl.config
|
||||
xrpld.core > xrpl.core
|
||||
@@ -268,21 +268,22 @@ xrpld.core > xrpl.protocol
|
||||
xrpld.core > xrpl.rdb
|
||||
xrpld.overlay > xrpl.basics
|
||||
xrpld.overlay > xrpl.config
|
||||
xrpld.overlay > xrpl.consensus
|
||||
xrpld.overlay > xrpl.core
|
||||
xrpld.overlay > xrpld.consensus
|
||||
xrpld.overlay > xrpld.core
|
||||
xrpld.overlay > xrpld.peerfinder
|
||||
xrpld.overlay > xrpl.json
|
||||
xrpld.overlay > xrpl.ledger
|
||||
xrpld.overlay > xrpl.peerfinder
|
||||
xrpld.overlay > xrpl.protocol
|
||||
xrpld.overlay > xrpl.resource
|
||||
xrpld.overlay > xrpl.server
|
||||
xrpld.overlay > xrpl.shamap
|
||||
xrpld.overlay > xrpl.tx
|
||||
xrpld.peerfinder > xrpl.basics
|
||||
xrpld.peerfinder > xrpl.config
|
||||
xrpld.peerfinder > xrpld.app
|
||||
xrpld.peerfinder > xrpld.core
|
||||
xrpld.peerfinder > xrpl.protocol
|
||||
xrpld.peerfinder > xrpl.peerfinder
|
||||
xrpld.peerfinder > xrpl.rdb
|
||||
xrpld.perflog > xrpl.basics
|
||||
xrpld.perflog > xrpl.config
|
||||
|
||||
2
.github/scripts/rename/binary.sh
vendored
2
.github/scripts/rename/binary.sh
vendored
@@ -49,7 +49,7 @@ ${SED_COMMAND} -i -E 's@ripple/xrpld@XRPLF/rippled@g' BUILD.md
|
||||
${SED_COMMAND} -i -E 's@XRPLF/xrpld@XRPLF/rippled@g' BUILD.md
|
||||
${SED_COMMAND} -i -E 's@xrpld \(`xrpld`\)@xrpld@g' BUILD.md
|
||||
${SED_COMMAND} -i -E 's@XRPLF/xrpld@XRPLF/rippled@g' CONTRIBUTING.md
|
||||
${SED_COMMAND} -i -E 's@XRPLF/xrpld@XRPLF/rippled@g' docs/build/install.md
|
||||
${SED_COMMAND} -i -E 's@XRPLF/xrpld@XRPLF/rippled@g' docs/install.md
|
||||
|
||||
popd
|
||||
echo "Processing complete."
|
||||
|
||||
4
.github/scripts/rename/docs.sh
vendored
4
.github/scripts/rename/docs.sh
vendored
@@ -77,8 +77,8 @@ ${SED_COMMAND} -i 's/Ripple integrators/XRPL developers/' README.md
|
||||
${SED_COMMAND} -i 's/sanitizer-configuration-for-rippled/sanitizer-configuration-for-xrpld/' docs/build/sanitizers.md
|
||||
${SED_COMMAND} -i 's/rippled/xrpld/g' .github/scripts/levelization/README.md
|
||||
${SED_COMMAND} -i 's/rippled/xrpld/g' .github/scripts/strategy-matrix/generate.py
|
||||
${SED_COMMAND} -i 's@/rippled@/xrpld@g' docs/build/install.md
|
||||
${SED_COMMAND} -i 's@github.com/XRPLF/xrpld@github.com/XRPLF/rippled@g' docs/build/install.md
|
||||
${SED_COMMAND} -i 's@/rippled@/xrpld@g' docs/install.md
|
||||
${SED_COMMAND} -i 's@github.com/XRPLF/xrpld@github.com/XRPLF/rippled@g' docs/install.md
|
||||
${SED_COMMAND} -i 's/rippled/xrpld/g' docs/Doxyfile
|
||||
${SED_COMMAND} -i 's/ripple_basics/basics/' include/xrpl/basics/CountedObject.h
|
||||
${SED_COMMAND} -i 's/<ripple/<xrpl/' include/xrpl/protocol/AccountID.h
|
||||
|
||||
3
.github/scripts/rename/namespace.sh
vendored
3
.github/scripts/rename/namespace.sh
vendored
@@ -46,9 +46,6 @@ for DIRECTORY in "${DIRECTORIES[@]}"; do
|
||||
done
|
||||
done
|
||||
|
||||
# Special case for NuDBFactory that has ripple twice in the test suite name.
|
||||
${SED_COMMAND} -i -E 's/(BEAST_DEFINE_TESTSUITE.+)ripple(.+)/\1xrpl\2/g' src/test/nodestore/NuDBFactory_test.cpp
|
||||
|
||||
DIRECTORY=$1
|
||||
find "${DIRECTORY}" -type f -name "*.md" | while read -r FILE; do
|
||||
echo "Processing file: ${FILE}"
|
||||
|
||||
121
.github/scripts/strategy-matrix/generate.py
vendored
121
.github/scripts/strategy-matrix/generate.py
vendored
@@ -7,7 +7,12 @@ from pathlib import Path
|
||||
|
||||
THIS_DIR = Path(__file__).parent.resolve()
|
||||
|
||||
_BASE_CMAKE_ARGS = ["-Dtests=ON", "-Dwerr=ON", "-Dxrpld=ON", "-Dwextra=ON"]
|
||||
_BASE_CMAKE_ARGS = [
|
||||
"-Dtests=ON",
|
||||
"-Dwerr=ON",
|
||||
"-Dxrpld=ON",
|
||||
"-Dwextra=ON",
|
||||
]
|
||||
|
||||
# Maps sanitizer names (as used in cmake) to short config-name suffixes.
|
||||
_SANITIZER_SUFFIX: dict[str, str] = {
|
||||
@@ -17,6 +22,19 @@ _SANITIZER_SUFFIX: dict[str, str] = {
|
||||
}
|
||||
|
||||
|
||||
def config_name(
|
||||
distro: str,
|
||||
compiler: str,
|
||||
build_type: str,
|
||||
arch: str,
|
||||
suffix: str = "",
|
||||
sanitizer: str = "",
|
||||
) -> str:
|
||||
"""Name a config. Its artifacts are named after it, so packaging reuses this."""
|
||||
parts = [s for s in [suffix, _SANITIZER_SUFFIX.get(sanitizer, "")] if s]
|
||||
return "-".join([f"{distro}-{compiler}-{build_type.lower()}-{arch}", *parts])
|
||||
|
||||
|
||||
def get_cmake_args(build_type: str, extra_args: str) -> str:
|
||||
"""Get the full list of CMake arguments for a config."""
|
||||
args = _BASE_CMAKE_ARGS.copy()
|
||||
@@ -31,22 +49,41 @@ def get_cmake_args(build_type: str, extra_args: str) -> str:
|
||||
|
||||
|
||||
# Every config must declare 'minimal'. Minimal configs form the reduced matrix
|
||||
# built for pull requests by default; the full matrix adds the rest. Packaging
|
||||
# configs declare it too, but packaging is gated in the workflow, not by it.
|
||||
# built for pull requests by default; the full matrix adds the rest.
|
||||
#
|
||||
# Configs may also opt into 'benchmark' to smoke-run the benchmarks, or carry a
|
||||
# 'package' map to be packaged as well. Note that either applies to every entry
|
||||
# a config expands into, so only set them on configs that expand to a single
|
||||
# combination.
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class PackageConfig:
|
||||
"""The 'package' map of a config whose binaries are also packaged."""
|
||||
|
||||
type: str # "deb" or "rpm"; has to match what the image provides
|
||||
# The packaging container image: a vanilla distro image, not the nix image
|
||||
# the config itself builds in.
|
||||
image: str
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class LinuxConfig:
|
||||
"""One entry in linux.json's 'configs' or 'package_configs' arrays."""
|
||||
"""One entry in a linux.json 'configs' array."""
|
||||
|
||||
compiler: list[str]
|
||||
build_type: list[str]
|
||||
arch: list[str]
|
||||
minimal: bool
|
||||
benchmark: bool = False # if true, smoke-run the benchmarks after testing
|
||||
sanitizers: list[str] = dataclasses.field(default_factory=list)
|
||||
suffix: str = ""
|
||||
extra_cmake_args: str = ""
|
||||
image: str = "" # only used by package_configs entries
|
||||
package: PackageConfig | None = None # set to also package this config
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if isinstance(self.package, dict):
|
||||
self.package = PackageConfig(**self.package)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
@@ -55,22 +92,16 @@ class LinuxFile:
|
||||
|
||||
image_tag: str
|
||||
configs: dict[str, list[LinuxConfig]] # distro → configs
|
||||
package_configs: dict[str, list[LinuxConfig]] # distro → packaging configs
|
||||
|
||||
@classmethod
|
||||
def load(cls, path: Path) -> "LinuxFile":
|
||||
data = json.loads(path.read_text())
|
||||
|
||||
def parse(section: dict) -> dict[str, list[LinuxConfig]]:
|
||||
return {
|
||||
distro: [LinuxConfig(**c) for c in cfgs]
|
||||
for distro, cfgs in section.items()
|
||||
}
|
||||
|
||||
return cls(
|
||||
image_tag=data["image_tag"],
|
||||
configs=parse(data["configs"]),
|
||||
package_configs=parse(data.get("package_configs", {})),
|
||||
configs={
|
||||
distro: [LinuxConfig(**c) for c in cfgs]
|
||||
for distro, cfgs in data["configs"].items()
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -81,7 +112,11 @@ class PlatformConfig:
|
||||
build_type: list[str]
|
||||
minimal: bool
|
||||
build_only: bool = False # if true, skip tests (e.g. macos/Windows Debug)
|
||||
benchmark: bool = False # if true, smoke-run the benchmarks after testing
|
||||
extra_cmake_args: str = ""
|
||||
# "" is the runner's system compiler, "nix" the flake's CI environment.
|
||||
# macOS only: Linux always builds in a Nix image, Windows has no Nix.
|
||||
toolchain: str = ""
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if isinstance(self.build_type, str):
|
||||
@@ -125,20 +160,23 @@ class MatrixEntry:
|
||||
cmake_args: str
|
||||
cmake_target: str
|
||||
build_only: bool
|
||||
benchmark: bool
|
||||
build_type: str
|
||||
architecture: Architecture
|
||||
sanitizers: str
|
||||
image: str = "" # container image; empty for macOS/Windows (runs natively)
|
||||
compiler: str = "" # compiler name ("gcc" or "clang"); empty for macOS/Windows
|
||||
toolchain: str = "" # "nix" for the flake's CI environment; see PlatformConfig
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class PackagingEntry:
|
||||
"""One entry in the generated packaging strategy matrix."""
|
||||
|
||||
artifact_name: str
|
||||
xrpld_artifact_name: str
|
||||
validator_keys_artifact_name: str
|
||||
image: str
|
||||
distro: str # e.g. "debian" or "rhel"; drives package-format-specific steps
|
||||
package_type: str # "deb" or "rpm"; drives the format-specific steps
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -179,13 +217,9 @@ def expand_linux_matrix(linux: LinuxFile, minimal: bool) -> list[MatrixEntry]:
|
||||
effective_sanitizers,
|
||||
effective_archs.items(),
|
||||
):
|
||||
name = f"{distro}-{compiler}-{build_type.lower()}-{arch}"
|
||||
suffix_parts = [
|
||||
s for s in [cfg.suffix, _SANITIZER_SUFFIX.get(sanitizer, "")] if s
|
||||
]
|
||||
if suffix_parts:
|
||||
name += "-" + "-".join(suffix_parts)
|
||||
|
||||
name = config_name(
|
||||
distro, compiler, build_type, arch, cfg.suffix, sanitizer
|
||||
)
|
||||
entries.append(
|
||||
MatrixEntry(
|
||||
config_name=name,
|
||||
@@ -193,6 +227,7 @@ def expand_linux_matrix(linux: LinuxFile, minimal: bool) -> list[MatrixEntry]:
|
||||
cmake_args=get_cmake_args(build_type, cfg.extra_cmake_args),
|
||||
cmake_target="all",
|
||||
build_only=False,
|
||||
benchmark=cfg.benchmark,
|
||||
build_type=build_type,
|
||||
architecture=arch_info,
|
||||
sanitizers=sanitizer,
|
||||
@@ -204,22 +239,33 @@ def expand_linux_matrix(linux: LinuxFile, minimal: bool) -> list[MatrixEntry]:
|
||||
|
||||
|
||||
def expand_linux_packaging(linux: LinuxFile) -> list[PackagingEntry]:
|
||||
"""Generate the packaging matrix from a LinuxFile's package_configs section.
|
||||
"""Generate the packaging matrix from the configs that carry a 'package' map.
|
||||
|
||||
Packaging uses vanilla distro images (debian:bookworm, ubi9, …) instead of
|
||||
the nix-based build images, because deb/rpm tooling (debhelper, rpm-build)
|
||||
is taken from the distro's archive rather than from nixpkgs. Each config
|
||||
entry carries its own 'image'.
|
||||
Packaging consumes the binaries that config's build job uploaded, so the
|
||||
artifact names come from the same config name, and a packaged config is one
|
||||
that passes -Dvalidator_keys=ON.
|
||||
|
||||
Packaging itself runs in vanilla distro images (debian:trixie, almalinux:10)
|
||||
instead of the nix-based build images, because deb/rpm tooling (debhelper,
|
||||
rpm-build) is taken from the distro's archive rather than from nixpkgs.
|
||||
"""
|
||||
entries = []
|
||||
for distro, configs in linux.package_configs.items():
|
||||
for distro, configs in linux.configs.items():
|
||||
for cfg in configs:
|
||||
for compiler, build_type in itertools.product(cfg.compiler, cfg.build_type):
|
||||
if cfg.package is None:
|
||||
continue
|
||||
for compiler, build_type, arch in itertools.product(
|
||||
cfg.compiler, cfg.build_type, cfg.arch
|
||||
):
|
||||
# The packaging workflow hardcodes an amd64 runner.
|
||||
assert arch == "amd64", f"cannot package {distro} on {arch}"
|
||||
name = config_name(distro, compiler, build_type, arch, cfg.suffix)
|
||||
entries.append(
|
||||
PackagingEntry(
|
||||
artifact_name=f"xrpld-{distro}-{compiler}-{build_type.lower()}-amd64",
|
||||
image=cfg.image,
|
||||
distro=distro,
|
||||
xrpld_artifact_name=f"xrpld-{name}",
|
||||
validator_keys_artifact_name=f"validator-keys-{name}",
|
||||
image=cfg.package.image,
|
||||
package_type=cfg.package.type,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -239,15 +285,20 @@ def expand_platform_matrix(pf: PlatformFile, minimal: bool) -> list[MatrixEntry]
|
||||
if minimal and not cfg.minimal:
|
||||
continue
|
||||
for build_type in cfg.build_type:
|
||||
name = f"{platform_name}-{arch}-{build_type.lower()}"
|
||||
if cfg.toolchain:
|
||||
name += f"-{cfg.toolchain}"
|
||||
entries.append(
|
||||
MatrixEntry(
|
||||
config_name=f"{platform_name}-{arch}-{build_type.lower()}",
|
||||
config_name=name,
|
||||
cmake_args=get_cmake_args(build_type, cfg.extra_cmake_args),
|
||||
cmake_target="install" if is_windows else "all",
|
||||
build_only=cfg.build_only,
|
||||
benchmark=cfg.benchmark,
|
||||
build_type=build_type,
|
||||
architecture=Architecture(platform=pf.platform, runner=pf.runner),
|
||||
sanitizers="",
|
||||
toolchain=cfg.toolchain,
|
||||
)
|
||||
)
|
||||
return entries
|
||||
|
||||
53
.github/scripts/strategy-matrix/linux.json
vendored
53
.github/scripts/strategy-matrix/linux.json
vendored
@@ -1,13 +1,23 @@
|
||||
{
|
||||
"image_tag": "sha-e29b523",
|
||||
"image_tag": "sha-060957e",
|
||||
"configs": {
|
||||
"ubuntu": [
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Debug"],
|
||||
"arch": ["amd64"],
|
||||
"minimal": true,
|
||||
"suffix": "coverage",
|
||||
"extra_cmake_args": "-DUNIT_TEST_REFERENCE_FEE=500 -Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0"
|
||||
},
|
||||
{
|
||||
"compiler": ["clang"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"minimal": true
|
||||
"minimal": true,
|
||||
"benchmark": true
|
||||
},
|
||||
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
@@ -29,14 +39,6 @@
|
||||
"sanitizers": ["address", "undefinedbehavior"]
|
||||
},
|
||||
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Debug"],
|
||||
"arch": ["amd64"],
|
||||
"minimal": true,
|
||||
"suffix": "coverage",
|
||||
"extra_cmake_args": "-DUNIT_TEST_REFERENCE_FEE=500 -Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0"
|
||||
},
|
||||
{
|
||||
"compiler": ["clang"],
|
||||
"build_type": ["Debug"],
|
||||
@@ -63,32 +65,17 @@
|
||||
}
|
||||
],
|
||||
|
||||
"debian": [
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"minimal": false
|
||||
}
|
||||
],
|
||||
|
||||
"rhel": [
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"minimal": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"package_configs": {
|
||||
"debian": [
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"minimal": false,
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-577d745"
|
||||
"extra_cmake_args": "-Dvalidator_keys=ON",
|
||||
"package": {
|
||||
"type": "deb",
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-49cdc10"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -98,7 +85,11 @@
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"minimal": false,
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-577d745"
|
||||
"extra_cmake_args": "-Dvalidator_keys=ON",
|
||||
"package": {
|
||||
"type": "rpm",
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-49cdc10"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
13
.github/scripts/strategy-matrix/macos.json
vendored
13
.github/scripts/strategy-matrix/macos.json
vendored
@@ -12,6 +12,19 @@
|
||||
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
|
||||
"build_only": true,
|
||||
"minimal": false
|
||||
},
|
||||
{
|
||||
"build_type": "Release",
|
||||
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
|
||||
"toolchain": "nix",
|
||||
"minimal": false
|
||||
},
|
||||
{
|
||||
"build_type": "Debug",
|
||||
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
|
||||
"toolchain": "nix",
|
||||
"build_only": true,
|
||||
"minimal": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
14
.github/workflows/build-nix-images.yml
vendored
14
.github/workflows/build-nix-images.yml
vendored
@@ -1,27 +1,31 @@
|
||||
name: Build Nix Docker images
|
||||
name: Build `nix` Docker images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-nix-images.yml"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "rust-toolchain.toml"
|
||||
- "nix/**"
|
||||
- "!nix/docker/README.md"
|
||||
- "!nix/devshell.nix"
|
||||
- "bin/check-tools.sh"
|
||||
- "!nix/check-tools/**"
|
||||
- "bin/default-loader-path.sh"
|
||||
- "bin/install-sanitizer-libs.sh"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/build-nix-images.yml"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "rust-toolchain.toml"
|
||||
- "nix/**"
|
||||
- "!nix/docker/README.md"
|
||||
- "!nix/devshell.nix"
|
||||
- "!nix/check-tools/**"
|
||||
- "bin/check-tools.sh"
|
||||
- "bin/default-loader-path.sh"
|
||||
- "bin/install-sanitizer-libs.sh"
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -36,7 +40,7 @@ defaults:
|
||||
|
||||
jobs:
|
||||
build-merge:
|
||||
name: Build and push nix-${{ matrix.distro.name }}
|
||||
name: Build and push `nix-${{ matrix.distro.name }}` image
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
@@ -54,7 +58,7 @@ jobs:
|
||||
base_image: debian:bookworm
|
||||
- name: rhel
|
||||
base_image: registry.access.redhat.com/ubi9/ubi:latest
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@ee03d31bcc4501d7599dc1b1ecd7a34af582ad1c
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@034e87065fcd0100214cf0672923bd38d193cf78
|
||||
with:
|
||||
image_name: xrpld/nix-${{ matrix.distro.name }}
|
||||
dockerfile: nix/docker/Dockerfile
|
||||
|
||||
23
.github/workflows/build-packaging-images.yml
vendored
23
.github/workflows/build-packaging-images.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Build packaging Docker images
|
||||
name: Build `packaging` Docker images
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -6,13 +6,13 @@ on:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-packaging-images.yml"
|
||||
- "package/Dockerfile"
|
||||
- "package/install-packaging-tools.sh"
|
||||
- "bin/install-packaging-tools.sh"
|
||||
- "package/docker/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/build-packaging-images.yml"
|
||||
- "package/Dockerfile"
|
||||
- "package/install-packaging-tools.sh"
|
||||
- "bin/install-packaging-tools.sh"
|
||||
- "package/docker/**"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
@@ -26,21 +26,24 @@ defaults:
|
||||
|
||||
jobs:
|
||||
build-merge:
|
||||
name: Build and push packaging-${{ matrix.distro.name }}
|
||||
name: Build and push `packaging-${{ matrix.distro.name }}` image
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Newest of each distro: these images only wrap pre-built binaries, so
|
||||
# they set no floor for consumers. build_pkg.py pins the RPM dist tag.
|
||||
distro:
|
||||
- name: debian
|
||||
base_image: debian:bookworm
|
||||
base_image: debian:trixie
|
||||
# AlmaLinux rather than UBI, which does not ship rpm-sign.
|
||||
- name: rhel
|
||||
base_image: registry.access.redhat.com/ubi9/ubi:latest
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@ee03d31bcc4501d7599dc1b1ecd7a34af582ad1c
|
||||
base_image: almalinux:10
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@034e87065fcd0100214cf0672923bd38d193cf78
|
||||
with:
|
||||
image_name: xrpld/packaging-${{ matrix.distro.name }}
|
||||
dockerfile: package/Dockerfile
|
||||
dockerfile: package/docker/Dockerfile
|
||||
base_image: ${{ matrix.distro.base_image }}
|
||||
push: ${{ github.event_name == 'push' }}
|
||||
|
||||
38
.github/workflows/build-pre-commit-image.yml
vendored
Normal file
38
.github/workflows/build-pre-commit-image.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Build `pre-commit` Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-pre-commit-image.yml"
|
||||
- "bin/pre-commit/Dockerfile"
|
||||
- "rust-toolchain.toml"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/build-pre-commit-image.yml"
|
||||
- "bin/pre-commit/Dockerfile"
|
||||
- "rust-toolchain.toml"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
# Read `on-trigger.yml` for the rationale behind this concurrency group name.
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' && github.sha || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
build-merge:
|
||||
name: Build and push `pre-commit` image
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@034e87065fcd0100214cf0672923bd38d193cf78
|
||||
with:
|
||||
image_name: xrpld/pre-commit
|
||||
dockerfile: bin/pre-commit/Dockerfile
|
||||
base_image: ubuntu:26.04
|
||||
push: ${{ github.event_name == 'push' }}
|
||||
80
.github/workflows/cargo-audit.yml
vendored
Normal file
80
.github/workflows/cargo-audit.yml
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
name: Cargo audit
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# 06:32 UTC every Monday.
|
||||
- cron: "32 6 * * 1"
|
||||
push:
|
||||
branches:
|
||||
- "develop"
|
||||
- "release/*"
|
||||
paths:
|
||||
- "crates/**/Cargo.toml"
|
||||
- "crates/Cargo.lock"
|
||||
- ".github/workflows/cargo-audit.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "crates/**/Cargo.toml"
|
||||
- "crates/Cargo.lock"
|
||||
- ".github/workflows/cargo-audit.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: crates
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
permissions:
|
||||
contents: read
|
||||
# Needed to open an issue on scheduled failures.
|
||||
issues: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Run cargo audit
|
||||
id: audit
|
||||
continue-on-error: true
|
||||
run: |
|
||||
set -o pipefail
|
||||
cargo audit | tee /tmp/cargo-audit.txt
|
||||
|
||||
- name: Prepare issue body
|
||||
if: ${{ steps.audit.outcome != 'success' && github.event_name == 'schedule' }}
|
||||
run: |
|
||||
{
|
||||
echo "## \`cargo audit\` found advisories"
|
||||
echo
|
||||
echo '```'
|
||||
cat /tmp/cargo-audit.txt
|
||||
echo '```'
|
||||
echo
|
||||
echo "---"
|
||||
echo "*This issue was automatically created by the cargo-audit workflow.*"
|
||||
} >/tmp/cargo-audit-issue.md
|
||||
|
||||
- name: Create issue
|
||||
if: ${{ steps.audit.outcome != 'success' && github.event_name == 'schedule' }}
|
||||
uses: XRPLF/actions/create-issue@2b8bc36af85b88bca0dd7bfac2e2dc05f94ad712
|
||||
with:
|
||||
title: "cargo audit found vulnerabilities"
|
||||
body_file: /tmp/cargo-audit-issue.md
|
||||
labels: "Bug,Security"
|
||||
|
||||
- name: Fail if advisories were found
|
||||
if: ${{ steps.audit.outcome != 'success' }}
|
||||
run: |
|
||||
echo "cargo audit found advisories!"
|
||||
cat /tmp/cargo-audit.txt
|
||||
exit 1
|
||||
2
.github/workflows/check-pr-description.yml
vendored
2
.github/workflows/check-pr-description.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Write PR body to file
|
||||
env:
|
||||
|
||||
2
.github/workflows/check-pr-title.yml
vendored
2
.github/workflows/check-pr-title.yml
vendored
@@ -20,4 +20,4 @@ on:
|
||||
jobs:
|
||||
check_title:
|
||||
if: ${{ github.event.pull_request.draft != true }}
|
||||
uses: XRPLF/actions/.github/workflows/check-pr-title.yml@cba1f0891650baf1a9c88624dc2d72573be2eb81
|
||||
uses: XRPLF/actions/.github/workflows/check-pr-title.yml@d7c65e49225a38f6d8010eacf017bb5a98d7476c
|
||||
|
||||
114
.github/workflows/check-tools.yml
vendored
Normal file
114
.github/workflows/check-tools.yml
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
# Verifies the committed snapshots of `bin/check-tools.sh` output for each Nix
|
||||
# environment (see nix/check-tools/). If the environment changes — a new image
|
||||
# tag, an updated flake.lock, a different tool list — without the matching
|
||||
# snapshot being regenerated and committed, this workflow fails so the drift is
|
||||
# caught in review.
|
||||
#
|
||||
# To regenerate the snapshots, see nix/check-tools/README.md.
|
||||
name: Check tools
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/check-tools.yml"
|
||||
- ".github/scripts/strategy-matrix/linux.json"
|
||||
- "bin/check-tools.sh"
|
||||
- "nix/**"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "rust-toolchain.toml"
|
||||
push:
|
||||
branches:
|
||||
- "develop"
|
||||
paths:
|
||||
- ".github/workflows/check-tools.yml"
|
||||
- ".github/scripts/strategy-matrix/linux.json"
|
||||
- "bin/check-tools.sh"
|
||||
- "nix/**"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "rust-toolchain.toml"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
# The nix-nixos image tag is pinned alongside the build matrix in linux.json,
|
||||
# so snapshots are checked against the exact image CI builds against.
|
||||
linux-image-tag:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
tag: ${{ steps.tag.outputs.tag }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Read nix image tag
|
||||
id: tag
|
||||
run: echo "tag=$(jq -r .image_tag .github/scripts/strategy-matrix/linux.json)" >>"${GITHUB_OUTPUT}"
|
||||
|
||||
# One job for all environments; they differ only in whether the tools come
|
||||
# from the nix-nixos container (Linux) or `nix develop` (macOS).
|
||||
check-tools:
|
||||
needs: linux-image-tag
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: ubuntu-latest
|
||||
snapshot: nix/check-tools/nix-ubuntu-amd64.txt
|
||||
nix_develop: false
|
||||
- runner: ubuntu-24.04-arm
|
||||
snapshot: nix/check-tools/nix-ubuntu-arm64.txt
|
||||
nix_develop: false
|
||||
- runner: macos-26-apple-clang-21
|
||||
snapshot: nix/check-tools/macos.txt
|
||||
nix_develop: true
|
||||
runs-on: ${{ matrix.runner }}
|
||||
# Linux runs inside the pinned nix-nixos image; macOS runs natively and uses
|
||||
# the flake's dev shell instead (see the run step below).
|
||||
container: ${{ !matrix.nix_develop && format('ghcr.io/xrplf/xrpld/nix-ubuntu:{0}', needs.linux-image-tag.outputs.tag) || null }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
- name: Regenerate snapshot
|
||||
env:
|
||||
CHECK_TOOLS_SKIP_CLONE: "1"
|
||||
# check-tools.sh skips some macOS tools when CI is set; the snapshots
|
||||
# capture the full `nix develop` environment, so unset it here.
|
||||
CI: ""
|
||||
run: |
|
||||
if [ "${{ matrix.nix_develop }}" = "true" ]; then
|
||||
# `nix develop` prints the dev-shell greeting first; keep only the
|
||||
# check-tools.sh output (from the "Detected OS:" line onward).
|
||||
nix --extra-experimental-features "nix-command flakes" develop \
|
||||
-c bash bin/check-tools.sh | sed -n '/^Detected OS:/,$p' >"${{ matrix.snapshot }}"
|
||||
else
|
||||
bash bin/check-tools.sh >"${{ matrix.snapshot }}"
|
||||
fi
|
||||
|
||||
- name: Verify snapshot is up to date
|
||||
run: |
|
||||
if ! git diff --exit-code -- "${{ matrix.snapshot }}"; then
|
||||
echo "::error::${{ matrix.snapshot }} is out of date. Regenerate it (see nix/check-tools/README.md) and commit the result."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Upload regenerated snapshot
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: check-tools-${{ runner.os }}-${{ runner.arch }}
|
||||
path: ${{ matrix.snapshot }}
|
||||
64
.github/workflows/on-pr.yml
vendored
64
.github/workflows/on-pr.yml
vendored
@@ -25,18 +25,11 @@ on:
|
||||
- unlabeled
|
||||
|
||||
concurrency:
|
||||
# Use a per-ref group so a newer run (a push, or a change to a label below)
|
||||
# supersedes the in-progress one for that ref. Label events we don't act on get
|
||||
# their own unique group (per run id) instead, keeping them out of the shared
|
||||
# group so real builds keep running. Keep this list in sync with `should-run`.
|
||||
group: >-
|
||||
${{ github.workflow }}-${{ github.ref }}${{
|
||||
((github.event.action == 'labeled' || github.event.action == 'unlabeled')
|
||||
&& github.event.label.name != 'Ready to merge'
|
||||
&& github.event.label.name != 'DraftRunCI'
|
||||
&& github.event.label.name != 'Full CI build')
|
||||
&& format('-{0}', github.run_id) || ''
|
||||
}}
|
||||
# A single per-ref group with cancel-in-progress means any newer run (a push
|
||||
# or a label change) supersedes the in-progress one for that ref. Keeping
|
||||
# exactly one authoritative run per ref ensures a fast do-nothing run can never
|
||||
# mask a real build's checks.
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
@@ -44,26 +37,22 @@ defaults:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
# This job determines whether the rest of the workflow should run. It runs
|
||||
# when the PR is not a draft (which should also cover merge-group) or has the
|
||||
# 'DraftRunCI' or 'Full CI build' label. For label events it only runs when the
|
||||
# label added or removed is one we act on ('Ready to merge', 'DraftRunCI' or
|
||||
# 'Full CI build'), so unrelated label changes do not trigger a redundant run.
|
||||
# This job determines whether the rest of the workflow should run at all,
|
||||
# based on the current set of labels: it runs when the PR is not a draft
|
||||
# (which should also cover merge-group) or has the 'DraftRunCI' or
|
||||
# 'Full CI build' label. Whether a build then happens, and whether it is the
|
||||
# minimal or full matrix, is decided further below and in the strategy matrix.
|
||||
should-run:
|
||||
if: >-
|
||||
${{
|
||||
((github.event.action != 'labeled' && github.event.action != 'unlabeled')
|
||||
|| github.event.label.name == 'Ready to merge'
|
||||
|| github.event.label.name == 'DraftRunCI'
|
||||
|| github.event.label.name == 'Full CI build')
|
||||
&& (!github.event.pull_request.draft
|
||||
|| contains(github.event.pull_request.labels.*.name, 'DraftRunCI')
|
||||
|| contains(github.event.pull_request.labels.*.name, 'Full CI build'))
|
||||
!github.event.pull_request.draft
|
||||
|| contains(github.event.pull_request.labels.*.name, 'DraftRunCI')
|
||||
|| contains(github.event.pull_request.labels.*.name, 'Full CI build')
|
||||
}}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Determine changed files
|
||||
# This step checks whether any files have changed that should
|
||||
# cause the next jobs to run. We do it this way rather than
|
||||
@@ -88,22 +77,28 @@ jobs:
|
||||
|
||||
# Keep the paths below in sync with those in `on-trigger.yml`.
|
||||
.github/actions/build-deps/**
|
||||
.github/actions/generate-version/**
|
||||
.github/actions/release-info/**
|
||||
.github/actions/setup-conan/**
|
||||
.github/actions/setup-nix-env/**
|
||||
.github/scripts/strategy-matrix/**
|
||||
.github/workflows/reusable-build-test-config.yml
|
||||
.github/workflows/reusable-build-test.yml
|
||||
.github/workflows/reusable-check-autogen.yml
|
||||
.github/workflows/reusable-clang-tidy.yml
|
||||
.github/workflows/reusable-package.yml
|
||||
.github/workflows/reusable-rust.yml
|
||||
.github/workflows/reusable-strategy-matrix.yml
|
||||
.github/workflows/reusable-test.yml
|
||||
.github/workflows/reusable-upload-recipe.yml
|
||||
.clang-tidy
|
||||
.codecov.yml
|
||||
bin/check-nix-store-refs.sh
|
||||
bin/check-tools.sh
|
||||
bin/default-loader-path.sh
|
||||
cfg/**
|
||||
cmake/**
|
||||
conan/**
|
||||
crates/**
|
||||
external/**
|
||||
include/**
|
||||
src/**
|
||||
@@ -111,6 +106,9 @@ jobs:
|
||||
CMakeLists.txt
|
||||
conanfile.py
|
||||
conan.lock
|
||||
flake.lock
|
||||
flake.nix
|
||||
nix/**
|
||||
LICENSE.md
|
||||
package/**
|
||||
README.md
|
||||
@@ -136,6 +134,11 @@ jobs:
|
||||
outputs:
|
||||
go: ${{ steps.go.outputs.go == 'true' }}
|
||||
|
||||
check-autogen:
|
||||
needs: should-run
|
||||
if: ${{ needs.should-run.outputs.go == 'true' }}
|
||||
uses: ./.github/workflows/reusable-check-autogen.yml
|
||||
|
||||
check-levelization:
|
||||
needs: should-run
|
||||
if: ${{ needs.should-run.outputs.go == 'true' }}
|
||||
@@ -172,6 +175,13 @@ jobs:
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
rust:
|
||||
needs: should-run
|
||||
if: ${{ needs.should-run.outputs.go == 'true' }}
|
||||
uses: ./.github/workflows/reusable-rust.yml
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
package:
|
||||
needs: [should-run, build-test]
|
||||
# Packaging consumes the debian/rhel release binaries, which are only built
|
||||
@@ -210,10 +220,12 @@ jobs:
|
||||
passed:
|
||||
if: failure() || cancelled()
|
||||
needs:
|
||||
- check-autogen
|
||||
- check-levelization
|
||||
- check-rename
|
||||
- clang-tidy
|
||||
- build-test
|
||||
- rust
|
||||
- package
|
||||
- upload-recipe
|
||||
- notify-clio
|
||||
|
||||
18
.github/workflows/on-tag.yml
vendored
18
.github/workflows/on-tag.yml
vendored
@@ -1,5 +1,9 @@
|
||||
# This workflow uploads the libxrpl recipe to the Conan remote and builds
|
||||
# release packages when a versioned tag is pushed.
|
||||
# When a versioned tag is pushed, this workflow:
|
||||
#
|
||||
# - uploads the libxrpl recipe to the Conan remote
|
||||
# - builds and tests the release binaries
|
||||
# - builds the DEB and RPM packages
|
||||
# - publishes those packages to the XRPLF package repositories
|
||||
name: Tag
|
||||
|
||||
on:
|
||||
@@ -24,7 +28,7 @@ jobs:
|
||||
remote_password: ${{ secrets.NEXUS_REMOTE_PASSWORD }}
|
||||
|
||||
build-test:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
if: ${{ github.repository_owner == 'XRPLF' }}
|
||||
uses: ./.github/workflows/reusable-build-test.yml
|
||||
strategy:
|
||||
fail-fast: true
|
||||
@@ -37,6 +41,12 @@ jobs:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
package:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
if: ${{ github.repository_owner == 'XRPLF' }}
|
||||
needs: build-test
|
||||
uses: ./.github/workflows/reusable-package.yml
|
||||
with:
|
||||
publish: true
|
||||
secrets:
|
||||
remote_username: ${{ secrets.NEXUS_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.NEXUS_REMOTE_PASSWORD }}
|
||||
signing_key: ${{ secrets.NEXUS_PACKAGES_PRIVATE_KEY }}
|
||||
|
||||
27
.github/workflows/on-trigger.yml
vendored
27
.github/workflows/on-trigger.yml
vendored
@@ -15,22 +15,28 @@ on:
|
||||
|
||||
# Keep the paths below in sync with those in `on-pr.yml`.
|
||||
- ".github/actions/build-deps/**"
|
||||
- ".github/actions/generate-version/**"
|
||||
- ".github/actions/release-info/**"
|
||||
- ".github/actions/setup-conan/**"
|
||||
- ".github/actions/setup-nix-env/**"
|
||||
- ".github/scripts/strategy-matrix/**"
|
||||
- ".github/workflows/reusable-build-test-config.yml"
|
||||
- ".github/workflows/reusable-build-test.yml"
|
||||
- ".github/workflows/reusable-check-autogen.yml"
|
||||
- ".github/workflows/reusable-clang-tidy.yml"
|
||||
- ".github/workflows/reusable-package.yml"
|
||||
- ".github/workflows/reusable-rust.yml"
|
||||
- ".github/workflows/reusable-strategy-matrix.yml"
|
||||
- ".github/workflows/reusable-test.yml"
|
||||
- ".github/workflows/reusable-upload-recipe.yml"
|
||||
- ".clang-tidy"
|
||||
- ".codecov.yml"
|
||||
- "bin/check-nix-store-refs.sh"
|
||||
- "bin/check-tools.sh"
|
||||
- "bin/default-loader-path.sh"
|
||||
- "cfg/**"
|
||||
- "cmake/**"
|
||||
- "conan/**"
|
||||
- "crates/**"
|
||||
- "external/**"
|
||||
- "include/**"
|
||||
- "src/**"
|
||||
@@ -38,6 +44,9 @@ on:
|
||||
- "CMakeLists.txt"
|
||||
- "conanfile.py"
|
||||
- "conan.lock"
|
||||
- "flake.lock"
|
||||
- "flake.nix"
|
||||
- "nix/**"
|
||||
- "LICENSE.md"
|
||||
- "package/**"
|
||||
- "README.md"
|
||||
@@ -66,6 +75,9 @@ defaults:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
check-autogen:
|
||||
uses: ./.github/workflows/reusable-check-autogen.yml
|
||||
|
||||
clang-tidy:
|
||||
uses: ./.github/workflows/reusable-clang-tidy.yml
|
||||
permissions:
|
||||
@@ -91,6 +103,11 @@ jobs:
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
rust:
|
||||
uses: ./.github/workflows/reusable-rust.yml
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
upload-recipe:
|
||||
needs: build-test
|
||||
# Only run when pushing to the develop branch.
|
||||
@@ -103,3 +120,11 @@ jobs:
|
||||
package:
|
||||
needs: build-test
|
||||
uses: ./.github/workflows/reusable-package.yml
|
||||
with:
|
||||
# Packages are built on every trigger; only develop pushes in XRPLF/rippled
|
||||
# publish them, matching upload-recipe above.
|
||||
publish: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' && github.ref == 'refs/heads/develop' }}
|
||||
secrets:
|
||||
remote_username: ${{ secrets.NEXUS_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.NEXUS_REMOTE_PASSWORD }}
|
||||
signing_key: ${{ secrets.NEXUS_PACKAGES_PRIVATE_KEY }}
|
||||
|
||||
4
.github/workflows/pre-commit.yml
vendored
4
.github/workflows/pre-commit.yml
vendored
@@ -14,7 +14,7 @@ on:
|
||||
jobs:
|
||||
# Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks.
|
||||
run-hooks:
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@1bde119a1ab71305ba5d3716e7a82cea1c7bdede
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@279ec358f4a1be4088be3e024b07916fa97c75b6
|
||||
with:
|
||||
runs_on: ubuntu-latest
|
||||
container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/xrpld/pre-commit:sha-473fe44" }'
|
||||
|
||||
8
.github/workflows/publish-docs.yml
vendored
8
.github/workflows/publish-docs.yml
vendored
@@ -41,13 +41,13 @@ env:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-e29b523
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@64ec3cf3b152b4444638f470bbd6df7a7a30c81c
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
@@ -91,4 +91,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deploy
|
||||
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|
||||
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
|
||||
|
||||
182
.github/workflows/reusable-build-test-config.yml
vendored
182
.github/workflows/reusable-build-test-config.yml
vendored
@@ -3,6 +3,12 @@ name: Build and test configuration
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
benchmark:
|
||||
description: "Whether to smoke-run the benchmarks after testing."
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
build_only:
|
||||
description: 'Whether to only build or to build and test the code ("true", "false").'
|
||||
required: true
|
||||
@@ -63,6 +69,12 @@ on:
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
toolchain:
|
||||
description: 'Where the toolchain comes from ("nix" to build the flake CI environment on the runner, empty for the system one). macOS only: Linux always builds in a Nix image, and Nix has no Windows support.'
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
secrets:
|
||||
CODECOV_TOKEN:
|
||||
description: "The Codecov token to use for uploading coverage reports."
|
||||
@@ -100,23 +112,32 @@ jobs:
|
||||
# header files are copied into separate directories by CMake, which will
|
||||
# otherwise result in cache misses.
|
||||
CCACHE_SLOPPINESS: include_file_ctime,include_file_mtime
|
||||
# Determine if coverage and voidstar should be enabled.
|
||||
# Determine if coverage, voidstar and validator-keys should be enabled.
|
||||
COVERAGE_ENABLED: ${{ contains(inputs.cmake_args, '-Dcoverage=ON') }}
|
||||
VOIDSTAR_ENABLED: ${{ contains(inputs.cmake_args, '-Dvoidstar=ON') }}
|
||||
VALIDATOR_KEYS_ENABLED: ${{ contains(inputs.cmake_args, '-Dvalidator_keys=ON') }}
|
||||
SANITIZERS_ENABLED: ${{ inputs.sanitizers != '' }}
|
||||
# The binaries reusable-package.yml consumes. A private repository skips
|
||||
# them except on a tag push, which is what produces its release packages.
|
||||
PACKAGING_ARTIFACTS_ENABLED: ${{ github.event.repository.visibility == 'public' || startsWith(github.ref, 'refs/tags/') }}
|
||||
steps:
|
||||
- name: Cleanup workspace (macOS and Windows)
|
||||
if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }}
|
||||
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@64ec3cf3b152b4444638f470bbd6df7a7a30c81c
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
with:
|
||||
enable_ccache: ${{ inputs.ccache_enabled }}
|
||||
|
||||
# Before any step that uses a build tool, composite actions included.
|
||||
- name: Setup Nix environment
|
||||
if: ${{ inputs.toolchain == 'nix' }}
|
||||
uses: ./.github/actions/setup-nix-env
|
||||
|
||||
- name: Set ccache log file
|
||||
if: ${{ inputs.ccache_enabled && runner.debug == '1' }}
|
||||
run: echo "CCACHE_LOGFILE=${{ runner.temp }}/ccache.log" >>"${GITHUB_ENV}"
|
||||
@@ -141,7 +162,21 @@ jobs:
|
||||
with:
|
||||
compiler: ${{ inputs.compiler }}
|
||||
|
||||
- name: Use cargo artifacts cache
|
||||
uses: ./.github/actions/cargo-cache
|
||||
with:
|
||||
cache-directories: ${{ env.BUILD_DIR }}/corrosion
|
||||
key: ${{ inputs.config_name }}
|
||||
# two workspaces here because build artifacts are located in 2 places:
|
||||
# - crates/target when cargo is called directly
|
||||
# - build/cargo when cargo is called by cmake
|
||||
workspaces: |
|
||||
crates
|
||||
crates -> ${{ runner.os == 'Windows' && format('../{0}/x64/{1}/cargo', env.BUILD_DIR, inputs.build_type) || format('../{0}/cargo', env.BUILD_DIR) }}
|
||||
|
||||
# `setup-nix-env` already did this for the Nix toolchain.
|
||||
- name: Setup Conan
|
||||
if: ${{ inputs.toolchain != 'nix' }}
|
||||
env:
|
||||
SANITIZERS: ${{ inputs.sanitizers }}
|
||||
uses: ./.github/actions/setup-conan
|
||||
@@ -170,9 +205,9 @@ jobs:
|
||||
..
|
||||
|
||||
# Export the sanitizer options before any instrumented binary runs. The
|
||||
# protocol code-gen and build steps below invoke instrumented dependency
|
||||
# tools (protoc, grpc), so setting UBSAN_OPTIONS here lets the UBSan
|
||||
# suppression list silence their diagnostics too, not just at test time.
|
||||
# build step below invokes instrumented dependency tools (protoc, grpc),
|
||||
# so setting UBSAN_OPTIONS here lets the UBSan suppression list silence
|
||||
# their diagnostics too, not just at test time.
|
||||
# GITHUB_WORKSPACE (not the github.workspace context) is used so the path
|
||||
# resolves correctly inside the container job.
|
||||
- name: Set sanitizer options
|
||||
@@ -190,32 +225,6 @@ jobs:
|
||||
echo "UBSAN_OPTIONS=include=${SUPP}/runtime-ubsan-options.txt:suppressions=${SUPP}/ubsan.supp" >>${GITHUB_ENV}
|
||||
echo "LSAN_OPTIONS=include=${SUPP}/runtime-lsan-options.txt:suppressions=${SUPP}/lsan.supp" >>${GITHUB_ENV}
|
||||
|
||||
- name: Check protocol autogen files are up-to-date
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
env:
|
||||
MESSAGE: |
|
||||
|
||||
The generated protocol wrapper classes are out of date.
|
||||
|
||||
This typically happens when the macro files or generator scripts
|
||||
have changed but the generated files were not regenerated.
|
||||
|
||||
To fix this:
|
||||
1. Run: cmake --build . --target setup_code_gen
|
||||
2. Run: cmake --build . --target code_gen
|
||||
3. Commit and push the regenerated files
|
||||
run: |
|
||||
set -e
|
||||
cmake --build . --target setup_code_gen
|
||||
cmake --build . --target code_gen
|
||||
DIFF=$(git -C .. status --porcelain -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen)
|
||||
if [ -n "${DIFF}" ]; then
|
||||
echo "::error::Generated protocol files are out of date"
|
||||
git -C .. diff -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen
|
||||
echo "${MESSAGE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build the binary
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
env:
|
||||
@@ -223,11 +232,31 @@ jobs:
|
||||
BUILD_TYPE: ${{ inputs.build_type }}
|
||||
CMAKE_TARGET: ${{ inputs.cmake_target }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
cmake \
|
||||
--build . \
|
||||
--config "${BUILD_TYPE}" \
|
||||
--parallel "${BUILD_NPROC}" \
|
||||
--target "${CMAKE_TARGET}"
|
||||
--target "${CMAKE_TARGET}" \
|
||||
2>&1 | tee "${GITHUB_WORKSPACE}/build.log"
|
||||
|
||||
# Nothing may reference the store, so whole trees are checked - the Conan
|
||||
# cache included, since what it holds is what gets uploaded and reused.
|
||||
- name: Check the build output for Nix store references (Nix toolchain)
|
||||
if: ${{ inputs.toolchain == 'nix' }}
|
||||
run: ./bin/check-nix-store-refs.sh "${BUILD_DIR}"
|
||||
|
||||
- name: Check the Conan cache for Nix store references (Nix toolchain)
|
||||
if: ${{ inputs.toolchain == 'nix' }}
|
||||
run: ./bin/check-nix-store-refs.sh "${CONAN_HOME}"
|
||||
|
||||
# Only what PatchNixBinary.cmake retargets: the toolchain in the Linux
|
||||
# images always references the store. Same condition it uses.
|
||||
- name: Check for Nix store references (Linux)
|
||||
if: ${{ runner.os == 'Linux' && env.SANITIZERS_ENABLED == 'false' }}
|
||||
run: |
|
||||
./bin/check-nix-store-refs.sh "${BUILD_DIR}/xrpld"
|
||||
./bin/check-nix-store-refs.sh "${BUILD_DIR}/xrpl_tests"
|
||||
|
||||
- name: Show ccache statistics
|
||||
if: ${{ inputs.ccache_enabled }}
|
||||
@@ -239,7 +268,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload the binary (Linux)
|
||||
if: ${{ github.event.repository.visibility == 'public' && runner.os == 'Linux' }}
|
||||
if: ${{ env.PACKAGING_ARTIFACTS_ENABLED == 'true' && runner.os == 'Linux' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: xrpld-${{ inputs.config_name }}
|
||||
@@ -247,6 +276,22 @@ jobs:
|
||||
retention-days: 3
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Run the validator-keys tests
|
||||
if: ${{ env.VALIDATOR_KEYS_ENABLED == 'true' }}
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: ./validator-keys --unittest
|
||||
|
||||
- name: Upload the validator-keys binary
|
||||
if: ${{ env.PACKAGING_ARTIFACTS_ENABLED == 'true' && env.VALIDATOR_KEYS_ENABLED == 'true' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: validator-keys-${{ inputs.config_name }}
|
||||
path: |
|
||||
${{ env.BUILD_DIR }}/validator-keys
|
||||
${{ env.BUILD_DIR }}/validator-keys-LICENSE
|
||||
retention-days: 3
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload the test binary (Linux)
|
||||
if: ${{ github.event.repository.visibility == 'public' && runner.os == 'Linux' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
@@ -264,7 +309,7 @@ jobs:
|
||||
./xrpld --definitions | python3 -m json.tool >server_definitions.json
|
||||
|
||||
- name: Upload server definitions
|
||||
if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'debian-gcc-release-amd64' }}
|
||||
if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'ubuntu-gcc-debug-amd64-coverage' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: server-definitions
|
||||
@@ -322,27 +367,57 @@ jobs:
|
||||
PRELOAD=""
|
||||
fi
|
||||
|
||||
LD_PRELOAD="$PRELOAD" ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log
|
||||
LD_PRELOAD="$PRELOAD" ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee "${GITHUB_WORKSPACE}/unittest.log"
|
||||
|
||||
- name: Show test failure summary
|
||||
if: ${{ failure() && !inputs.build_only }}
|
||||
env:
|
||||
WORKING_DIR: ${{ runner.os == 'Windows' && format('{0}\{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}
|
||||
- name: Run Rust tests
|
||||
if: ${{ !inputs.build_only }}
|
||||
working-directory: crates
|
||||
# `xrpl-wasm-vm-ffi` is left out on Windows: its tests link as an executable, and
|
||||
# MSVC - unlike the Unix linkers - will not dead-strip the never-called cxx wrappers
|
||||
# whose C++ shims only the CMake build defines. The other runners cover these tests.
|
||||
run: cargo nextest run --workspace --all-features --locked --no-tests=warn ${{ runner.os == 'Windows' && '--exclude xrpl-wasm-vm-ffi' || '' }}
|
||||
|
||||
# Smoke-run every benchmark module with a single repetition to confirm the
|
||||
# benchmarks still build and execute. This is a correctness check, not a
|
||||
# performance measurement, so there is nothing to gain from repeating it
|
||||
# across configurations: it is opted into by a single config in the
|
||||
# strategy matrix (see the 'benchmark' flag in the JSON files), which
|
||||
# keeps it off instrumented builds (sanitizers/coverage/voidstar), where
|
||||
# it would be slow and meaningless, off Debug builds, where it is much
|
||||
# slower, and off Windows, where the `install` target does not build them.
|
||||
- name: Run the benchmarks
|
||||
if: ${{ inputs.benchmark }}
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
if [ ! -d "${WORKING_DIR}" ]; then
|
||||
echo "Working directory '${WORKING_DIR}' does not exist."
|
||||
exit 0
|
||||
fi
|
||||
rc=0
|
||||
while IFS= read -r bench; do
|
||||
echo "::group::${bench}"
|
||||
"./${bench}" --benchmark_repetitions=1 || rc=1
|
||||
echo "::endgroup::"
|
||||
done < <(find src/benchmarks -type f -perm -u+x -name 'xrpl.bench.*')
|
||||
exit "${rc}"
|
||||
|
||||
cd "${WORKING_DIR}"
|
||||
- name: Show build/test failure summary
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
cd "${GITHUB_WORKSPACE}"
|
||||
|
||||
if [ ! -f unittest.log ]; then
|
||||
echo "unittest.log not found; embedded tests may not have run."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! grep -E "failed" unittest.log; then
|
||||
echo "Log present but no failure lines found in unittest.log."
|
||||
if [ -f unittest.log ]; then
|
||||
if ! grep -E "failed" unittest.log | grep -vE "^I[0-9]|^[0-9]+> (ERR:|FTL:)"; then
|
||||
echo "unittest.log present but no failure lines found."
|
||||
fi
|
||||
elif [ -f build.log ]; then
|
||||
# GCC/Clang emit "error:" (covers "fatal error:"); MSVC emits
|
||||
# "error C####:", "error LNK####:", and "fatal error LNK####:".
|
||||
# -A6 prints the lines that follow each match (source line, caret,
|
||||
# notes, and the "N errors generated" tally) to capture the whole
|
||||
# diagnostic block.
|
||||
if ! grep -E -A6 "error:|error C[0-9]{4}|error LNK[0-9]{4}|fatal error" build.log; then
|
||||
echo "build.log present but no compile errors found."
|
||||
fi
|
||||
else
|
||||
echo "unittest.log/build.log not found; something went wrong."
|
||||
exit 1
|
||||
fi
|
||||
- name: Debug failure (Linux)
|
||||
if: ${{ failure() && runner.os == 'Linux' && !inputs.build_only }}
|
||||
@@ -366,13 +441,14 @@ jobs:
|
||||
--target coverage
|
||||
|
||||
- name: Upload coverage report
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
|
||||
if: ${{ github.repository_owner == 'XRPLF' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
disable_search: true
|
||||
disable_telem: true
|
||||
fail_ci_if_error: true
|
||||
files: ${{ env.BUILD_DIR }}/coverage.xml
|
||||
flags: cpp
|
||||
plugins: noop
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
||||
|
||||
2
.github/workflows/reusable-build-test.yml
vendored
2
.github/workflows/reusable-build-test.yml
vendored
@@ -40,6 +40,7 @@ jobs:
|
||||
fail-fast: ${{ github.event_name == 'merge_group' }}
|
||||
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
||||
with:
|
||||
benchmark: ${{ matrix.benchmark }}
|
||||
build_only: ${{ matrix.build_only }}
|
||||
build_type: ${{ matrix.build_type }}
|
||||
ccache_enabled: ${{ inputs.ccache_enabled }}
|
||||
@@ -50,5 +51,6 @@ jobs:
|
||||
config_name: ${{ matrix.config_name }}
|
||||
sanitizers: ${{ matrix.sanitizers }}
|
||||
compiler: ${{ matrix.compiler || '' }}
|
||||
toolchain: ${{ matrix.toolchain || '' }}
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
76
.github/workflows/reusable-check-autogen.yml
vendored
Normal file
76
.github/workflows/reusable-check-autogen.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
# This workflow checks that the generated protocol wrapper classes are
|
||||
# up-to-date with the macro files and generator scripts they are produced from,
|
||||
# see more info in include/xrpl/protocol_autogen/README.md.
|
||||
name: Check autogen
|
||||
|
||||
# This workflow can only be triggered by other workflows.
|
||||
on: workflow_call
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-autogen
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
BUILD_DIR: build/codegen
|
||||
|
||||
jobs:
|
||||
autogen:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
|
||||
# Code generation is pure Python, so the standalone project below offers
|
||||
# the same targets as the main build without needing its dependencies or
|
||||
# a compiler, which keeps this job down to a few seconds.
|
||||
- name: Configure CMake
|
||||
run: cmake -S cmake/codegen -B "${BUILD_DIR}"
|
||||
|
||||
- name: Install code generation dependencies
|
||||
run: cmake --build "${BUILD_DIR}" --target setup_code_gen
|
||||
|
||||
- name: Generate code
|
||||
run: cmake --build "${BUILD_DIR}" --target code_gen
|
||||
|
||||
- name: Check for differences
|
||||
env:
|
||||
MESSAGE: |
|
||||
|
||||
The generated protocol wrapper classes are out of date.
|
||||
|
||||
This typically happens when the macro files or generator scripts
|
||||
have changed but the generated files were not regenerated.
|
||||
|
||||
Run the following from the repository root, then commit and push
|
||||
the regenerated files. This needs neither the dependencies nor a
|
||||
compiler. See include/xrpl/protocol_autogen/README.md for more info.
|
||||
|
||||
cmake -S cmake/codegen -B build/codegen
|
||||
cmake --build build/codegen --target setup_code_gen
|
||||
cmake --build build/codegen --target code_gen
|
||||
|
||||
In an already configured build directory, the 'setup_code_gen' and
|
||||
'code_gen' targets do the same thing.
|
||||
run: |
|
||||
# Record untracked files in the index without staging their contents,
|
||||
# so that classes generated for a newly added transaction or ledger
|
||||
# entry type show up in the diff below rather than silently as an
|
||||
# empty one.
|
||||
git add --intent-to-add .
|
||||
DIFF=$(git status --porcelain)
|
||||
if [ -n "${DIFF}" ]; then
|
||||
# Print the differences to give the contributor a hint about what to
|
||||
# expect when running code generation on their own machine.
|
||||
git diff
|
||||
echo "${MESSAGE}"
|
||||
exit 1
|
||||
fi
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Check levelization
|
||||
run: python .github/scripts/levelization/generate.py
|
||||
- name: Check for differences
|
||||
|
||||
2
.github/workflows/reusable-check-rename.yml
vendored
2
.github/workflows/reusable-check-rename.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Check definitions
|
||||
run: .github/scripts/rename/definitions.sh .
|
||||
- name: Check copyright notices
|
||||
|
||||
21
.github/workflows/reusable-clang-tidy.yml
vendored
21
.github/workflows/reusable-clang-tidy.yml
vendored
@@ -27,23 +27,23 @@ jobs:
|
||||
determine-files:
|
||||
permissions:
|
||||
contents: read
|
||||
uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@d041ac9f1fa9f07a4ba335eb4c1c82233fb3fef6
|
||||
uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@70145243b905dc3e040a61d39c00e178cfb96f71
|
||||
|
||||
run-clang-tidy:
|
||||
name: Run clang tidy
|
||||
needs: [determine-files]
|
||||
if: ${{ needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.need_full_run == 'true' }}
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-e29b523"
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-060957e"
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@64ec3cf3b152b4444638f470bbd6df7a7a30c81c
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
@@ -59,6 +59,12 @@ jobs:
|
||||
with:
|
||||
compiler: ${{ env.COMPILER }}
|
||||
|
||||
- name: Use cargo artifacts cache
|
||||
uses: ./.github/actions/cargo-cache
|
||||
with:
|
||||
cache-directories: ${{ env.BUILD_DIR }}/corrosion
|
||||
workspaces: crates -> ../${{ env.BUILD_DIR }}/cargo
|
||||
|
||||
- name: Setup Conan
|
||||
uses: ./.github/actions/setup-conan
|
||||
|
||||
@@ -82,11 +88,10 @@ jobs:
|
||||
-Dverify_headers=ON \
|
||||
..
|
||||
|
||||
# clang-tidy needs headers generated from proto files
|
||||
- name: Build libxrpl.libpb
|
||||
- name: Build clang-tidy prerequisites
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
ninja -j ${{ steps.nproc.outputs.nproc }} xrpl.libpb
|
||||
ninja -j ${{ steps.nproc.outputs.nproc }} tidy_prerequisites
|
||||
|
||||
- name: Run clang tidy
|
||||
id: run_clang_tidy
|
||||
@@ -95,7 +100,7 @@ jobs:
|
||||
TARGETS: ${{ needs.determine-files.outputs.need_full_run != 'true' && needs.determine-files.outputs.cpp_changed_files || 'include src tests' }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" -quiet -fix -allow-no-checks ${TARGETS} 2>&1 | tee "${OUTPUT_FILE}"
|
||||
run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" -quiet -fix -format -allow-no-checks ${TARGETS} 2>&1 | tee "${OUTPUT_FILE}"
|
||||
|
||||
- name: Print filtered clang-tidy errors
|
||||
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
|
||||
|
||||
288
.github/workflows/reusable-package.yml
vendored
288
.github/workflows/reusable-package.yml
vendored
@@ -1,17 +1,40 @@
|
||||
# Build Linux packages (DEB and RPM) from pre-built binary artifacts.
|
||||
# Discovers which configurations to package from linux.json (configs in
|
||||
# "package_configs") and fans out one job per distro. Only linux/amd64 is
|
||||
# supported; the runner is hardcoded in the job below.
|
||||
# Build, verify and publish Linux packages from the pre-built xrpld and
|
||||
# validator-keys artifacts, in three stages:
|
||||
#
|
||||
# - 'package' builds and signs one format per config that carries a "package"
|
||||
# map in linux.json; that map names the container image and the format
|
||||
# - 'test-install' installs what was built on a range of distros and runs the
|
||||
# binaries there, so a package that cannot be installed never reaches Nexus
|
||||
# - 'publish' uploads with the image's publish_pkg.py, doing a --dry-run
|
||||
# unless 'publish: true'
|
||||
#
|
||||
# Only linux/amd64 is supported; the runner is hardcoded in the jobs below.
|
||||
name: Package
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
pkg_release:
|
||||
description: "Package release number. Increment when repackaging the same executable."
|
||||
publish:
|
||||
description: "Whether to publish the packages after building them."
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
nexus_url:
|
||||
description: "The base URL of the Nexus instance hosting the deb and rpm repositories."
|
||||
required: false
|
||||
type: string
|
||||
default: "1"
|
||||
default: https://packages.xrplf.org
|
||||
|
||||
secrets:
|
||||
remote_username:
|
||||
description: "The username of a Nexus account with write access to the repositories."
|
||||
required: false
|
||||
remote_password:
|
||||
description: "The password or token for that Nexus account."
|
||||
required: false
|
||||
signing_key:
|
||||
description: "Armoured PGP private key used to sign the RPMs. Required when publishing."
|
||||
required: false
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -19,6 +42,7 @@ defaults:
|
||||
|
||||
env:
|
||||
BUILD_DIR: build
|
||||
PACKAGE_DIR: packages
|
||||
|
||||
jobs:
|
||||
generate-matrix:
|
||||
@@ -27,10 +51,10 @@ jobs:
|
||||
matrix: ${{ steps.generate.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
|
||||
@@ -41,11 +65,208 @@ jobs:
|
||||
|
||||
package:
|
||||
needs: [generate-matrix]
|
||||
if: ${{ github.event.repository.visibility == 'public' }}
|
||||
if: ${{ github.event.repository.visibility == 'public' || startsWith(github.ref, 'refs/tags/') }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
||||
name: "${{ matrix.artifact_name }}"
|
||||
name: "${{ matrix.xrpld_artifact_name }}"
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: ${{ matrix.image }}
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
- name: Download pre-built xrpld binary
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: ${{ matrix.xrpld_artifact_name }}
|
||||
path: ${{ env.BUILD_DIR }}
|
||||
|
||||
- name: Download pre-built validator-keys binary
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: ${{ matrix.validator_keys_artifact_name }}
|
||||
path: ${{ env.BUILD_DIR }}
|
||||
|
||||
- name: Make binaries executable
|
||||
run: chmod +x "${BUILD_DIR}/xrpld" "${BUILD_DIR}/validator-keys"
|
||||
|
||||
- name: Determine release info
|
||||
id: release_info
|
||||
uses: ./.github/actions/release-info
|
||||
|
||||
- name: Build package
|
||||
env:
|
||||
PACKAGE_TYPE: ${{ matrix.package_type }}
|
||||
PKG_RELEASE: ${{ steps.release_info.outputs.pkg_release }}
|
||||
CHANNEL: ${{ steps.release_info.outputs.channel }}
|
||||
run: |
|
||||
./package/build_pkg.py \
|
||||
--package-type "${PACKAGE_TYPE}" \
|
||||
--build-dir "${BUILD_DIR}" \
|
||||
--pkg-release "${PKG_RELEASE}" \
|
||||
--channel "${CHANNEL}"
|
||||
|
||||
# Before the upload, so the artifact, the tested package and the published
|
||||
# package are the same bytes.
|
||||
- name: Sign RPM
|
||||
if: ${{ inputs.publish && matrix.package_type == 'rpm' }}
|
||||
env:
|
||||
PKG_SIGNING_KEY: ${{ secrets.signing_key }}
|
||||
run: ./package/sign_rpm.py --package-dir "${BUILD_DIR}"
|
||||
|
||||
# Split from the debug symbols, which are an order of magnitude larger, so
|
||||
# that test-install downloads only what it installs.
|
||||
- name: Upload package artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ matrix.xrpld_artifact_name }}-pkg
|
||||
path: |
|
||||
${{ env.BUILD_DIR }}/debbuild/xrpld_*.deb
|
||||
${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/xrpld-[0-9]*.rpm
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload debug symbol artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ matrix.xrpld_artifact_name }}-pkg-debug
|
||||
path: |
|
||||
${{ env.BUILD_DIR }}/debbuild/xrpld-dbgsym_*.deb
|
||||
${{ env.BUILD_DIR }}/debbuild/xrpld-dbgsym_*.ddeb
|
||||
${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/xrpld-debuginfo-*.rpm
|
||||
if-no-files-found: error
|
||||
|
||||
# Every distro family the packages target, oldest release first, so both ends
|
||||
# of the dependency range they declare are exercised.
|
||||
test-install:
|
||||
needs: [package]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- package_type: deb
|
||||
image: debian:11
|
||||
- package_type: deb
|
||||
image: debian:12
|
||||
- package_type: deb
|
||||
image: debian:13
|
||||
- package_type: deb
|
||||
image: ubuntu:20.04
|
||||
- package_type: deb
|
||||
image: ubuntu:22.04
|
||||
- package_type: deb
|
||||
image: ubuntu:24.04
|
||||
- package_type: deb
|
||||
image: ubuntu:26.04
|
||||
|
||||
- package_type: rpm
|
||||
image: almalinux:9
|
||||
- package_type: rpm
|
||||
image: almalinux:10
|
||||
- package_type: rpm
|
||||
image: rockylinux/rockylinux:9
|
||||
- package_type: rpm
|
||||
image: rockylinux/rockylinux:10
|
||||
- package_type: rpm
|
||||
image: registry.access.redhat.com/ubi9/ubi
|
||||
- package_type: rpm
|
||||
image: registry.access.redhat.com/ubi10/ubi
|
||||
name: "install ${{ matrix.package_type }} on ${{ matrix.image }}"
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
container: ${{ matrix.image }}
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
# Both formats land in one directory; the step below picks its own by
|
||||
# extension, so this stays independent of the artifact names.
|
||||
- name: Download package artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: "*-pkg"
|
||||
merge-multiple: true
|
||||
path: ${{ env.PACKAGE_DIR }}
|
||||
|
||||
- name: Find the package
|
||||
id: find
|
||||
env:
|
||||
PACKAGE_TYPE: ${{ matrix.package_type }}
|
||||
run: |
|
||||
package="$(find "${PACKAGE_DIR}" -type f -name "*.${PACKAGE_TYPE}" -print -quit)"
|
||||
test -n "${package}" || {
|
||||
echo "no .${PACKAGE_TYPE} found in ${PACKAGE_DIR}" >&2
|
||||
exit 1
|
||||
}
|
||||
echo "package=${package}" >>"${GITHUB_OUTPUT}"
|
||||
|
||||
# Debian 11 went end-of-life on 2026-08-31
|
||||
# (https://www.debian.org/News/2026/20260831) and its packages are
|
||||
# already partly gone from deb.debian.org, so switch to the
|
||||
# snapshot.debian.org entries the image ships commented out in its
|
||||
# sources.list: they are pinned to the snapshot the image was built
|
||||
# from, so they serve every version it needs and never go away.
|
||||
# Snapshots keep their original, long-passed Valid-Until, hence the
|
||||
# disabled check; the retries absorb snapshot.debian.org's throttling.
|
||||
- name: Switch Debian 11 to snapshot.debian.org
|
||||
if: ${{ matrix.image == 'debian:11' }}
|
||||
run: |
|
||||
sed -i 's|^deb |# deb |; s|^# deb http://snapshot|deb http://snapshot|' /etc/apt/sources.list
|
||||
printf '%s\n' \
|
||||
'Acquire::Check-Valid-Until "false";' \
|
||||
'Acquire::Retries "3";' \
|
||||
>/etc/apt/apt.conf.d/99snapshot
|
||||
|
||||
- name: Install the DEB
|
||||
if: ${{ matrix.package_type == 'deb' }}
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
PACKAGE: ${{ steps.find.outputs.package }}
|
||||
run: |
|
||||
# Stock Debian and Ubuntu images carry no package lists, so apt has
|
||||
# nothing to resolve the systemd dependency from until it fetches them.
|
||||
apt-get update -qq
|
||||
apt-get install -y "./${PACKAGE}"
|
||||
|
||||
- name: Install the RPM
|
||||
if: ${{ matrix.package_type == 'rpm' }}
|
||||
env:
|
||||
PACKAGE: ${{ steps.find.outputs.package }}
|
||||
run: dnf install -y "./${PACKAGE}"
|
||||
|
||||
- name: Run xrpld
|
||||
run: xrpld --version
|
||||
|
||||
- name: Run validator-keys
|
||||
run: validator-keys --version
|
||||
|
||||
- name: Run rippled, the legacy compatibility symlink
|
||||
run: rippled --version
|
||||
|
||||
- name: Check the service account
|
||||
run: id xrpld
|
||||
|
||||
- name: Check the state directory
|
||||
run: test -d /var/lib/xrpld
|
||||
|
||||
- name: Check the log directory
|
||||
run: test -d /var/log/xrpld
|
||||
|
||||
publish:
|
||||
needs: [generate-matrix, package, test-install]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
||||
name: "publish ${{ matrix.xrpld_artifact_name }}"
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
@@ -54,28 +275,35 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Download pre-built binary
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
# Both artifacts, so the debug symbols are published alongside the package.
|
||||
- name: Download package artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
path: ${{ env.BUILD_DIR }}
|
||||
pattern: ${{ matrix.xrpld_artifact_name }}-pkg*
|
||||
merge-multiple: true
|
||||
path: ${{ env.PACKAGE_DIR }}
|
||||
|
||||
- name: Make binary executable
|
||||
run: chmod +x "${BUILD_DIR}/xrpld"
|
||||
- name: Determine release info
|
||||
id: release_info
|
||||
uses: ./.github/actions/release-info
|
||||
|
||||
- name: Build package
|
||||
- name: Publish package
|
||||
env:
|
||||
PKG_RELEASE: ${{ inputs.pkg_release }}
|
||||
run: ./package/build_pkg.sh
|
||||
|
||||
- name: Upload package artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}-pkg
|
||||
path: |
|
||||
${{ env.BUILD_DIR }}/debbuild/*.deb
|
||||
${{ env.BUILD_DIR }}/debbuild/*.ddeb
|
||||
${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/*.rpm
|
||||
if-no-files-found: error
|
||||
CHANNEL: ${{ steps.release_info.outputs.channel }}
|
||||
DRY_RUN_OPTION: ${{ !inputs.publish && '--dry-run' || '' }}
|
||||
NEXUS_URL: ${{ inputs.nexus_url }}
|
||||
NEXUS_USERNAME: ${{ inputs.publish && secrets.remote_username || '' }}
|
||||
NEXUS_PASSWORD: ${{ inputs.publish && secrets.remote_password || '' }}
|
||||
run: |
|
||||
publish_pkg.py \
|
||||
--channel "${CHANNEL}" \
|
||||
--package-dir "${PACKAGE_DIR}" \
|
||||
--nexus-url "${NEXUS_URL}" \
|
||||
${DRY_RUN_OPTION}
|
||||
|
||||
84
.github/workflows/reusable-rust.yml
vendored
Normal file
84
.github/workflows/reusable-rust.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
# Clippy, coverage and documentation for the Rust crates in crates/. Each runs
|
||||
# as an independent job on a GitHub-hosted runner, but inside the same container
|
||||
# image used to build the crates in the C++/Corrosion path, so the toolchain
|
||||
# (and therefore the lints and the cargo cache) matches what production builds
|
||||
# use. Coverage is the exception: it needs the nightly rustc that honours
|
||||
# #[coverage(off)], which the image carries alongside the pinned stable.
|
||||
#
|
||||
# Rust unit tests are deliberately NOT run here. They run as part of the C++
|
||||
# build (reusable-build-test-config.yml), which already compiles the crates on a
|
||||
# self-hosted runner, so there is no need to provision a toolchain again.
|
||||
name: Rust
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
CODECOV_TOKEN:
|
||||
description: "The Codecov token to use for uploading coverage reports."
|
||||
required: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: crates
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Use cargo artifacts cache
|
||||
uses: ./.github/actions/cargo-cache
|
||||
|
||||
- name: Run clippy
|
||||
run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
|
||||
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Use the nightly Rust toolchain
|
||||
run: rust-nightly path >>"${GITHUB_PATH}"
|
||||
|
||||
- name: Use cargo artifacts cache
|
||||
uses: ./.github/actions/cargo-cache
|
||||
|
||||
- name: Generate coverage report
|
||||
run: cargo llvm-cov nextest --workspace --all-features --locked --no-tests=warn --lcov --output-path lcov.info
|
||||
|
||||
- name: Upload coverage report
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
disable_search: true
|
||||
disable_telem: true
|
||||
fail_ci_if_error: true
|
||||
files: crates/lcov.info
|
||||
flags: rust
|
||||
plugins: noop
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
||||
|
||||
doc:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Use cargo artifacts cache
|
||||
uses: ./.github/actions/cargo-cache
|
||||
|
||||
- name: Build documentation
|
||||
env:
|
||||
RUSTDOCFLAGS: "-D warnings"
|
||||
run: cargo doc --workspace --no-deps --all-features --locked
|
||||
@@ -23,10 +23,10 @@ jobs:
|
||||
matrix: ${{ steps.generate.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
|
||||
|
||||
21
.github/workflows/reusable-upload-recipe.yml
vendored
21
.github/workflows/reusable-upload-recipe.yml
vendored
@@ -40,18 +40,23 @@ defaults:
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-e29b523
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.remote_username }}
|
||||
CONAN_PASSWORD_XRPLF: ${{ secrets.remote_password }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Generate build version number
|
||||
id: version
|
||||
uses: ./.github/actions/generate-version
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
- name: Determine release info
|
||||
id: release_info
|
||||
uses: ./.github/actions/release-info
|
||||
|
||||
- name: Set up Conan
|
||||
uses: ./.github/actions/setup-conan
|
||||
@@ -64,8 +69,8 @@ jobs:
|
||||
|
||||
- name: Upload Conan recipe (version)
|
||||
run: |
|
||||
conan export . --version=${{ steps.version.outputs.version }}
|
||||
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/${{ steps.version.outputs.version }}
|
||||
conan export . --version=${{ steps.release_info.outputs.version }}
|
||||
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/${{ steps.release_info.outputs.version }}
|
||||
|
||||
# When this workflow is triggered by a push event, it will always be when merging into the
|
||||
# 'develop' branch, see on-trigger.yml.
|
||||
@@ -92,4 +97,4 @@ jobs:
|
||||
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/release
|
||||
|
||||
outputs:
|
||||
ref: xrpl/${{ steps.version.outputs.version }}
|
||||
ref: xrpl/${{ steps.release_info.outputs.version }}
|
||||
|
||||
15
.github/workflows/upload-conan-deps.yml
vendored
15
.github/workflows/upload-conan-deps.yml
vendored
@@ -65,13 +65,18 @@ jobs:
|
||||
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@64ec3cf3b152b4444638f470bbd6df7a7a30c81c
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
# Before any step that uses a build tool, composite actions included.
|
||||
- name: Setup Nix environment
|
||||
if: ${{ matrix.toolchain == 'nix' }}
|
||||
uses: ./.github/actions/setup-nix-env
|
||||
|
||||
- name: Print build environment
|
||||
uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574
|
||||
|
||||
@@ -87,7 +92,9 @@ jobs:
|
||||
with:
|
||||
compiler: ${{ matrix.compiler }}
|
||||
|
||||
# `setup-nix-env` already did this for the Nix toolchain.
|
||||
- name: Setup Conan
|
||||
if: ${{ matrix.toolchain != 'nix' }}
|
||||
env:
|
||||
SANITIZERS: ${{ matrix.sanitizers }}
|
||||
uses: ./.github/actions/setup-conan
|
||||
@@ -106,6 +113,10 @@ jobs:
|
||||
log_verbosity: ${{ runner.os == 'Windows' && 'quiet' || 'verbose' }}
|
||||
sanitizers: ${{ matrix.sanitizers }}
|
||||
|
||||
- name: Check the Conan cache for Nix store references (Nix toolchain)
|
||||
if: ${{ matrix.toolchain == 'nix' }}
|
||||
run: ./bin/check-nix-store-refs.sh "${CONAN_HOME}"
|
||||
|
||||
- name: Log into Conan remote
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }}
|
||||
run: conan remote login "${CONAN_REMOTE_NAME}" "${{ secrets.NEXUS_REMOTE_USERNAME }}" --password "${{ secrets.NEXUS_REMOTE_PASSWORD }}"
|
||||
|
||||
15
.gitignore
vendored
15
.gitignore
vendored
@@ -72,17 +72,28 @@ DerivedData
|
||||
/.zed/
|
||||
|
||||
# AI tools.
|
||||
# Shared/committable AI agent config (AGENTS.md, CLAUDE.md, GEMINI.md, .claude/settings.json,
|
||||
# tool-specific rules files, etc.) should be checked in — see CONTRIBUTING.md. Only the
|
||||
# personal/local variants below are ignored.
|
||||
/.agent
|
||||
/.agents
|
||||
/.augment
|
||||
/.claude
|
||||
/CLAUDE.md
|
||||
/.claude/settings.local.json
|
||||
AGENTS.override.md
|
||||
CLAUDE.local.md
|
||||
GEMINI.local.md
|
||||
|
||||
# Python
|
||||
__pycache__
|
||||
|
||||
# Rust build artifacts.
|
||||
target/
|
||||
|
||||
# Direnv's directory
|
||||
/.direnv
|
||||
|
||||
# clangd cache
|
||||
/.cache
|
||||
|
||||
# Rust build directory
|
||||
crates/target
|
||||
|
||||
@@ -55,29 +55,59 @@ repos:
|
||||
types_or: [c++, c]
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: dd18dad857d6133e90bbe478f4f2f22ec0030269 # frozen: v22.1.5
|
||||
rev: f4d7745e17a28aad7eed2f4874ca8d1568c11c4c # frozen: v22.1.8
|
||||
hooks:
|
||||
- id: clang-format
|
||||
args: [--style=file]
|
||||
types_or: [c++, c, proto]
|
||||
exclude: ^include/xrpl/protocol_autogen/(transactions|ledger_entries)/
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: cargo-fmt
|
||||
name: cargo fmt
|
||||
entry: cargo fmt --manifest-path crates/Cargo.toml --all
|
||||
language: system
|
||||
types: [rust]
|
||||
pass_filenames: false # rustfmt formats the whole workspace
|
||||
- id: check-coverage-attrs
|
||||
name: check Rust coverage attributes
|
||||
entry: ./bin/pre-commit/check_rust_coverage_attrs.py
|
||||
language: python
|
||||
files: ^crates/.*\.rs$
|
||||
|
||||
- repo: https://github.com/BlankSpruce/gersemi-pre-commit
|
||||
rev: e98930bdc210d3387007f9252d8c1694ea7e410f # frozen: 0.27.7
|
||||
hooks:
|
||||
- id: gersemi
|
||||
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: 39e2973981e6d2f9b6c543b0086a2d2393abdc89 # frozen: v3.9.4
|
||||
rev: 9337a74165b178ae2c766f60bee7252a0f06f3e8 # frozen: v3.9.5
|
||||
hooks:
|
||||
- id: prettier
|
||||
args: [--end-of-line=auto]
|
||||
|
||||
# Scoped to package/: the rest of the repo's Python has pre-existing findings,
|
||||
# so widening these is its own change.
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: 7c55798a78262d14b2074abf623d8a992ebb70d4 # frozen: v0.16.2
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
args: [--fix]
|
||||
files: ^package/.*\.py$
|
||||
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 4160603246a6b365d4a2af661c6d71b0a0f50478 # frozen: 26.5.1
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: 41e691678310dfd3833f7ab4e180ddb014310356 # frozen: v2.3.0
|
||||
hooks:
|
||||
- id: mypy
|
||||
args: [--strict]
|
||||
files: ^package/.*\.py$
|
||||
|
||||
- repo: https://github.com/scop/pre-commit-shfmt
|
||||
rev: 05c1426671b9237fb5e1444dd63aa5731bec0dfb # frozen: v3.13.1-1
|
||||
hooks:
|
||||
|
||||
42
AGENTS.md
Normal file
42
AGENTS.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# AGENTS.md
|
||||
|
||||
This file provides guidance to AI coding agents (Claude Code, and other AGENTS.md-compatible tools) when working with code in this repository.
|
||||
|
||||
## Build
|
||||
|
||||
Required on Linux/macOS: use the Nix devshell, which sets up the compiler, Conan, ccache, and (optionally) Rust automatically.
|
||||
|
||||
```bash
|
||||
nix develop
|
||||
```
|
||||
|
||||
For alternate devshell variants (specific compiler, no-compiler, coverage), see [docs/build/nix.md](./docs/build/nix.md). For the manual build steps, CMake options, and protocol codegen commands, see [BUILD.md](./BUILD.md) (`## Steps`, `## Options`, `## Code generation`).
|
||||
|
||||
Rust crate tests (independent of the CMake build): `cargo test --manifest-path crates/Cargo.toml --workspace` (CI uses `cargo nextest`).
|
||||
|
||||
## Testing
|
||||
|
||||
Unit tests are a custom framework built into the `xrpld` binary itself (not Boost.Test/GTest/Catch); see [CONTRIBUTING.md](./CONTRIBUTING.md#unit-tests) for the basic invocation. Notes not covered there:
|
||||
|
||||
- A suite's `--unittest` name is built from the arguments to its `BEAST_DEFINE_TESTSUITE`/`BEAST_DEFINE_TESTSUITE_PRIO` macro (usually at the bottom of the test file), in reverse order and joined with `.`: `BEAST_DEFINE_TESTSUITE(Credentials, app, xrpl)` → `xrpl.app.Credentials`.
|
||||
- `--unittest-arg` does nothing — don't use it.
|
||||
- Tests that run offline in under a minute should be automatic `--unittest` suites; anything else is a manual/integration test.
|
||||
- New tests should be written using `gtest` under `src/tests/` unless that isn't possible, in which case fall back to the legacy Beast framework under `src/test/`. `tests/` (top-level) holds integration tests exercised against `libxrpl`/`xrpld`.
|
||||
|
||||
## Lint/Format
|
||||
|
||||
See [CONTRIBUTING.md](./CONTRIBUTING.md#pre-commit-hooks) for `pre-commit` setup and [CONTRIBUTING.md](./CONTRIBUTING.md#clang-tidy) for `clang-tidy` (opt-in, needs local `clang-tidy` and generated headers).
|
||||
|
||||
## Code Style
|
||||
|
||||
New file placement and header levelization: see [CONTRIBUTING.md](./CONTRIBUTING.md#before-making-a-pull-request). Braces, whitespace, member order, and other conventions: see [docs/CodingStyle.md](./docs/CodingStyle.md). `XRPL_ASSERT`/`UNREACHABLE` contracts: see [CONTRIBUTING.md](./CONTRIBUTING.md#contracts-and-instrumentation). Commit messages: see [CONTRIBUTING.md](./CONTRIBUTING.md#good-commit-messages).
|
||||
|
||||
## Architecture
|
||||
|
||||
Paths below reflect the current layout; update this section if modularization moves a subsystem to a different directory.
|
||||
|
||||
- `include/xrpl/` + `src/libxrpl/` — the core protocol library: ledger, shamap, consensus, crypto, json, resource, nodestore, rdb, peerfinder, and `tx/` (transaction application: `Transactor.cpp`, `applySteps.cpp`, invariants, payment paths). `tx/transactors/` has one file per transaction type, grouped by subsystem: `escrow/`, `vault/`, `lending/`, `sponsor/`, `nft/`, `token/` (MPT), `payment_channel/`, `permissioned_domain/`, `dex/`, `oracle/`, `did/`, `credentials/`, `bridge/`, `check/`, `delegate/`, `account/`, `system/`. Any change to transaction-processing behavior must be gated behind an Amendment.
|
||||
- `src/xrpld/` — the server application built on top of `libxrpl`: `app`, `core`, `overlay` (P2P networking), `peerfinder`, `perflog`, `rpc`, `shamap`. `main` builds an `ApplicationImp` implementing `Application`; most components hold a reference to it (`app_`), giving broad cross-component access — expect to trace call chains through `Application&`.
|
||||
- `src/test/` — unit tests mirroring the subsystems above, plus `jtx/` (the transaction-building test DSL — e.g. `jtx/escrow.h`, `jtx/vault.h`, `jtx/sponsor.h`, `jtx/permissioned_dex.h`) and `unit_test/` (the custom test framework itself, derived from Beast).
|
||||
- `src/tests/` — unit tests for `libxrpl` written in `gtest`, gradually replacing the `src/test` equivalents.
|
||||
- `crates/` — a Rust workspace holding the WebAssembly engine that runs Smart Escrow contracts, bridged into C++ via `cxxbridge`/the `cxx` crate; see [crates/README.md](./crates/README.md) for more details.
|
||||
@@ -22,17 +22,47 @@ API version 2 is available in `xrpld` version 2.0.0 and later. See [API-VERSION-
|
||||
|
||||
This version is supported by all `xrpld` versions. For WebSocket and HTTP JSON-RPC requests, it is currently the default API version used when no `api_version` is specified.
|
||||
|
||||
## Unreleased
|
||||
## XRP Ledger server version 3.4.0
|
||||
|
||||
This section contains changes targeting a future version.
|
||||
Version 3.4.0 is not yet released. These changes are available in the 3.4.0 beta releases.
|
||||
|
||||
### Additions
|
||||
### Additions in 3.4.0
|
||||
|
||||
- `account_tx`: Added an optional `delegate` request object to filter delegated transactions. The object requires `delegate_filter`, which must be either `actor` for transactions owned by the requested account but signed by another account, or `authorizer` for transactions signed by the requested account on behalf of another account. The optional `counter_party` account narrows the results to a specific signer/delegate for `actor` or a specific owner/delegator for `authorizer`. Malformed `delegate`, `delegate_filter`, and `counter_party` values return standard invalid field errors, and invalid account IDs return `actMalformed`.
|
||||
When paginating delegate-filtered queries, a marker from a delegate-filtered query includes a `delegate` flag and is only valid for follow-up requests that also supply `delegate` (mixing marker conventions returns `invalidParams`). Because filtering is applied after the ledger scan, a page may contain fewer results than `limit` (possibly zero) while still returning a marker, so callers must continue until no marker is present.
|
||||
- `ledger`: `nftoken_id`, `nftoken_ids`, and `offer_id` are now included in transaction metadata when transactions are expanded (`expand`, or admin-only `full`), matching the `tx`, `account_tx`, and `subscribe` (`transactions` stream) responses. ([#5706](https://github.com/XRPLF/rippled/pull/5706))
|
||||
|
||||
### Bugfixes in 3.4.0
|
||||
|
||||
- `sign`, `sign_for`, `submit`: `signature_target` now returns `invalidParams` unless it names `CounterpartySignature` or `SponsorSignature`. It previously accepted any inner object field, such as `Book` or `NFToken`, and signed into it.
|
||||
- `sign`, `sign_for`, `submit`, `submit_multisigned`: With `fixCleanup3_4_0` enabled, a signature in `CounterpartySignature` or `SponsorSignature` covers a different prefix than the transaction's own signature, so a signature can no longer be moved from one of those roles into another. Clients that build these signatures themselves must use the new prefixes: `CPT` and `CPM` (single- and multi-signing) for `CounterpartySignature`, and `SPN` and `SPM` for `SponsorSignature`.
|
||||
- `get_aggregate_price`: Duplicate entries in the `oracles` request array are now ignored. [#6586](https://github.com/XRPLF/rippled/pull/6586)
|
||||
- `vault_info`: Errors now identify what the request got wrong instead of reporting every failure as the unregistered token `malformedRequest`, and the `error`, `error_code` and `error_message` fields now agree with each other. An invalid `vault_id` or `seq` returns `invalidParams`, an invalid `owner` returns `actMalformed`, and a request that mixes `vault_id` with `owner`/`seq` or supplies neither returns `invalidParams` with a message naming the accepted combinations. [#8015](https://github.com/XRPLF/rippled/pull/8015)
|
||||
- `vault_info`: A well-formed all-zero `vault_id` now returns `entryNotFound` instead of being rejected as malformed, and `entryNotFound` responses now include `error_code` and `error_message`. Clients that request `ripplerpc` 3.0 or above therefore receive HTTP 400 with that error rather than HTTP 200. [#8015](https://github.com/XRPLF/rippled/pull/8015)
|
||||
- `vault_info`: `vault_id` and `owner` must now be strings, matching how `ledger_entry` reads the same fields. An object or an array in either field previously produced an internal error, and a number was silently converted to its decimal text; `vault_id` now returns `invalidParams` and `owner` returns `actMalformed`. [#8015](https://github.com/XRPLF/rippled/pull/8015)
|
||||
- `gateway_balances`: The `account` and `ident` fields now return an `invalidParams` error if the value is not a string, instead of an `internal` error. [#7655](https://github.com/XRPLF/rippled/pull/7655)
|
||||
- `account_lines`: The `peer` field now returns an error if the value is not a string. [#7728](https://github.com/XRPLF/rippled/pull/7728)
|
||||
- `ledger`: `delivered_amount` is now included in the metadata of successful `AccountDelete` transactions when transactions are expanded (`expand`, or admin-only `full`). Previously it was only added for `Payment` and `CheckCash`, which made `ledger` inconsistent with `tx` and `account_tx`. [#5706](https://github.com/XRPLF/rippled/pull/5706)
|
||||
|
||||
## XRP Ledger server version 3.3.0
|
||||
|
||||
[Version 3.3.0](https://github.com/XRPLF/rippled/releases/tag/3.3.0) was released on Aug 6, 2026.
|
||||
|
||||
### Additions in 3.3.0
|
||||
|
||||
- `account_tx`: Added an optional `delegate` request object to filter delegated transactions. The object requires `delegate_filter`, which must be either `actor` for transactions owned by the requested account but signed by another account, or `authorizer` for transactions signed by the requested account on behalf of another account. The optional `counter_party` account narrows the results to a specific signer/delegate for `actor` or a specific owner/delegator for `authorizer`. Malformed `delegate`, `delegate_filter`, and `counter_party` values return standard invalid field errors, and invalid account IDs return `actMalformed`. When paginating delegate-filtered queries, a marker from a delegate-filtered query includes a `delegate` flag and is only valid for follow-up requests that also supply `delegate` (mixing marker conventions returns `invalidParams`). Because filtering is applied after the ledger scan, a page may contain fewer results than `limit` (possibly zero) while still returning a marker, so callers must continue until no marker is present. ([#6126](https://github.com/XRPLF/rippled/pull/6126))
|
||||
|
||||
## XRP Ledger server version 3.2.1
|
||||
|
||||
[Version 3.2.1](https://github.com/XRPLF/rippled/releases/tag/3.2.1) was released on Aug 1, 2026.
|
||||
|
||||
This release contains bug fixes only and no API changes.
|
||||
|
||||
## XRP Ledger server version 3.2.0
|
||||
|
||||
[Version 3.2.0](https://github.com/XRPLF/rippled/releases/tag/3.2.0) was released on Jun 16, 2026.
|
||||
|
||||
### Additions in 3.2.0
|
||||
|
||||
- `ledger_entry`, `account_objects`: The `Delegate` ledger entry now includes an optional `DestinationNode` field, which stores the index into the authorized account's owner directory. This field is present on entries created after bidirectional directory tracking was introduced and may appear in RPC responses for those entries. ([#6681](https://github.com/XRPLF/rippled/pull/6681))
|
||||
|
||||
- `server_definitions`: Added the following new sections to the response ([#6321](https://github.com/XRPLF/rippled/pull/6321)):
|
||||
- `TRANSACTION_FORMATS`: Describes the fields and their optionality for each transaction type, including common fields shared across all transactions.
|
||||
- `LEDGER_ENTRY_FORMATS`: Describes the fields and their optionality for each ledger entry type, including common fields shared across all ledger entries.
|
||||
@@ -40,7 +70,7 @@ This section contains changes targeting a future version.
|
||||
- `LEDGER_ENTRY_FLAGS`: Maps ledger entry type names to their flags and flag values.
|
||||
- `ACCOUNT_SET_FLAGS`: Maps AccountSet flag names (asf flags) to their numeric values.
|
||||
|
||||
### Bugfixes
|
||||
### Bugfixes in 3.2.0
|
||||
|
||||
- Peer Crawler: The `port` field in `overlay.active[]` now consistently returns an integer instead of a string for outbound peers. [#6318](https://github.com/XRPLF/rippled/pull/6318)
|
||||
- `ping`: The `ip` field is no longer returned as an empty string for proxied connections without a forwarded-for header. It is now omitted, consistent with the behavior for identified connections. [#6730](https://github.com/XRPLF/rippled/pull/6730)
|
||||
@@ -54,6 +84,24 @@ This section contains changes targeting a future version.
|
||||
- `subscribe`: The `taker` field in the `books` array now returns `actMalformed` instead of `badIssuer` if the value is not a valid account. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- Fixed a bug in `Forwarded` HTTP header parsing where the extracted IP address could be incorrect when no comma or semicolon delimiter follows the address. This could cause the server to misidentify a client's IP address when operating behind a reverse proxy. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
|
||||
## XRP Ledger server version 3.1.3
|
||||
|
||||
[Version 3.1.3](https://github.com/XRPLF/rippled/releases/tag/3.1.3) was released on May 8, 2026.
|
||||
|
||||
This release contains bug fixes only and no API changes.
|
||||
|
||||
## XRP Ledger server version 3.1.2
|
||||
|
||||
[Version 3.1.2](https://github.com/XRPLF/rippled/releases/tag/3.1.2) was released on Mar 12, 2026.
|
||||
|
||||
This release contains bug fixes only and no API changes.
|
||||
|
||||
## XRP Ledger server version 3.1.1
|
||||
|
||||
[Version 3.1.1](https://github.com/XRPLF/rippled/releases/tag/3.1.1) was released on Feb 23, 2026.
|
||||
|
||||
This release contains bug fixes only and no API changes.
|
||||
|
||||
## XRP Ledger server version 3.1.0
|
||||
|
||||
[Version 3.1.0](https://github.com/XRPLF/rippled/releases/tag/3.1.0) was released on Jan 27, 2026.
|
||||
|
||||
122
BUILD.md
122
BUILD.md
@@ -1,37 +1,17 @@
|
||||
| :warning: **WARNING** :warning: |
|
||||
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| These instructions assume you have a C++ development environment ready with Git, Python, Conan, CMake, and a C++ compiler. For help setting one up on Linux, macOS, or Windows, [see this guide](./docs/build/environment.md).<br><br>These instructions also assume a basic familiarity with Conan and CMake. If you are unfamiliar with Conan, you can read our [crash course](./docs/build/conan.md) or the official [Getting Started][conan-getting-started] walkthrough. |
|
||||
| :warning: **WARNING** :warning: |
|
||||
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| These instructions assume you have a C++ development environment ready with Git, Python, Conan, CMake, Rust, and a C++ compiler. For help setting one up on Linux, macOS, or Windows, [see this guide](./docs/build/environment.md).<br><br>These instructions also assume a basic familiarity with Conan and CMake. If you are unfamiliar with Conan, you can read our [crash course](./docs/build/conan.md) or the official [Getting Started][conan-getting-started] walkthrough. |
|
||||
|
||||
## Minimum Requirements
|
||||
|
||||
See [System Requirements](https://xrpl.org/system-requirements.html).
|
||||
For the hardware needed to run a node, see
|
||||
[System Requirements](https://xrpl.org/system-requirements.html).
|
||||
|
||||
Building xrpld generally requires Git, Python, Conan, CMake, and a C++
|
||||
compiler.
|
||||
|
||||
- [Python](https://www.python.org/downloads/)
|
||||
- [Conan](https://conan.io/downloads.html)
|
||||
- [CMake](https://cmake.org/download/)
|
||||
|
||||
You can verify that the required tools are installed and runnable with:
|
||||
|
||||
```bash
|
||||
./bin/check-tools.sh
|
||||
```
|
||||
|
||||
`xrpld` is written in the C++23 dialect. The [tested compiler versions][cpp23-support] are:
|
||||
|
||||
| Compiler | Version |
|
||||
| ----------- | --------------- |
|
||||
| GCC | 15.2 |
|
||||
| Clang | 22 |
|
||||
| Apple Clang | 21 |
|
||||
| MSVC | 19.44[^windows] |
|
||||
For the software needed to build xrpld, see the
|
||||
[environment setup guide](./docs/build/environment.md).
|
||||
|
||||
## Operating Systems
|
||||
|
||||
Please see the [environment setup guide](./docs/build/environment.md) for detailed instructions for all platforms.
|
||||
|
||||
### Linux
|
||||
|
||||
The Ubuntu Linux distribution has received the highest level of quality
|
||||
@@ -42,12 +22,13 @@ Our Linux CI tooling is distro-independent and uses a Nix-based environment, so
|
||||
### macOS
|
||||
|
||||
Many `xrpld` engineers use macOS for development.
|
||||
The minimum supported version is macOS 15 (Sequoia).
|
||||
CI testing is done in macOS 26 (Tahoe), but the build defaults `CMAKE_OSX_DEPLOYMENT_TARGET` to 15.
|
||||
|
||||
### Windows
|
||||
|
||||
Windows is used by some engineers for development only.
|
||||
|
||||
[^windows]: Windows is not recommended for production use.
|
||||
Windows is used by some engineers for development only, and is not recommended
|
||||
for production use.
|
||||
|
||||
## Steps
|
||||
|
||||
@@ -72,37 +53,25 @@ releases](https://github.com/XRPLF/rippled/releases).
|
||||
|
||||
### Set Up Conan
|
||||
|
||||
After you have a [C++ development environment](./docs/build/environment.md) ready with Git, Python,
|
||||
Conan, CMake, and a C++ compiler, you may need to set up your Conan profile.
|
||||
|
||||
These instructions assume a basic familiarity with Conan and CMake. If you are
|
||||
unfamiliar with Conan, then please read [this crash course](./docs/build/conan.md) or the official
|
||||
[Getting Started][conan-getting-started] walkthrough.
|
||||
|
||||
#### Profiles
|
||||
|
||||
We recommend that you install our Conan profiles:
|
||||
Once your [development environment](./docs/build/environment.md) is ready, set
|
||||
Conan up for this repository:
|
||||
|
||||
```bash
|
||||
conan config install conan/profiles/ -tf $(conan config home)/profiles/
|
||||
./conan/init.sh
|
||||
```
|
||||
|
||||
You can check your Conan profile by running:
|
||||
That installs our [`global.conf`](./conan/global.conf), our Conan
|
||||
[profiles](./conan/profiles), and the `xrplf` remote that hosts some of our
|
||||
dependencies. It honours `CONAN_HOME` and never deletes an existing Conan home,
|
||||
so it is safe to re-run — it only overwrites the files it manages.
|
||||
|
||||
```bash
|
||||
conan profile show
|
||||
```
|
||||
> [!TIP]
|
||||
> In the [Nix development shell](./docs/build/nix.md#conan-configuration) this is
|
||||
> already done for you: the script runs on entry.
|
||||
|
||||
If the default profile is not suitable for your environment, you can create a custom profile and pass it to Conan.
|
||||
More information on customizing Conan can be found in the [Advanced Conan configuration](./docs/build/advanced_conan.md).
|
||||
|
||||
#### Add xrplf remote
|
||||
|
||||
Run the following command to add the `xrplf` remote, which hosts some of our dependencies:
|
||||
|
||||
```bash
|
||||
conan remote add --index 0 --force xrplf https://conan.xrplf.org/repository/conan/
|
||||
```
|
||||
You can inspect the resulting profile with `conan profile show`. If it is not
|
||||
suitable for your environment, create a custom profile and pass it to Conan — see
|
||||
[Advanced Conan configuration](./docs/build/advanced_conan.md).
|
||||
|
||||
### Set Up Ccache
|
||||
|
||||
@@ -245,7 +214,17 @@ cmake --build . --target setup_code_gen # create venv and install dependencies
|
||||
cmake --build . --target code_gen # regenerate code
|
||||
```
|
||||
|
||||
The regenerated files should be committed alongside your changes.
|
||||
The same targets are also available as a standalone project, which does not
|
||||
need the dependencies to be configured first:
|
||||
|
||||
```
|
||||
cmake -S cmake/codegen -B build/codegen
|
||||
cmake --build build/codegen --target setup_code_gen
|
||||
cmake --build build/codegen --target code_gen
|
||||
```
|
||||
|
||||
The regenerated files should be committed alongside your changes. CI verifies
|
||||
that they are up-to-date.
|
||||
|
||||
## Coverage report
|
||||
|
||||
@@ -257,10 +236,14 @@ which is only enabled when the `coverage` option is set, e.g. with
|
||||
Prerequisites for the coverage report:
|
||||
|
||||
- [gcovr tool][gcovr] (can be installed e.g. with [pip][python-pip])
|
||||
- `gcov` for GCC (installed with the compiler by default) or
|
||||
- `llvm-cov` for Clang (installed with the compiler by default)
|
||||
- `gcov` for GCC or `llvm-cov` for Clang, usually installed with the compiler
|
||||
- `Debug` build type
|
||||
|
||||
> [!NOTE]
|
||||
> Clang coverage is not available in the [Nix development shell](./docs/build/nix.md#building-xrpld-in-the-nix-shell):
|
||||
> its `clang` shells do not ship `llvm-cov`. Use a `gcc` shell instead (`.#gcc`,
|
||||
> or `.#gcc-plain` on Linux), which provides a `gcov` matching its compiler.
|
||||
|
||||
A coverage report is created when the following steps are completed, in order:
|
||||
|
||||
1. `xrpld` binary built with instrumentation data, enabled by the `coverage`
|
||||
@@ -333,6 +316,22 @@ memory) since they concatenate sources into fewer translation units. Non-unity
|
||||
builds may be faster for incremental builds, and can be helpful for detecting
|
||||
`#include` omissions.
|
||||
|
||||
### Rust crates
|
||||
|
||||
The build compiles the Rust workspace in `crates/` and generates the cxxbridge
|
||||
bindings the C++ side includes, so it needs a Rust toolchain (`cargo`, `rustc`)
|
||||
at the channel pinned in [`rust-toolchain.toml`](./rust-toolchain.toml). The
|
||||
[Nix development shell](./docs/build/nix.md) provides one; otherwise install it
|
||||
as described in [Rust](./docs/build/environment.md#rust).
|
||||
|
||||
The crates also have their own Rust unit tests. Those are run with `cargo` and
|
||||
need only the Rust toolchain, independently of CMake (CI runs them with
|
||||
`cargo nextest`):
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path crates/Cargo.toml --workspace
|
||||
```
|
||||
|
||||
### Verifying headers
|
||||
|
||||
The regular build only compiles `.cpp` files, so a header is only ever checked
|
||||
@@ -377,10 +376,14 @@ After any updates or changes to dependencies, you may need to do the following:
|
||||
4. [Regenerate lockfile](./docs/build/advanced_conan.md#conan-lockfile).
|
||||
5. Re-run [conan install](#build-and-test).
|
||||
|
||||
If you are using the Nix development shell, whether prebuilt Conan binaries apply
|
||||
depends on your platform — see
|
||||
[Prebuilt packages](./docs/build/nix.md#prebuilt-packages).
|
||||
|
||||
#### ERROR: Package not resolved
|
||||
|
||||
If you're seeing an error like `ERROR: Package 'snappy/1.1.10' not resolved: Unable to find 'snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1756234314.246' in remotes.`,
|
||||
please [add `xrplf` remote](#add-xrplf-remote) or re-run `conan export` for [patched recipes](./docs/build/advanced_conan.md#patched-recipes).
|
||||
please [set Conan up](#set-up-conan) so the `xrplf` remote is configured, or re-run `conan export` for [patched recipes](./docs/build/advanced_conan.md#patched-recipes).
|
||||
|
||||
### `protobuf/port_def.inc` file not found
|
||||
|
||||
@@ -400,7 +403,6 @@ For example, if you want to build Debug:
|
||||
1. For conan install, pass `--settings build_type=Debug`
|
||||
2. For cmake, pass `-DCMAKE_BUILD_TYPE=Debug`
|
||||
|
||||
[cpp23-support]: https://en.cppreference.com/w/cpp/compiler_support/23
|
||||
[conan-getting-started]: https://docs.conan.io/en/latest/getting_started.html
|
||||
[unity-build]: https://en.wikipedia.org/wiki/Unity_build
|
||||
[gcovr]: https://gcovr.com/en/stable/getting-started.html
|
||||
|
||||
@@ -13,6 +13,23 @@ if(DEFINED CMAKE_MODULE_PATH)
|
||||
endif()
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
|
||||
# Must be set before project() because project() consumes it when configuring the compiler and SDK.
|
||||
# A user-provided -DCMAKE_OSX_DEPLOYMENT_TARGET still takes precedence.
|
||||
#
|
||||
# CMAKE_SYSTEM_NAME can't be used before project(), so CMAKE_HOST_SYSTEM_NAME is used instead.
|
||||
#
|
||||
# When CMAKE_OSX_DEPLOYMENT_TARGET is bumped to >=26.0, FastFloat dependency won't be needed anymore
|
||||
if(
|
||||
CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin"
|
||||
AND NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET
|
||||
)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET
|
||||
"15.0"
|
||||
CACHE STRING
|
||||
"Minimum macOS deployment version"
|
||||
)
|
||||
endif()
|
||||
|
||||
project(xrpl)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
@@ -87,6 +104,7 @@ include(deps/Boost)
|
||||
add_subdirectory(external/antithesis-sdk)
|
||||
find_package(date REQUIRED)
|
||||
find_package(ed25519 REQUIRED)
|
||||
find_package(FastFloat REQUIRED)
|
||||
find_package(gRPC REQUIRED)
|
||||
find_package(LibArchive REQUIRED)
|
||||
find_package(lz4 REQUIRED)
|
||||
@@ -102,6 +120,7 @@ target_link_libraries(
|
||||
xrpl_libs
|
||||
INTERFACE
|
||||
ed25519::ed25519
|
||||
FastFloat::fast_float
|
||||
lz4::lz4
|
||||
mpt-crypto::mpt-crypto
|
||||
OpenSSL::Crypto
|
||||
@@ -131,17 +150,30 @@ else()
|
||||
endif()
|
||||
target_link_libraries(xrpl_libs INTERFACE ${nudb})
|
||||
|
||||
if(benchmark)
|
||||
find_package(benchmark REQUIRED)
|
||||
endif()
|
||||
|
||||
if(coverage)
|
||||
include(XrplCov)
|
||||
endif()
|
||||
|
||||
add_custom_target(tidy_prerequisites)
|
||||
|
||||
add_subdirectory(crates)
|
||||
include(XrplCore)
|
||||
include(XrplProtocolAutogen)
|
||||
include(XrplInstall)
|
||||
include(XrplPackaging)
|
||||
include(XrplValidatorKeys)
|
||||
# Must come after XrplValidatorKeys: the 'package' target depends on the
|
||||
# validator-keys target existing.
|
||||
include(XrplPackaging)
|
||||
|
||||
if(tests)
|
||||
include(CTest)
|
||||
add_subdirectory(src/tests/libxrpl)
|
||||
endif()
|
||||
|
||||
if(benchmark)
|
||||
add_subdirectory(src/benchmarks/libxrpl)
|
||||
endif()
|
||||
|
||||
4
CODEOWNERS
Normal file
4
CODEOWNERS
Normal file
@@ -0,0 +1,4 @@
|
||||
# By default, anyone can review changes.
|
||||
|
||||
# The CI tooling team should review changes to the CI configuration.
|
||||
/.github/ @XRPLF/ci-tooling
|
||||
@@ -59,6 +59,17 @@ to an existing XLS. Neither change will be released (in an amendment's
|
||||
case, marked as `Supported::yes`) until the corresponding XLS's status
|
||||
is `Final`.
|
||||
|
||||
## AI coding agents
|
||||
|
||||
[`AGENTS.md`](./AGENTS.md) (and its `CLAUDE.md` symlink, for Claude Code) holds shared, checked-in guidance for AI coding agents working in this repository — build/test/lint commands and architecture notes. Additional `AGENTS.md` files may exist in subdirectories to give agents context specific to that part of the codebase; whenever you add one, also add a `CLAUDE.md` symlink pointing to it (`ln -s AGENTS.md CLAUDE.md`) so Claude Code picks it up too.
|
||||
|
||||
If you want to give an agent personal instructions that shouldn't be shared with other contributors (e.g. your own workflow preferences), those are gitignored, not checked in:
|
||||
|
||||
- `CLAUDE.local.md` — read by Claude Code alongside `CLAUDE.md`.
|
||||
- `AGENTS.override.md` — read by AGENTS.md-compatible tools that support a personal override file layered on top of `AGENTS.md`.
|
||||
|
||||
Likewise, `.claude/settings.local.json` is for personal, untracked Claude Code settings, while `.claude/settings.json` is shared.
|
||||
|
||||
## Before making a pull request
|
||||
|
||||
(Or marking a draft pull request as ready.)
|
||||
@@ -82,7 +93,8 @@ If you create new source files, they must be organized as follows:
|
||||
under `include/xrpl`, and source (`.cpp`) files must go under
|
||||
`src/libxrpl`.
|
||||
- All other non-test files must go under `src/xrpld`.
|
||||
- All test source files must go under `src/test`.
|
||||
- New test source files should use `gtest` and go under `src/tests`, unless that isn't possible, in which case they should use our legacy test framework and go under `src/test`.
|
||||
- All benchmark source files must go under `src/benchmarks`.
|
||||
|
||||
The source must be formatted according to the style guide below. The easiest
|
||||
way to satisfy this is to install the [`pre-commit`](#pre-commit-hooks) hooks,
|
||||
@@ -224,8 +236,9 @@ environment, so you don't need to install most of the individual tools
|
||||
yourself. The version of each hook sourced from an external repository
|
||||
(`clang-format`, `gersemi`, etc.) is pinned in that file, so running the hooks
|
||||
locally uses exactly the same versions as CI. A few `local` hooks — most notably
|
||||
`clang-tidy` — run tools from your own environment; see
|
||||
[Installing clang-tidy](#installing-clang-tidy) for how to get those.
|
||||
`clang-tidy` and `cargo fmt` — run tools from your own environment; see
|
||||
[Installing clang-tidy](#installing-clang-tidy) and
|
||||
[Rust](./docs/build/environment.md#rust) for how to get those.
|
||||
|
||||
To get started, install `pre-commit` and enable the git hook scripts:
|
||||
|
||||
@@ -254,6 +267,7 @@ The hooks configured in this repository include, among others:
|
||||
- `clang-tidy` — C++ static analysis (see [Clang-tidy](#clang-tidy)); opt in with `TIDY=1`
|
||||
- `fix-include-style`, `fix-pragma-once`, `check-doxygen-style` — C++ hygiene
|
||||
- `gersemi` — CMake formatting
|
||||
- `cargo fmt` — Rust formatting for the crates in `crates/`
|
||||
- `prettier`, `black`, `shfmt` — formatting for JavaScript/JSON/Markdown, Python, and shell
|
||||
- `cspell` — spell checking
|
||||
|
||||
@@ -318,7 +332,11 @@ See the [environment setup guide](./docs/build/environment.md#clang-tidy) for ho
|
||||
|
||||
### Running clang-tidy locally
|
||||
|
||||
Before running clang-tidy, you must build the project to generate required files (particularly protobuf headers). Refer to [`BUILD.md`](./BUILD.md) for build instructions.
|
||||
Before running clang-tidy, you must generate the files it depends on (protobuf headers and the cxxbridge headers from the Rust crates). Configure the project as described in [`BUILD.md`](./BUILD.md), then build the `tidy_prerequisites` target, which generates all of them:
|
||||
|
||||
```bash
|
||||
cmake --build build --target tidy_prerequisites
|
||||
```
|
||||
|
||||
#### Via pre-commit (recommended)
|
||||
|
||||
@@ -347,12 +365,14 @@ run-clang-tidy -p build -allow-no-checks src tests
|
||||
```
|
||||
|
||||
This will check all source files in the `src`, `include` and `tests` directories using the compile commands from your `build` directory.
|
||||
If you wish to automatically fix whatever clang-tidy finds _and_ is capable of fixing, add `-fix` to the above command:
|
||||
If you wish to automatically fix whatever clang-tidy finds _and_ is capable of fixing, add `-fix -format` to the above command:
|
||||
|
||||
```
|
||||
run-clang-tidy -p build -quiet -fix -allow-no-checks src tests
|
||||
run-clang-tidy -p build -quiet -fix -format -allow-no-checks src tests
|
||||
```
|
||||
|
||||
`-format` reformats the fixed code with [`.clang-format`](./.clang-format); without it the fixes are inserted in LLVM style and the `clang-format` hook rewrites them afterwards.
|
||||
|
||||
## Contracts and instrumentation
|
||||
|
||||
We are using [Antithesis](https://antithesis.com/) for continuous fuzzing,
|
||||
|
||||
@@ -54,6 +54,7 @@ Here are some good places to start learning the source code:
|
||||
| `./docs` | Source documentation files and doxygen config. |
|
||||
| `./cfg` | Example configuration files. |
|
||||
| `./src` | Source code. |
|
||||
| `./crates` | Rust source code. |
|
||||
|
||||
Some of the directories under `src` are external repositories included using
|
||||
git-subtree. See those directories' README files for more details.
|
||||
|
||||
111
bin/check-nix-store-refs.sh
Executable file
111
bin/check-nix-store-refs.sh
Executable file
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env bash
|
||||
# Fail if a binary under <path> records a /nix/store path it resolves at run
|
||||
# time. See docs/build/nix.md#prebuilt-packages for why that matters.
|
||||
#
|
||||
# <path> is a file or a directory. macOS: nothing may reference the store, so
|
||||
# point it at whole trees. Linux: the toolchain always writes the store into
|
||||
# PT_INTERP and RUNPATH, so only at what cmake/PatchNixBinary.cmake retargets.
|
||||
#
|
||||
# Only Mach-O / ELF is inspected. Static archives hold store paths in debug info
|
||||
# alone; the scripts in a Conan cache are all git hook samples and autotools
|
||||
# scratch, 36 false positives to 0 real.
|
||||
#
|
||||
# Usage: bin/check-nix-store-refs.sh <path>
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "usage: $0 <path>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [ ! -e "$1" ]; then
|
||||
echo "$0: no such path: $1" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
case "$(uname -s)" in
|
||||
Darwin)
|
||||
format=Mach-O
|
||||
recorded_paths=macho_recorded_paths
|
||||
tool=otool
|
||||
;;
|
||||
Linux)
|
||||
format=ELF
|
||||
recorded_paths=elf_recorded_paths
|
||||
tool=readelf
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported OS - skipping the Nix store reference check."
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# `pipefail` would catch this too, but only as a bare nonzero exit.
|
||||
if ! command -v "${tool}" >/dev/null; then
|
||||
echo "$0: ${tool} not found; cannot inspect binaries" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Both list what the file records. `ldd` would answer what this machine resolves
|
||||
# now, which is wrong both ways: store paths for a correctly patched binary,
|
||||
# silence for a store RUNPATH that resolves nowhere.
|
||||
|
||||
# `name` covers LC_ID_DYLIB and LC_LOAD*_DYLIB, `path` covers LC_RPATH.
|
||||
macho_recorded_paths() {
|
||||
otool -l "$1" | sed -nE 's#^ *(name|path) ([^ ]*).*#\2#p'
|
||||
}
|
||||
|
||||
# RPATH and RUNPATH are colon-separated.
|
||||
elf_recorded_paths() {
|
||||
readelf -ldW "$1" |
|
||||
sed -nE \
|
||||
-e 's#.*program interpreter: ([^]]*)\].*#\1#p' \
|
||||
-e 's#.*\((RPATH|RUNPATH|NEEDED)\).*\[([^]]*)\].*#\2#p' |
|
||||
tr ':' '\n'
|
||||
}
|
||||
|
||||
checked=0
|
||||
skipped=0
|
||||
leaked=0
|
||||
|
||||
while IFS= read -r file; do
|
||||
case "$(file -b "${file}" 2>/dev/null)" in
|
||||
*"${format}"*) ;;
|
||||
*)
|
||||
skipped=$((skipped + 1))
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
checked=$((checked + 1))
|
||||
|
||||
# Filter after extracting, or a search path starting elsewhere ($ORIGIN)
|
||||
# hides the rest. `sed` not `grep`: grep calls "no matches" a failure, and
|
||||
# the `|| true` that would need masks a broken pipeline too.
|
||||
refs="$("${recorded_paths}" "${file}" | sed -n '\#^/nix/store/#p' | sort -u)"
|
||||
if [ -n "${refs}" ]; then
|
||||
leaked=$((leaked + 1))
|
||||
echo "::error file=${file}::references the Nix store at run time"
|
||||
echo "${file}"
|
||||
echo "${refs}" | sed 's/^/ /'
|
||||
fi
|
||||
done < <(find "$1" -type f \( -perm -u+x -o -name '*.dylib' -o -name '*.so*' \))
|
||||
|
||||
echo "$1: checked ${checked}, skipped ${skipped}, ${leaked} with Nix store references."
|
||||
|
||||
if [ "${leaked}" -ne 0 ]; then
|
||||
cat >&2 <<'EOF'
|
||||
|
||||
Fixes, in order of preference:
|
||||
- A Conan package built before this check existed: drop it
|
||||
(`conan remove '<name>/*'`) and rebuild.
|
||||
- A binary that should have been retargeted to the system loader: check that
|
||||
cmake/PatchNixBinary.cmake ran for it.
|
||||
- Link the macOS system library instead of the Nix one - see
|
||||
libresolvSystemStub in nix/darwin.nix.
|
||||
- No system library exists (libstdc++): link it statically.
|
||||
- None of the above: pin the toolchain into the package ID, following
|
||||
`user.package:libc_version` in conan/profiles/ci.
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
@@ -15,10 +15,14 @@
|
||||
# - Windows: the core build tools only (CMake, Conan, Git, Python).
|
||||
# MSVC is expected to be provided separately and is not checked here.
|
||||
#
|
||||
# Some tools (clang-format, doxygen, gcovr, gh, git-cliff, gpg, pre-commit,
|
||||
# run-clang-tidy) are present in our Linux CI images and in local development
|
||||
# setups, but not in the macOS CI environment. They are checked everywhere
|
||||
# except when running in CI on macOS.
|
||||
# Some tools (clang-format, clang-tidy, doxygen, gcovr, gh, git-cliff, gpg,
|
||||
# pre-commit, run-clang-tidy) are present in our Linux CI images and in local
|
||||
# development setups, but not in the macOS CI environment. They are checked
|
||||
# everywhere except when running in CI on macOS.
|
||||
#
|
||||
# Tools that Nix also exposes under a version-suffixed name (`clang-tidy-22`,
|
||||
# `g++-15`, ...) are probed under both names: a suffixed name can break while
|
||||
# the plain one still works (see mkVersionedToolLinks in nix/packages.nix).
|
||||
#
|
||||
# Environment variables:
|
||||
# CI if set, skip the tools above when on macOS.
|
||||
@@ -26,12 +30,27 @@
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
# Version suffixes of the Nix tool links, tracking nix/packages.nix.
|
||||
gcc_version=15
|
||||
llvm_version=22
|
||||
|
||||
missing=()
|
||||
checked=0
|
||||
|
||||
# tool_path <name>
|
||||
# Fully resolved path of a tool, so the snapshots record which derivation
|
||||
# provides it. Prints nothing when it isn't on PATH.
|
||||
tool_path() {
|
||||
local path
|
||||
path="$(command -v "$1" 2>/dev/null)" || return 0
|
||||
readlink -f "${path}" 2>/dev/null || printf '%s' "${path}"
|
||||
}
|
||||
|
||||
# check <name> [probe-command...]
|
||||
# Runs the probe (default: "<name> --version") quietly. Records <name> as
|
||||
# missing if the command is not found or exits non-zero.
|
||||
# Runs the probe (default: "<name> --version"), capturing both stdout and
|
||||
# stderr, and prints three lines: the status and name, the first non-blank line
|
||||
# of the probe output (its version, or the error when it failed), and the tool's
|
||||
# resolved path. Records <name> as missing if it is not found or exits non-zero.
|
||||
check() {
|
||||
local name="$1"
|
||||
shift
|
||||
@@ -40,14 +59,18 @@ check() {
|
||||
probe=("${name}" --version)
|
||||
fi
|
||||
|
||||
echo "Checking ${name}..."
|
||||
checked=$((checked + 1))
|
||||
if "${probe[@]}" | head -n 1; then
|
||||
printf ' [ ok ] %s\n' "${name}"
|
||||
local output version path
|
||||
path="$(tool_path "${name}")"
|
||||
if output="$("${probe[@]}" 2>&1)"; then
|
||||
printf ' ✅ %s\n' "${name}"
|
||||
else
|
||||
printf ' [MISS] %s\n' "${name}"
|
||||
printf ' ❌ %s\n' "${name}"
|
||||
missing+=("${name}")
|
||||
fi
|
||||
version="$(printf '%s\n' "${output}" | grep -m1 '[^[:space:]]' || true)"
|
||||
printf ' %s\n' "${version:-(no output)}"
|
||||
printf ' %s\n' "${path:-(not found)}"
|
||||
}
|
||||
|
||||
case "$(uname -s)" in
|
||||
@@ -79,24 +102,35 @@ if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
|
||||
echo "Development tooling:"
|
||||
check ccache
|
||||
check clang
|
||||
check "clang-${llvm_version}"
|
||||
check clang++
|
||||
check "clang++-${llvm_version}"
|
||||
check ClangBuildAnalyzer
|
||||
check curl
|
||||
check file
|
||||
check less
|
||||
check make
|
||||
check netstat which netstat
|
||||
# net-tools netstat reports "net-tools X.Y"; macOS ships BSD netstat with no
|
||||
# version flag, so fall back to a presence marker there.
|
||||
check netstat sh -c 'command -v netstat >/dev/null && { netstat --version 2>&1 | grep -m1 -oE "net-tools [0-9.]+" || echo present; }'
|
||||
check ninja
|
||||
check perl
|
||||
check perl perl -e 'print "$^V\n"'
|
||||
check pkg-config
|
||||
check vim
|
||||
check zip
|
||||
check zip bash -c 'zip --version 2>&1 | grep -m1 -oE "Zip [0-9.]+"'
|
||||
|
||||
# These tools are present in our Linux CI images and in local development
|
||||
# setups, but not in the macOS CI environment. So check them everywhere
|
||||
# except when running in CI on macOS.
|
||||
if [ "${os}" = "linux" ] || [ -z "${CI:-}" ]; then
|
||||
check clang-apply-replacements
|
||||
check "clang-apply-replacements-${llvm_version}"
|
||||
check clang-format
|
||||
check "clang-format-${llvm_version}"
|
||||
# clang-tidy leads --version with the LLVM banner, not the version.
|
||||
tidy_probe="--version | grep -m1 -oE 'LLVM version [0-9.]+'"
|
||||
check clang-tidy sh -c "clang-tidy ${tidy_probe}"
|
||||
check "clang-tidy-${llvm_version}" sh -c "clang-tidy-${llvm_version} ${tidy_probe}"
|
||||
check dot
|
||||
check doxygen
|
||||
check gcovr
|
||||
@@ -107,6 +141,7 @@ if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
|
||||
# pre-commit, or its alternative implementation prek
|
||||
check pre-commit sh -c 'pre-commit --version || prek --version'
|
||||
check run-clang-tidy run-clang-tidy --help
|
||||
check "run-clang-tidy-${llvm_version}" "run-clang-tidy-${llvm_version}" --help
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -121,8 +156,9 @@ if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
|
||||
check cargo-audit cargo audit --version
|
||||
check cargo-llvm-cov cargo llvm-cov --version
|
||||
check cargo-nextest cargo nextest --version
|
||||
check clippy clippy-driver --version
|
||||
check clippy-driver
|
||||
check rust-analyzer
|
||||
check rust-nightly rust-nightly run rustc --version
|
||||
check rustc
|
||||
check rustfmt
|
||||
fi
|
||||
@@ -133,7 +169,11 @@ if [ "${os}" = "linux" ]; then
|
||||
echo
|
||||
echo "GCC toolchain:"
|
||||
check gcc
|
||||
check "gcc-${gcc_version}"
|
||||
check g++
|
||||
check "g++-${gcc_version}"
|
||||
check cpp
|
||||
check "cpp-${gcc_version}"
|
||||
check gcov
|
||||
|
||||
echo
|
||||
@@ -158,9 +198,9 @@ else
|
||||
checked=$((checked + 1))
|
||||
tmp_clone="$(mktemp -d)"
|
||||
if git clone --depth 1 https://github.com/XRPLF/actions.git "${tmp_clone}/actions" >/dev/null 2>&1; then
|
||||
printf ' [ ok ] git clone over HTTPS\n'
|
||||
printf ' ✅ git clone over HTTPS\n'
|
||||
else
|
||||
printf ' [MISS] git clone over HTTPS\n'
|
||||
printf ' ❌ git clone over HTTPS\n'
|
||||
missing+=("git-https-clone")
|
||||
fi
|
||||
rm -rf "${tmp_clone}"
|
||||
@@ -168,9 +208,9 @@ fi
|
||||
|
||||
echo
|
||||
if [ "${#missing[@]}" -eq 0 ]; then
|
||||
echo "All ${checked} checked tools are present and runnable."
|
||||
echo "✅ All ${checked} checked tools are present and runnable."
|
||||
else
|
||||
echo "Missing or non-functional tools (${#missing[@]} of ${checked}):" >&2
|
||||
echo "❌ Missing or non-functional tools (${#missing[@]} of ${checked}):" >&2
|
||||
for tool in "${missing[@]}"; do
|
||||
echo " - ${tool}" >&2
|
||||
done
|
||||
|
||||
@@ -22,23 +22,41 @@ case "${ID}" in
|
||||
;;
|
||||
esac
|
||||
|
||||
# Packaging runs in a vanilla distro image, so the tooling comes from the distro's
|
||||
# archive rather than from nixpkgs:
|
||||
#
|
||||
# - debhelper and dpkg-dev build the DEB, and lintian checks it
|
||||
# - binutils gives debian/rules the readelf its glibc-floor check runs; it
|
||||
# already arrives via dpkg-dev, but that tool is called directly
|
||||
# - rpm-build builds the RPM, with systemd-rpm-macros and redhat-rpm-config
|
||||
# supplying the systemd and find-debuginfo macros the spec uses
|
||||
# - rpm-sign and gnupg2 sign the built RPM
|
||||
# - python3 runs the packaging scripts
|
||||
# - git gives build_pkg.py the commit timestamp it stamps files with
|
||||
# - ca-certificates lets git and the packaging scripts verify TLS
|
||||
function install() {
|
||||
case "${ID}" in
|
||||
debian | ubuntu)
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends \
|
||||
binutils \
|
||||
ca-certificates \
|
||||
debhelper \
|
||||
debhelper-compat \
|
||||
dpkg-dev \
|
||||
git
|
||||
git \
|
||||
lintian \
|
||||
python3
|
||||
;;
|
||||
|
||||
rhel | centos | rocky | almalinux)
|
||||
dnf install -y --setopt=install_weak_deps=False \
|
||||
git \
|
||||
rpm-build \
|
||||
gnupg2 \
|
||||
python3 \
|
||||
redhat-rpm-config \
|
||||
rpm-build \
|
||||
rpm-sign \
|
||||
systemd-rpm-macros
|
||||
;;
|
||||
esac
|
||||
55
bin/pre-commit/Dockerfile
Normal file
55
bin/pre-commit/Dockerfile
Normal file
@@ -0,0 +1,55 @@
|
||||
ARG BASE_IMAGE=ubuntu:26.04
|
||||
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
|
||||
ENTRYPOINT ["/bin/bash"]
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN <<EOF
|
||||
pkgs=()
|
||||
pkgs+=(curl) # Required to install nix.
|
||||
pkgs+=(doxygen) # Needed for Clio's check-doxygen-docs.sh.
|
||||
pkgs+=(git) # Required for prepare-runner.
|
||||
pkgs+=(libatomic1) # Required to run pre-commit provided `node`.
|
||||
pkgs+=(python3) # Python 3 interpreter.
|
||||
pkgs+=(python3-pip) # Package manager for Python applications.
|
||||
pkgs+=(xz-utils) # Required to install nix
|
||||
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends "${pkgs[@]}"
|
||||
apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
EOF
|
||||
|
||||
ARG PRE_COMMIT_VERSION=4.6.0
|
||||
RUN pip install --no-cache --break-system-packages \
|
||||
pre-commit==${PRE_COMMIT_VERSION}
|
||||
|
||||
RUN sh <(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemon --yes
|
||||
|
||||
# Add nix to PATH and set NIX environment variables,
|
||||
# so nix is available in all shells including non-interactive shells (e.g., GitHub Actions).
|
||||
ENV PATH="/nix/var/nix/profiles/default/bin:${PATH}"
|
||||
ENV NIX_PROFILES="/nix/var/nix/profiles/default"
|
||||
ENV NIX_SSL_CERT_FILE="/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt"
|
||||
|
||||
# Verify nix installation
|
||||
RUN nix --version
|
||||
|
||||
ENV RUSTUP_HOME="/opt/rust/rustup"
|
||||
ENV CARGO_HOME="/opt/rust/cargo"
|
||||
ENV PATH="/opt/rust/cargo/bin:${PATH}"
|
||||
|
||||
WORKDIR /tmp
|
||||
COPY rust-toolchain.toml /tmp/rust-toolchain.toml
|
||||
RUN <<EOF
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
|
||||
| sh -s -- -y --no-modify-path --profile minimal --default-toolchain none
|
||||
rustup toolchain install
|
||||
rustup show
|
||||
cargo fmt --version
|
||||
EOF
|
||||
|
||||
WORKDIR /
|
||||
149
bin/pre-commit/check_rust_coverage_attrs.py
Executable file
149
bin/pre-commit/check_rust_coverage_attrs.py
Executable file
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Check that Rust unit tests stay out of the coverage report.
|
||||
|
||||
cargo-llvm-cov instruments the test code along with everything else, so a test
|
||||
module that is not excluded counts its own body as covered and inflates the
|
||||
reported number. Excluding it takes two attributes:
|
||||
|
||||
* every `#[cfg(test)]` module carries
|
||||
`#[cfg_attr(coverage_nightly, coverage(off))]`;
|
||||
* every crate root (lib.rs, main.rs) carries
|
||||
`#![cfg_attr(coverage_nightly, feature(coverage_attribute))]`, which the
|
||||
attribute above needs in order to compile.
|
||||
|
||||
Both are inert outside the coverage job: cargo-llvm-cov defines
|
||||
`coverage_nightly` only when it runs on a nightly toolchain.
|
||||
|
||||
The crate-root gate is checked even in a crate that has no tests yet, because
|
||||
that is what lets the first test module added later carry the attribute without
|
||||
a build failure. Missing it is a hard error, so it cannot go unnoticed; a
|
||||
missing `coverage(off)` fails open, which is why this check exists.
|
||||
|
||||
Matching is on exact attribute text, which works because `cargo fmt` runs over
|
||||
the whole workspace in the hook ahead of this one: rustfmt puts every attribute
|
||||
on its own line and normalizes what is inside it, turning `#[cfg( test )]`
|
||||
and `#[cfg(test,)]` alike into `#[cfg(test)]`. So there is nothing here that
|
||||
parses Rust. The price is that a cfg this file does not spell out literally --
|
||||
`all(test, ...)`, `any(test, ...)`, `not(test)` -- is reported rather than
|
||||
classified, on the grounds that guessing at coverage semantics is how a check
|
||||
like this ends up quietly wrong.
|
||||
|
||||
Usage: ./bin/pre-commit/check_rust_coverage_attrs.py <file1> <file2> ...
|
||||
|
||||
Exit status is non-zero if any violation is found.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
CRATE_ROOTS = {"lib.rs", "main.rs"}
|
||||
|
||||
FEATURE_ATTR = "#![cfg_attr(coverage_nightly, feature(coverage_attribute))]"
|
||||
COVERAGE_OFF_ATTR = "#[cfg_attr(coverage_nightly, coverage(off))]"
|
||||
CFG_TEST_ATTR = "#[cfg(test)]"
|
||||
|
||||
# Any other cfg that mentions `test`. String literals are blanked before this
|
||||
# runs, so `feature = "test"` does not read as the `test` cfg.
|
||||
RE_CFG_MENTIONS_TEST = re.compile(r"^#\[cfg\(.*\btest\b.*\)\]$")
|
||||
RE_STRING = re.compile(r'"(?:[^"\\]|\\.)*"')
|
||||
RE_MOD = re.compile(r"^(?:pub(?:\([^)]*\))?\s+)?mod\s+([A-Za-z_]\w*)")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Finding:
|
||||
line: int
|
||||
label: str
|
||||
message: str
|
||||
|
||||
|
||||
def _check_module(attrs: list[str], line: int, name: str) -> list[Finding]:
|
||||
"""Findings for one module, given the attributes attached to it."""
|
||||
if COVERAGE_OFF_ATTR in attrs:
|
||||
return [] # excluded from coverage; which cfg gates it does not matter
|
||||
if CFG_TEST_ATTR in attrs:
|
||||
return [
|
||||
Finding(
|
||||
line,
|
||||
"missing-coverage-off",
|
||||
f"`mod {name}` is #[cfg(test)] but not excluded from coverage; "
|
||||
f"add {COVERAGE_OFF_ATTR}",
|
||||
)
|
||||
]
|
||||
unclassified = [
|
||||
attr for attr in attrs if RE_CFG_MENTIONS_TEST.match(RE_STRING.sub('""', attr))
|
||||
]
|
||||
if unclassified:
|
||||
return [
|
||||
Finding(
|
||||
line,
|
||||
"unclassified-cfg",
|
||||
f"`mod {name}` is gated on {unclassified[0]}, which this check "
|
||||
f"cannot tell apart from a module that ships in the library; "
|
||||
f"add {COVERAGE_OFF_ATTR} if it is test-only, or teach this "
|
||||
f"check the cfg if it is not",
|
||||
)
|
||||
]
|
||||
return []
|
||||
|
||||
|
||||
def _check_test_modules(lines: list[str]) -> list[Finding]:
|
||||
"""Findings for every test module that is not excluded from coverage."""
|
||||
findings: list[Finding] = []
|
||||
attrs: list[str] = []
|
||||
attrs_line = 0
|
||||
for number, raw in enumerate(lines, start=1):
|
||||
stripped = raw.strip()
|
||||
# Blank lines and comments are allowed between an attribute and its item.
|
||||
if not stripped or stripped.startswith("//"):
|
||||
continue
|
||||
if stripped.startswith("#["):
|
||||
if not attrs:
|
||||
attrs_line = number
|
||||
attrs.append(stripped)
|
||||
continue
|
||||
module = RE_MOD.match(stripped)
|
||||
if module is not None and attrs:
|
||||
findings += _check_module(attrs, attrs_line, module.group(1))
|
||||
attrs = []
|
||||
return findings
|
||||
|
||||
|
||||
def _check_crate_root(name: str, lines: list[str]) -> list[Finding]:
|
||||
"""A finding if a crate root is missing the coverage_attribute feature gate."""
|
||||
if name not in CRATE_ROOTS:
|
||||
return []
|
||||
if any(line.strip() == FEATURE_ATTR for line in lines):
|
||||
return []
|
||||
return [
|
||||
Finding(
|
||||
1,
|
||||
"missing-feature-gate",
|
||||
f"crate root is missing {FEATURE_ATTR}",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def check_source(name: str, text: str) -> list[Finding]:
|
||||
"""Findings for one file's contents; `name` is its base name (lib.rs, ...)."""
|
||||
lines = text.splitlines()
|
||||
return _check_crate_root(name, lines) + _check_test_modules(lines)
|
||||
|
||||
|
||||
def check_file(path: Path) -> list[Finding]:
|
||||
return check_source(path.name, path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
total = 0
|
||||
for path in (Path(name) for name in sys.argv[1:]):
|
||||
for finding in check_file(path):
|
||||
total += 1
|
||||
print(f"{path}:{finding.line}: {finding.label}: {finding.message}")
|
||||
return 1 if total else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -144,7 +144,11 @@ def main():
|
||||
+ files
|
||||
)
|
||||
canonicalize_fix_paths(Path(fixes_dir))
|
||||
applied = subprocess.run([clang_apply_replacements, fixes_dir])
|
||||
# `FormatStyle` in .clang-tidy does not reach this path,
|
||||
# so ask for the repository style here.
|
||||
applied = subprocess.run(
|
||||
[clang_apply_replacements, "--format", "--style=file", fixes_dir]
|
||||
)
|
||||
|
||||
return result.returncode or applied.returncode
|
||||
|
||||
|
||||
@@ -488,6 +488,17 @@
|
||||
# Must be a number between 100 and 1000, defaults to 250
|
||||
#
|
||||
#
|
||||
# [max_subscriptions_per_connection]
|
||||
#
|
||||
# Maximum number of account, real-time account, and account-history
|
||||
# subscriptions a single client connection may hold at once. Bounds the
|
||||
# per-connection state torn down when the connection disconnects. Book
|
||||
# subscriptions are tracked separately and are not counted here.
|
||||
#
|
||||
# Defaults to 100000 if not set; large enough for legitimate power users
|
||||
# such as block explorers.
|
||||
#
|
||||
#
|
||||
# [overlay]
|
||||
#
|
||||
# Controls settings related to the peer to peer overlay.
|
||||
@@ -538,6 +549,45 @@
|
||||
# only be used for local testing and debugging. Do not disable
|
||||
# on mainnet.
|
||||
#
|
||||
# max_untrusted_count = <number>
|
||||
#
|
||||
# The number of manifests the server keeps for validators it does not
|
||||
# list, and the number it sends and processes in a single peer protocol
|
||||
# message. Once the server holds this many, a manifest for a new
|
||||
# unlisted validator is rejected, so peer gossip cannot grow the cache
|
||||
# without end.
|
||||
#
|
||||
# This option can take any value between 50 and 1000, inclusive. If
|
||||
# the option is not present the server uses its built-in value.
|
||||
#
|
||||
# The current default (which is subject to change) is 300.
|
||||
#
|
||||
# max_trusted_count = <number>
|
||||
#
|
||||
# The number of manifests for listed validators to allow for when
|
||||
# sizing peer protocol messages. Manifests for listed validators are
|
||||
# never dropped, whether sending or receiving, because doing so would
|
||||
# delay a validator key change reaching this server. Set this above the
|
||||
# number of validators the server lists.
|
||||
#
|
||||
# Together the two counts above set the largest manifest message the
|
||||
# server accepts: bigger messages are discarded without reading them,
|
||||
# and without penalising the sender. Raising either means the server
|
||||
# accepts and sends bigger messages than a peer using the defaults, and
|
||||
# those peers will discard what this server sends. Lowering either below
|
||||
# what peers send makes this server discard their manifest messages,
|
||||
# which it does without recording anything.
|
||||
#
|
||||
# This option can take any value between 50 and 1000, inclusive. If
|
||||
# the option is not present the server uses its built-in value.
|
||||
#
|
||||
# The current default (which is subject to change) is 300.
|
||||
#
|
||||
# NOTE: These two options (max_untrusted_count and max_trusted_count)
|
||||
# are transitional. They exist to bound manifest-message size and cache
|
||||
# growth during the network upgrade. They may be removed in a future
|
||||
# release once the fleet has upgraded, and should not be relied upon as
|
||||
# stable configuration.
|
||||
#
|
||||
# [transaction_queue] EXPERIMENTAL
|
||||
#
|
||||
@@ -1044,8 +1094,8 @@
|
||||
# Default is 100.
|
||||
#
|
||||
# back_off_milliseconds
|
||||
# Number of milliseconds to wait between
|
||||
# online_delete batches to allow other functions
|
||||
# Number of milliseconds to wait between online_delete
|
||||
# SQL deletion batches to allow other functions
|
||||
# to catch up.
|
||||
# Default is 100.
|
||||
#
|
||||
@@ -1059,10 +1109,22 @@
|
||||
# The online delete process checks periodically
|
||||
# that xrpld is still in sync with the network,
|
||||
# and that the validated ledger is less than
|
||||
# 'age_threshold_seconds' old. If not, then continue
|
||||
# 'age_threshold_seconds' old, and that all
|
||||
# recent ledgers are available. If not, then continue
|
||||
# sleeping for this number of seconds and
|
||||
# checking until healthy.
|
||||
# Default is 5.
|
||||
# Default is 2.
|
||||
#
|
||||
# max_waiting_ledgers
|
||||
# The maximum number of ledgers that may be validated
|
||||
# while online deletion is waiting for the node to get
|
||||
# fully synced with the rest of the network. If more than
|
||||
# this number of ledgers are validated while waiting, then
|
||||
# online deletion gives up on the current ledger and tries
|
||||
# again later. Note this only affects situations that cause
|
||||
# rotation to wait, such as going out of sync, or missing
|
||||
# ledgers. Forward progress is not penalized. Minimum is 64.
|
||||
# Default is the online_delete value.
|
||||
#
|
||||
# Notes:
|
||||
# The 'node_db' entry configures the primary, persistent storage.
|
||||
@@ -1298,6 +1360,39 @@
|
||||
# Example:
|
||||
# owner_reserve = 200000 # 0.2 XRP
|
||||
#
|
||||
# gas_limit = <gas>
|
||||
#
|
||||
# The gas limit is the maximum amount of gas that can be
|
||||
# consumed by a single transaction. The gas limit is used to prevent
|
||||
# transactions from consuming too many resources.
|
||||
#
|
||||
# If this parameter is unspecified, xrpld will use an internal
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# gas_limit = 1000000 # 1 million gas
|
||||
#
|
||||
# bytecode_size_limit = <bytes>
|
||||
#
|
||||
# The bytecode size limit is the maximum size of a WASM extension in
|
||||
# bytes. The size limit is used to prevent extensions from consuming
|
||||
# too many resources.
|
||||
#
|
||||
# If this parameter is unspecified, xrpld will use an internal
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# bytecode_size_limit = 100000 # 100 kb
|
||||
#
|
||||
# gas_price = <micro-drops>
|
||||
#
|
||||
# The gas price is the conversion between WASM gas and its price in drops.
|
||||
#
|
||||
# If this parameter is unspecified, xrpld will use an internal
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# gas_price = 1000000 # 1 drop per gas
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
# 9. Misc Settings
|
||||
|
||||
@@ -29,6 +29,27 @@ if(CMAKE_GENERATOR STREQUAL "Xcode")
|
||||
set(is_xcode TRUE)
|
||||
endif()
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Nix toolchain detection
|
||||
# --------------------------------------------------------------------
|
||||
# True when the C++ compiler resolves into the Nix store. CMAKE_CXX_COMPILER may
|
||||
# be referenced through a symlink outside the store (a Nix profile, a /usr/bin
|
||||
# alternative, ...), so resolve the real path before matching.
|
||||
set(is_nix_compiler FALSE)
|
||||
get_filename_component(_cxx_real "${CMAKE_CXX_COMPILER}" REALPATH)
|
||||
if(_cxx_real MATCHES "^/nix/store/")
|
||||
set(is_nix_compiler TRUE)
|
||||
endif()
|
||||
unset(_cxx_real)
|
||||
|
||||
# True inside the Nix CI Docker image, identified by the /nix/ci-env tree it
|
||||
# ships (see nix/docker/Dockerfile). The dev shell and bare systems don't have
|
||||
# it, so it distinguishes the CI image from other Nix-compiler environments.
|
||||
set(is_ci_image FALSE)
|
||||
if(EXISTS "/nix/ci-env/bin")
|
||||
set(is_ci_image TRUE)
|
||||
endif()
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Operating system detection
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
@@ -1,26 +1,38 @@
|
||||
#[===================================================================[
|
||||
Patch executables to run in non-Nix environments.
|
||||
|
||||
The Nix-based CI image links binaries against an ELF interpreter (loader)
|
||||
The Nix toolchain links binaries against an ELF interpreter (loader)
|
||||
that lives in the Nix store, so the resulting binaries don't run elsewhere
|
||||
(including once installed from the .deb package). `patch_nix_binary` adds a
|
||||
POST_BUILD step that resets the interpreter to the system default loader and
|
||||
drops the rpath.
|
||||
(including once installed from the .deb package). `patch_nix_binary` resets
|
||||
the interpreter to the system default loader and drops the rpath, once the
|
||||
binary has been linked.
|
||||
|
||||
This is only active inside the Nix-based image, detected by the presence of
|
||||
/tmp/loader-path.sh (shipped by that image, resolves the default loader). It
|
||||
is skipped for sanitizer builds, whose runtime libraries are resolved through
|
||||
the rpath. Everywhere else `patch_nix_binary` is a no-op.
|
||||
This runs by default for Nix-toolchain builds (determined by whether the compiler resolves under /nix/store/).
|
||||
Those builds are where binaries get a Nix-store loader.
|
||||
It is opted out of by setting the XRPLD_NO_PATCH_NIX_BINARY environment variable —
|
||||
the plain Nix dev shells set it, since their binaries link a newer glibc
|
||||
and must not be retargeted to the system loader.
|
||||
|
||||
Non-Nix builds (a system compiler, already using the system loader) and sanitizer builds
|
||||
(runtime libraries resolved through the rpath) are skipped too.
|
||||
Everywhere else `patch_nix_binary` is a no-op.
|
||||
|
||||
The default loader is resolved by bin/default-loader-path.sh.
|
||||
#]===================================================================]
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
include(CompilationEnv)
|
||||
|
||||
# Provided by the Nix-based CI image; prints the system default ELF loader path.
|
||||
set(_loader_path_script "/tmp/loader-path.sh")
|
||||
# Resolves the system default ELF loader path for the current architecture.
|
||||
set(_loader_path_script "${CMAKE_SOURCE_DIR}/bin/default-loader-path.sh")
|
||||
|
||||
if(is_linux AND NOT SANITIZERS_ENABLED AND EXISTS "${_loader_path_script}")
|
||||
if(
|
||||
is_linux
|
||||
AND NOT SANITIZERS_ENABLED
|
||||
AND is_nix_compiler
|
||||
AND NOT DEFINED ENV{XRPLD_NO_PATCH_NIX_BINARY}
|
||||
)
|
||||
execute_process(
|
||||
COMMAND "${_loader_path_script}"
|
||||
OUTPUT_VARIABLE DEFAULT_LOADER_PATH
|
||||
@@ -41,13 +53,38 @@ function(patch_nix_binary target)
|
||||
if(NOT PATCH_NIX_BINARIES)
|
||||
return()
|
||||
endif()
|
||||
add_custom_command(
|
||||
TARGET ${target}
|
||||
POST_BUILD
|
||||
COMMAND
|
||||
"${PATCHELF_COMMAND}" --set-interpreter "${DEFAULT_LOADER_PATH}"
|
||||
--remove-rpath "$<TARGET_FILE:${target}>"
|
||||
COMMENT "Patching ${target}: set default loader, remove rpath"
|
||||
VERBATIM
|
||||
|
||||
set(patch_command
|
||||
"${PATCHELF_COMMAND}"
|
||||
--set-interpreter
|
||||
"${DEFAULT_LOADER_PATH}"
|
||||
--remove-rpath
|
||||
"$<TARGET_FILE:${target}>"
|
||||
)
|
||||
set(comment "Patching ${target}: set default loader, remove rpath")
|
||||
|
||||
# POST_BUILD is the cheap way to do this: it runs only when the binary is
|
||||
# relinked. It is also only available in the directory that defined the
|
||||
# target, so for a target from elsewhere (e.g. a FetchContent subproject)
|
||||
# fall back to a custom target that runs after the binary is linked. That
|
||||
# one runs on every build, which is harmless because patchelf is idempotent.
|
||||
get_target_property(target_source_dir ${target} SOURCE_DIR)
|
||||
if("${target_source_dir}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
add_custom_command(
|
||||
TARGET ${target}
|
||||
POST_BUILD
|
||||
COMMAND ${patch_command}
|
||||
COMMENT "${comment}"
|
||||
VERBATIM
|
||||
)
|
||||
else()
|
||||
add_custom_target(
|
||||
${target}-patch-nix
|
||||
ALL
|
||||
COMMAND ${patch_command}
|
||||
COMMENT "${comment}"
|
||||
VERBATIM
|
||||
)
|
||||
add_dependencies(${target}-patch-nix ${target})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
46
cmake/XrplAddBenchmark.cmake
Normal file
46
cmake/XrplAddBenchmark.cmake
Normal file
@@ -0,0 +1,46 @@
|
||||
include_guard()
|
||||
|
||||
include(isolate_headers)
|
||||
|
||||
# Define a benchmark executable for the module `name`.
|
||||
#
|
||||
# This follows the same general pattern as other build helpers in this repo
|
||||
# (e.g. `add_module`): create a target and isolate headers, but here the target
|
||||
# is a benchmark executable and no `add_test(...)` is registered.
|
||||
#
|
||||
# `isolate_headers` exposes only `${CMAKE_CURRENT_SOURCE_DIR}/${name}` on the
|
||||
# include path, rooted at `src`, so a benchmark's own headers are reached as
|
||||
# `<benchmarks/.../${name}/...>` and nothing else in the tree leaks in.
|
||||
function(xrpl_add_benchmark name)
|
||||
set(target ${PROJECT_NAME}.bench.${name})
|
||||
|
||||
file(
|
||||
GLOB_RECURSE sources
|
||||
CONFIGURE_DEPENDS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${name}/*.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${name}.cpp"
|
||||
)
|
||||
add_executable(${target} ${ARGN} ${sources})
|
||||
|
||||
# Benchmark sources register cases through Google Benchmark's static
|
||||
# registrars (anonymous-namespace lambdas). Merging several such files into
|
||||
# one unity translation unit collides those internal-linkage entities, so
|
||||
# keep benchmarks out of the unity build - mirroring xrpl.libpb in
|
||||
# XrplCore.cmake. Each file compiles fine on its own.
|
||||
set_target_properties(${target} PROPERTIES UNITY_BUILD OFF)
|
||||
|
||||
# Land next to `xrpl_tests` in the build root rather than buried under
|
||||
# `src/benchmarks/libxrpl/`. A benchmark is something a person runs by hand,
|
||||
# repeatedly, and comparing two of them should not mean typing two long paths.
|
||||
set_target_properties(
|
||||
${target}
|
||||
PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
)
|
||||
|
||||
isolate_headers(
|
||||
${target}
|
||||
"${CMAKE_SOURCE_DIR}/src"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${name}"
|
||||
PRIVATE
|
||||
)
|
||||
endfunction()
|
||||
@@ -120,7 +120,10 @@ if(MSVC)
|
||||
_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS
|
||||
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:Debug>>:_CRTDBG_MAP_ALLOC>
|
||||
)
|
||||
target_link_libraries(common INTERFACE -errorreport:none -machine:X64)
|
||||
target_link_libraries(
|
||||
common
|
||||
INTERFACE -errorreport:none -machine:X64 -ignore:4099
|
||||
)
|
||||
else()
|
||||
target_compile_options(
|
||||
common
|
||||
@@ -171,9 +174,8 @@ else()
|
||||
# Clang wrapper supplies those paths itself (via -nostdinc++), so at compile time the
|
||||
# flag is unused -> Clang errors under our -Werror. At link time the flag IS consumed
|
||||
# (it selects the C++ runtime), so we move it there instead of dropping it entirely.
|
||||
get_filename_component(_cxx_real "${CMAKE_CXX_COMPILER}" REALPATH)
|
||||
if(
|
||||
_cxx_real MATCHES "^/nix/store/"
|
||||
is_nix_compiler
|
||||
AND is_linux
|
||||
AND is_clang
|
||||
AND CMAKE_CXX_FLAGS MATCHES "stdlib=libstdc"
|
||||
@@ -267,10 +269,50 @@ elseif(use_lld)
|
||||
)
|
||||
if("${LD_VERSION}" MATCHES "LLD")
|
||||
target_link_libraries(common INTERFACE -fuse-ld=lld)
|
||||
# remembered for the linker flag probe below
|
||||
set(fuse_ld_flag "-fuse-ld=lld")
|
||||
endif()
|
||||
unset(LD_VERSION)
|
||||
endif()
|
||||
|
||||
# Linker warnings are errors where we control the toolchain and the dependencies: CI and the Nix dev shell.
|
||||
# On non-Nix macOS we suppress the deployment target warning: an old Conan profile may not pin os.version.
|
||||
# Only the new Apple linker understands the flag, so probe the actual linker (lld may be selected above).
|
||||
if(is_macos OR is_linux)
|
||||
if(is_ci OR is_nix_compiler)
|
||||
if(is_macos)
|
||||
set(fatal_warnings_flag "-Wl,-fatal_warnings")
|
||||
else()
|
||||
set(fatal_warnings_flag "-Wl,--fatal-warnings")
|
||||
endif()
|
||||
message(
|
||||
STATUS
|
||||
"Treating all linker warnings as errors (${fatal_warnings_flag})"
|
||||
)
|
||||
target_link_options(common INTERFACE "${fatal_warnings_flag}")
|
||||
unset(fatal_warnings_flag)
|
||||
elseif(is_macos)
|
||||
set(silence_flag "-Wl,-deployment_target_mismatches,suppress")
|
||||
set(probe_flags ${fuse_ld_flag} "${silence_flag}")
|
||||
include(CheckLinkerFlag)
|
||||
check_linker_flag(
|
||||
CXX
|
||||
"${probe_flags}"
|
||||
have_deployment_target_mismatches
|
||||
)
|
||||
if(have_deployment_target_mismatches)
|
||||
message(
|
||||
STATUS
|
||||
"Silencing macOS deployment target mismatch warnings (${silence_flag})"
|
||||
)
|
||||
target_link_options(common INTERFACE "${silence_flag}")
|
||||
endif()
|
||||
unset(probe_flags)
|
||||
unset(silence_flag)
|
||||
endif()
|
||||
endif()
|
||||
unset(fuse_ld_flag)
|
||||
|
||||
if(assert)
|
||||
foreach(var_ CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_RELEASE)
|
||||
string(REGEX REPLACE "[-/]DNDEBUG" "" ${var_} "${${var_}}")
|
||||
|
||||
@@ -51,6 +51,8 @@ target_compile_options(
|
||||
|
||||
target_link_libraries(xrpl.libpb PUBLIC protobuf::libprotobuf gRPC::grpc++)
|
||||
|
||||
add_dependencies(tidy_prerequisites xrpl.libpb)
|
||||
|
||||
# TODO: Clean up the number of library targets later.
|
||||
add_library(xrpl.imports.main INTERFACE)
|
||||
|
||||
@@ -133,6 +135,12 @@ target_link_libraries(
|
||||
add_module(xrpl resource)
|
||||
target_link_libraries(xrpl.libxrpl.resource PUBLIC xrpl.libxrpl.protocol)
|
||||
|
||||
add_module(xrpl peerfinder)
|
||||
target_link_libraries(
|
||||
xrpl.libxrpl.peerfinder
|
||||
PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.protocol
|
||||
)
|
||||
|
||||
# Level 08
|
||||
add_module(xrpl net)
|
||||
target_link_libraries(
|
||||
@@ -199,7 +207,21 @@ target_link_libraries(
|
||||
)
|
||||
|
||||
add_module(xrpl tx)
|
||||
target_link_libraries(xrpl.libxrpl.tx PUBLIC xrpl.libxrpl.ledger)
|
||||
target_link_libraries(
|
||||
xrpl.libxrpl.tx
|
||||
PUBLIC xrpl.libxrpl.ledger xrpl_wasm_vm_ffi_cxxbridge
|
||||
)
|
||||
add_dependencies(xrpl.libxrpl.tx xrpl_crates)
|
||||
|
||||
add_module(xrpl consensus)
|
||||
target_link_libraries(
|
||||
xrpl.libxrpl.consensus
|
||||
PUBLIC
|
||||
xrpl.libxrpl.basics
|
||||
xrpl.libxrpl.json
|
||||
xrpl.libxrpl.protocol
|
||||
xrpl.libxrpl.ledger
|
||||
)
|
||||
|
||||
add_library(xrpl.libxrpl)
|
||||
set_target_properties(xrpl.libxrpl PROPERTIES OUTPUT_NAME xrpl)
|
||||
@@ -220,6 +242,7 @@ target_link_modules(
|
||||
beast
|
||||
conditions
|
||||
config
|
||||
consensus
|
||||
core
|
||||
crypto
|
||||
git
|
||||
@@ -227,6 +250,7 @@ target_link_modules(
|
||||
ledger
|
||||
net
|
||||
nodestore
|
||||
peerfinder
|
||||
protocol
|
||||
protocol_autogen
|
||||
rdb
|
||||
|
||||
@@ -44,6 +44,7 @@ setup_target_for_coverage_gcovr(
|
||||
EXCLUDE
|
||||
"src/test"
|
||||
"src/tests"
|
||||
"src/benchmarks"
|
||||
"include/xrpl/beast/test"
|
||||
"include/xrpl/beast/unit_test"
|
||||
"${CMAKE_BINARY_DIR}/pb-xrpl.libpb"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#[===================================================================[
|
||||
Linux packaging support: 'package' target.
|
||||
|
||||
The packaging script (package/build_pkg.sh) installs to FHS-standard
|
||||
The packaging script (package/build_pkg.py) installs to FHS-standard
|
||||
paths (/usr/bin, /etc/xrpld, etc.) regardless of CMAKE_INSTALL_PREFIX,
|
||||
so no prefix guard is needed here.
|
||||
#]===================================================================]
|
||||
@@ -25,19 +25,33 @@ if(NOT (RPMBUILD_EXECUTABLE OR DPKG_BUILDPACKAGE_EXECUTABLE))
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(package_env
|
||||
SRC_DIR=${CMAKE_SOURCE_DIR}
|
||||
BUILD_DIR=${CMAKE_BINARY_DIR}
|
||||
PKG_RELEASE=${pkg_release}
|
||||
)
|
||||
if(NOT TARGET xrpld)
|
||||
message(STATUS "xrpld=ON is required; 'package' target not available")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT TARGET validator-keys)
|
||||
message(
|
||||
STATUS
|
||||
"validator_keys=ON is required; 'package' target not available"
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(DPKG_BUILDPACKAGE_EXECUTABLE)
|
||||
set(pkg_type deb)
|
||||
else()
|
||||
set(pkg_type rpm)
|
||||
endif()
|
||||
|
||||
add_custom_target(
|
||||
package
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E env ${package_env}
|
||||
${CMAKE_SOURCE_DIR}/package/build_pkg.sh
|
||||
${CMAKE_SOURCE_DIR}/package/build_pkg.py --package-type=${pkg_type}
|
||||
--build-dir=${CMAKE_BINARY_DIR} --pkg-release=${pkg_release}
|
||||
--channel=UNRELEASED
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
DEPENDS xrpld
|
||||
COMMENT "Building Linux package (deb/rpm inferred from host tooling)"
|
||||
DEPENDS xrpld validator-keys
|
||||
COMMENT "Building Linux ${pkg_type} package"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
@@ -2,21 +2,22 @@
|
||||
Protocol Autogen - Code generation for protocol wrapper classes
|
||||
#]===================================================================]
|
||||
|
||||
# The repository root, derived from the location of this file rather than from
|
||||
# the including project, so that the targets below can also be offered on their
|
||||
# own by cmake/codegen/CMakeLists.txt.
|
||||
get_filename_component(XRPL_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
|
||||
|
||||
set(CODEGEN_VENV_DIR
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/.venv"
|
||||
"${XRPL_ROOT}/.venv"
|
||||
CACHE PATH
|
||||
"Path to a Python virtual environment for code generation. A venv will be created here by setup_code_gen and used to run generation scripts."
|
||||
)
|
||||
|
||||
# Directory paths
|
||||
set(MACRO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl/protocol/detail")
|
||||
set(AUTOGEN_HEADER_DIR
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl/protocol_autogen"
|
||||
)
|
||||
set(AUTOGEN_TEST_DIR
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/tests/libxrpl/protocol_autogen"
|
||||
)
|
||||
set(SCRIPTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake/scripts/codegen")
|
||||
set(MACRO_DIR "${XRPL_ROOT}/include/xrpl/protocol/detail")
|
||||
set(AUTOGEN_HEADER_DIR "${XRPL_ROOT}/include/xrpl/protocol_autogen")
|
||||
set(AUTOGEN_TEST_DIR "${XRPL_ROOT}/src/tests/libxrpl/protocol_autogen")
|
||||
set(SCRIPTS_DIR "${XRPL_ROOT}/cmake/scripts/codegen")
|
||||
|
||||
# Input macro files
|
||||
set(TRANSACTIONS_MACRO "${MACRO_DIR}/transactions.macro")
|
||||
@@ -114,14 +115,14 @@ if(CODEGEN_VENV_DIR)
|
||||
setup_code_gen
|
||||
COMMAND ${Python3_EXECUTABLE} -m venv "${CODEGEN_VENV_DIR}"
|
||||
COMMAND ${CODEGEN_PYTHON} -m pip install -r "${REQUIREMENTS_FILE}"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
WORKING_DIRECTORY "${XRPL_ROOT}"
|
||||
COMMENT "Creating venv and installing code generation dependencies..."
|
||||
)
|
||||
else()
|
||||
add_custom_target(
|
||||
setup_code_gen
|
||||
COMMAND ${Python3_EXECUTABLE} -m pip install -r "${REQUIREMENTS_FILE}"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
WORKING_DIRECTORY "${XRPL_ROOT}"
|
||||
COMMENT "Installing code generation dependencies..."
|
||||
)
|
||||
endif()
|
||||
@@ -139,8 +140,8 @@ add_custom_target(
|
||||
-DSFIELDS_MACRO=${SFIELDS_MACRO}
|
||||
-DAUTOGEN_HEADER_DIR=${AUTOGEN_HEADER_DIR}
|
||||
-DAUTOGEN_TEST_DIR=${AUTOGEN_TEST_DIR} -P
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/XrplProtocolAutogenRun.cmake"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/XrplProtocolAutogenRun.cmake"
|
||||
WORKING_DIRECTORY "${XRPL_ROOT}"
|
||||
COMMENT "Running protocol code generation..."
|
||||
SOURCES ${ALL_INPUT_FILES}
|
||||
)
|
||||
|
||||
@@ -36,6 +36,19 @@ elseif(is_gcc)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# A Nix compiler is only meant to be used from a managed environment: the xrpld
|
||||
# dev shell (which exports XRPL_DEVSHELL) or the CI image. Using one from a bare
|
||||
# shell usually means a leaked toolchain (picked up via PATH or a Conan profile)
|
||||
# and leads to confusing breakage, so fail early with guidance.
|
||||
if(is_nix_compiler AND NOT is_ci_image AND NOT DEFINED ENV{XRPL_DEVSHELL})
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"A Nix compiler (${CMAKE_CXX_COMPILER}) is being used outside the xrpld "
|
||||
"dev shell. Enter it with `nix develop` (see docs/build/nix.md) before "
|
||||
"configuring the build."
|
||||
)
|
||||
endif()
|
||||
|
||||
# check for in-source build and fail
|
||||
if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
|
||||
message(
|
||||
|
||||
@@ -30,6 +30,8 @@ if(tests)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
option(benchmark "Build benchmarks" ON)
|
||||
|
||||
# Enabled by default so every header is compiled on its own as the main file of
|
||||
# its own compile_commands.json entry - this is what lets clang-tidy (and clangd
|
||||
# and IDEs) analyse a header's own includes directly. The per-header objects are
|
||||
|
||||
@@ -5,22 +5,39 @@ option(
|
||||
)
|
||||
|
||||
if(validator_keys)
|
||||
git_branch(current_branch)
|
||||
# default to tracking VK master branch unless we are on release
|
||||
if(NOT (current_branch STREQUAL "release"))
|
||||
set(current_branch "master")
|
||||
endif()
|
||||
message(STATUS "Tracking ValidatorKeys branch: ${current_branch}")
|
||||
# Own the install destination below rather than relying on another module
|
||||
# having pulled this in first.
|
||||
include(GNUInstallDirs)
|
||||
|
||||
# Pinned to an exact commit, not a branch: the tool ships inside our
|
||||
# packages, so the same xrpld version must always package the same
|
||||
# validator-keys. Bump this deliberately.
|
||||
set(validator_keys_commit "4c0fb75eec9601c711645998c904507e87e910ae")
|
||||
message(STATUS "Using ValidatorKeys commit: ${validator_keys_commit}")
|
||||
|
||||
FetchContent_Declare(
|
||||
validator_keys
|
||||
GIT_REPOSITORY https://github.com/ripple/validator-keys-tool.git
|
||||
GIT_TAG "${current_branch}"
|
||||
GIT_TAG "${validator_keys_commit}"
|
||||
)
|
||||
FetchContent_MakeAvailable(validator_keys)
|
||||
# The tool's own CMakeLists excludes the target from 'all' when it is built
|
||||
# as a subproject. Undo that, so validator_keys=ON really does build it.
|
||||
set_target_properties(
|
||||
validator-keys
|
||||
PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
EXCLUDE_FROM_ALL OFF
|
||||
EXCLUDE_FROM_DEFAULT_BUILD OFF
|
||||
)
|
||||
# We ship this binary, so like xrpld it must not keep the Nix store's ELF
|
||||
# loader, or it cannot run on the target distro at all.
|
||||
patch_nix_binary(validator-keys)
|
||||
|
||||
configure_file(
|
||||
"${validator_keys_SOURCE_DIR}/LICENSE"
|
||||
"${CMAKE_BINARY_DIR}/validator-keys-LICENSE"
|
||||
COPYONLY
|
||||
)
|
||||
install(TARGETS validator-keys RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endif()
|
||||
|
||||
21
cmake/codegen/CMakeLists.txt
Normal file
21
cmake/codegen/CMakeLists.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
#[===================================================================[
|
||||
Protocol Autogen - Standalone project
|
||||
|
||||
Exposes the 'setup_code_gen' and 'code_gen' targets on their own, without
|
||||
configuring the rest of xrpl. Code generation is pure Python, so this needs
|
||||
neither the dependencies nor a compiler, which makes it usable in CI and by
|
||||
contributors who only want to regenerate the protocol wrapper classes:
|
||||
|
||||
cmake -S cmake/codegen -B build/codegen
|
||||
cmake --build build/codegen --target setup_code_gen
|
||||
cmake --build build/codegen --target code_gen
|
||||
|
||||
The targets are identical to the ones offered by the top-level build, since
|
||||
both come from cmake/XrplProtocolAutogen.cmake.
|
||||
#]===================================================================]
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(xrpl_codegen LANGUAGES NONE)
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../XrplProtocolAutogen.cmake")
|
||||
@@ -8,6 +8,7 @@ Uses pcpp to preprocess the macro file and pyparsing to parse the DSL.
|
||||
|
||||
import io
|
||||
import argparse
|
||||
import re
|
||||
from pathlib import Path
|
||||
import pyparsing as pp
|
||||
|
||||
@@ -53,28 +54,89 @@ def create_transaction_parser():
|
||||
return macro_parser
|
||||
|
||||
|
||||
# Defaults for xrpl::TxSettings members, mirroring
|
||||
# include/xrpl/protocol/TxSettings.h. A transaction's settings blob only names
|
||||
# the members that differ from these.
|
||||
SETTING_DEFAULTS = {
|
||||
"delegable": "Delegation::NotDelegable",
|
||||
"amendment": "uint256{}",
|
||||
"privileges": "Privilege::NoPriv",
|
||||
}
|
||||
|
||||
|
||||
def parse_settings(settings_str):
|
||||
"""Parse a TxSettings blob into a dict, filling in defaults.
|
||||
|
||||
Args:
|
||||
settings_str: A string like '({.delegable = Delegation::NotDelegable,
|
||||
.privileges = Privilege::CreateAcct})', or '({})'.
|
||||
|
||||
Returns:
|
||||
A dict with a value for every key in SETTING_DEFAULTS.
|
||||
"""
|
||||
body = settings_str.strip()
|
||||
if not (body.startswith("(") and body.endswith(")")):
|
||||
raise ValueError(
|
||||
f"Malformed settings blob, expected '({{...}})': {settings_str!r}"
|
||||
)
|
||||
body = body[1:-1].strip()
|
||||
if not (body.startswith("{") and body.endswith("}")):
|
||||
raise ValueError(
|
||||
f"Malformed settings blob, expected '({{...}})': {settings_str!r}"
|
||||
)
|
||||
body = body[1:-1]
|
||||
|
||||
# Strip comments, which may be interleaved with the designated initializers.
|
||||
body = re.sub(r"//[^\n]*", "", body)
|
||||
|
||||
settings = dict(SETTING_DEFAULTS)
|
||||
seen = set()
|
||||
# Each entry runs from '.key =' up to the next '.key =' or the end.
|
||||
for key, value in re.findall(
|
||||
r"\.(\w+)\s*=\s*(.*?)(?=,\s*\.\w+\s*=|,?\s*$)", body, re.S
|
||||
):
|
||||
if key not in SETTING_DEFAULTS:
|
||||
raise ValueError(f"Unknown TxSettings member '.{key}' in {settings_str!r}")
|
||||
settings[key] = " ".join(value.split()).rstrip(",")
|
||||
seen.add(key)
|
||||
|
||||
# Catch a typo'd or unparsed initializer rather than silently defaulting it.
|
||||
# Every '.member' in the blob must have been consumed above.
|
||||
if len(re.findall(r"\.\w+", body)) != len(seen):
|
||||
raise ValueError(f"Could not parse every setting in {settings_str!r}")
|
||||
|
||||
# A blob with content but no designated initializer is positional, which
|
||||
# would otherwise be read as "all defaults" and silently generate the
|
||||
# wrong output.
|
||||
if body.strip() and not seen:
|
||||
raise ValueError(
|
||||
"TxSettings requires designated initializers (.member = value), "
|
||||
f"got {settings_str!r}"
|
||||
)
|
||||
|
||||
return settings
|
||||
|
||||
|
||||
def parse_transaction_args(args_list):
|
||||
"""Parse the arguments of a TRANSACTION macro call.
|
||||
|
||||
Args:
|
||||
args_list: A list of parsed arguments from pyparsing, e.g.,
|
||||
['ttPAYMENT', '0', 'Payment', 'Delegation::delegable',
|
||||
'uint256{}', 'createAcct', '({...})']
|
||||
['ttPAYMENT', '0', 'Payment',
|
||||
'({.privileges = Privilege::CreateAcct})', '({...})']
|
||||
|
||||
Returns:
|
||||
A dict with parsed transaction information.
|
||||
"""
|
||||
if len(args_list) < 7:
|
||||
if len(args_list) < 5:
|
||||
raise ValueError(
|
||||
f"Expected at least 7 parts in TRANSACTION, got {len(args_list)}: {args_list}"
|
||||
f"Expected at least 5 parts in TRANSACTION, got {len(args_list)}: {args_list}"
|
||||
)
|
||||
|
||||
tag = args_list[0]
|
||||
value = args_list[1]
|
||||
name = args_list[2]
|
||||
delegable = args_list[3]
|
||||
amendments = args_list[4]
|
||||
privileges = args_list[5]
|
||||
settings = parse_settings(args_list[3])
|
||||
fields_str = args_list[-1]
|
||||
|
||||
# Parse fields: ({field1, field2, ...})
|
||||
@@ -84,9 +146,9 @@ def parse_transaction_args(args_list):
|
||||
"tag": tag,
|
||||
"value": value,
|
||||
"name": name,
|
||||
"delegable": delegable,
|
||||
"amendments": amendments,
|
||||
"privileges": privileges,
|
||||
"delegable": settings["delegable"],
|
||||
"amendments": settings["amendment"],
|
||||
"privileges": settings["privileges"],
|
||||
"fields": fields,
|
||||
}
|
||||
|
||||
|
||||
15
conan.lock
15
conan.lock
@@ -10,22 +10,25 @@
|
||||
"rocksdb/10.5.1#4a197eca381a3e5ae8adf8cffa5aacd0%1782392413.075713",
|
||||
"re2/20251105#8579cfd0bda4daf0683f9e3898f964b4%1782392402.431897",
|
||||
"protobuf/6.33.5#ff253ead763bd8d9904a52979cd21e81%1782392410.233933",
|
||||
"openssl/3.6.3#1163d4ddc603907084d08a6a0c6e580f%1782307150.583886",
|
||||
"openssl/3.6.3#f806de8933e3bf6f01016c6a888cee2e%1783945160.863288",
|
||||
"nudb/2.0.9#11149c73f8f2baff9a0198fe25971fc7%1782392402.297166",
|
||||
"mpt-crypto/0.4.0-rc2#a580f2f9ad0e795de696aa62d54fb9af%1782425834.488828",
|
||||
"mpt-crypto/1.0.2#b313cef0c1a493eb970ad185b2e9bab7%1784285108.866483",
|
||||
"lz4/1.10.0#982d9b673900f665a1da109e09c17cab%1782392402.164188",
|
||||
"libiconv/1.17#9923bc6dc6f106646d6967e0039a5ada%1782392792.775744",
|
||||
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1782392402.420732",
|
||||
"libarchive/3.8.7#c446109bd1f1d8ba7936c94189bc50e6%1782392403.066892",
|
||||
"jemalloc/5.3.1#1fc58d55316041f10fbc1e8a2eae632a%1776700028.228",
|
||||
"gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1782392402.791979",
|
||||
"grpc/1.81.1#5217e6ef0544c42b46f4af35d5e7f649%1782307148.845616",
|
||||
"grpc/1.81.1#f729f6d75992d20f9c72828e9142d62f%1783945160.094135",
|
||||
"fast_float/8.2.10#f6f28d6bb22112078e7dbda611caf681%1782494504.298",
|
||||
"ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1782307148.15562",
|
||||
"date/3.0.4#862e11e80030356b53c2c38599ceb32b%1782392402.538492",
|
||||
"corrosion/0.6.1#bfa292df0a957bc70a450ff316cd9435%1786119416.131296",
|
||||
"c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1782392402.681654",
|
||||
"bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1782392402.296732",
|
||||
"boost/1.91.0#ea540ca2133d831b560036aa24dece3c%1782392419.475605",
|
||||
"abseil/20250127.0#bb0baf1f362bc4a725a24eddd419b8f7%1782307147.395833"
|
||||
"benchmark/1.9.5#b885dc73ad67b40a55d45684d1c88ad1%1782736613.864841",
|
||||
"abseil/20250127.0#9ef01c1451a8340f9022e46238c0fbb6%1783945159.651047"
|
||||
],
|
||||
"build_requires": [
|
||||
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708",
|
||||
@@ -33,12 +36,12 @@
|
||||
"protobuf/6.33.5#ff253ead763bd8d9904a52979cd21e81%1782392410.233933",
|
||||
"nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1782395690.33162",
|
||||
"msys2/cci.latest#d22fe7b2808f5fd34d0a7923ace9c54f%1770657326.649",
|
||||
"m4/1.4.19#34c4bbc3eeebe98ca6edf2f52d602e7d%1777282960.259",
|
||||
"m4/1.4.19#1727f439cf74e83826ec96d0b4904eee%1784541921.659",
|
||||
"cmake/4.3.3#840cf00ea09777e05c2050a50a82c722%1782392418.696091",
|
||||
"b2/5.4.2#ffd6084a119587e70f11cd45d1a386e2%1782392402.624226",
|
||||
"automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56",
|
||||
"autoconf/2.71#51077f068e61700d65bb05541ea1e4b0%1731054366.86",
|
||||
"abseil/20250127.0#bb0baf1f362bc4a725a24eddd419b8f7%1782307147.395833"
|
||||
"abseil/20250127.0#9ef01c1451a8340f9022e46238c0fbb6%1783945159.651047"
|
||||
],
|
||||
"python_requires": [],
|
||||
"overrides": {
|
||||
|
||||
21
conan/init.sh
Executable file
21
conan/init.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install our Conan configuration, profiles and the xrplf remote into CONAN_HOME.
|
||||
# Safe to re-run; never deletes the Conan home.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONAN_DIR="$(conan config home)"
|
||||
|
||||
echo "Installing Conan configuration into ${CONAN_DIR}"
|
||||
conan config install "${SCRIPT_DIR}/global.conf"
|
||||
conan config install "${SCRIPT_DIR}/profiles" -tf "${CONAN_DIR}/profiles"
|
||||
# This script manages these files, so make them read-only - Conan does not
|
||||
# preserve the source mode. Only the files: the directories must stay writable
|
||||
# for `conan config install` to replace them.
|
||||
chmod a-w "${CONAN_DIR}/global.conf"
|
||||
find "${CONAN_DIR}/profiles" -type f -exec chmod a-w {} +
|
||||
|
||||
echo "Adding the xrplf Conan remote"
|
||||
# --index 0: our patched recipes must win over Conan Center.
|
||||
conan remote add --index 0 --force xrplf https://conan.xrplf.org/repository/conan/
|
||||
@@ -1,9 +1,13 @@
|
||||
{% set os = detect_api.detect_os() %}
|
||||
{% set arch = detect_api.detect_arch() %}
|
||||
{% set compiler, version, compiler_exe = detect_api.detect_default_compiler() %}
|
||||
{% set compiler_version = version %}
|
||||
{% if os == "Linux" %}
|
||||
{% set compiler_version = detect_api.default_compiler_version(compiler, version) %}
|
||||
{% if os == "Macos" %}
|
||||
{# Minimum macOS the dependencies target. #}
|
||||
{# Without this, Conan builds each dependency against the (possibly newer) host SDK, so the #}
|
||||
{# dependency objects target a newer macOS than the binary and the linker warns. #}
|
||||
{# Keep at or below CMAKE_OSX_DEPLOYMENT_TARGET in CMakeLists.txt. #}
|
||||
{% set min_macos_version = "15.0" %}
|
||||
{% endif %}
|
||||
|
||||
[settings]
|
||||
@@ -18,10 +22,39 @@ compiler.runtime=static
|
||||
{% else %}
|
||||
compiler.libcxx={{ detect_api.detect_libcxx(compiler, version, compiler_exe) }}
|
||||
{% endif %}
|
||||
{% if os == "Macos" %}
|
||||
os.version={{ min_macos_version }}
|
||||
{% endif %}
|
||||
|
||||
[conf]
|
||||
{# The Boost recipe builds with b2, which doesn't use Conan's toolchain files. #}
|
||||
{# Instead it hand-rolls the compiler for user-config.jam, #}
|
||||
{# and its fallback probes a version-suffixed binary (e.g. `g++-15`) before plain `g++`. #}
|
||||
{# Inside the Nix shell the wrapper only provides `g++`/`gcc` (no `-15` suffix), #}
|
||||
{# so on a host that also has a system `g++-15` the probe escapes Nix #}
|
||||
{# and picks the system compiler, which is mismatched with the Nix libraries #}
|
||||
{# and breaks the build (e.g. Boost.Stacktrace link checks fail). #}
|
||||
{# Pinning the executables here short-circuits that probe so Boost (and the rest of the toolchain) #}
|
||||
{# resolve the same compiler. #}
|
||||
{# Not part of the package ID, so binaries stay shareable. #}
|
||||
{% if os != "Windows" %}
|
||||
{% set cc_exe = {"gcc": "gcc", "clang": "clang", "apple-clang": "clang"}.get(compiler) %}
|
||||
{% set cxx_exe = {"gcc": "g++", "clang": "clang++", "apple-clang": "clang++"}.get(compiler) %}
|
||||
tools.build:compiler_executables={'c':'{{ cc_exe }}','cpp':'{{ cxx_exe }}'}
|
||||
{% endif %}
|
||||
|
||||
{# By default, Conan tries to reuse binaries built with different cppstd versions. #}
|
||||
{# We want to avoid that to improve reproduceability, so we add the cppstd version to the package ID. #}
|
||||
{# More info: https://docs.conan.io/2/reference/extensions/binary_compatibility.html #}
|
||||
user.package:cppstd_version=23
|
||||
tools.info.package_id:confs+=["user.package:cppstd_version"]
|
||||
|
||||
{% if os == "Macos" %}
|
||||
[buildenv]
|
||||
{# os.version adds -mmacosx-version-min to compiler command lines, #}
|
||||
{# but Boost.Context's b2 assembly (.S) rule ignores it, #}
|
||||
{# so those objects keep the host SDK version and still warn at link time. #}
|
||||
{# clang's assembler honors this env var regardless, pinning them. #}
|
||||
{# Scoped to boost/* since it is the only gap. #}
|
||||
boost/*:MACOSX_DEPLOYMENT_TARGET={{ min_macos_version }}
|
||||
{% endif %}
|
||||
|
||||
14
conanfile.py
14
conanfile.py
@@ -15,6 +15,7 @@ class Xrpl(ConanFile):
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
options = {
|
||||
"assertions": [True, False],
|
||||
"benchmark": [True, False],
|
||||
"coverage": [True, False],
|
||||
"fPIC": [True, False],
|
||||
"jemalloc": [True, False],
|
||||
@@ -27,7 +28,9 @@ class Xrpl(ConanFile):
|
||||
}
|
||||
|
||||
requires = [
|
||||
"corrosion/0.6.1",
|
||||
"ed25519/2015.03",
|
||||
"fast_float/8.2.10",
|
||||
"grpc/1.81.1",
|
||||
"libarchive/3.8.7",
|
||||
"nudb/2.0.9",
|
||||
@@ -46,6 +49,7 @@ class Xrpl(ConanFile):
|
||||
|
||||
default_options = {
|
||||
"assertions": False,
|
||||
"benchmark": True,
|
||||
"coverage": False,
|
||||
"fPIC": True,
|
||||
"jemalloc": False,
|
||||
@@ -129,12 +133,14 @@ class Xrpl(ConanFile):
|
||||
self.options["boost"].without_cobalt = True
|
||||
|
||||
def requirements(self):
|
||||
if self.options.benchmark:
|
||||
self.requires("benchmark/1.9.5")
|
||||
self.requires("boost/1.91.0", force=True, transitive_headers=True)
|
||||
self.requires("date/3.0.4", transitive_headers=True)
|
||||
if self.options.jemalloc:
|
||||
self.requires("jemalloc/5.3.1")
|
||||
self.requires("lz4/1.10.0", force=True)
|
||||
self.requires("mpt-crypto/0.4.0-rc2", transitive_headers=True)
|
||||
self.requires("mpt-crypto/1.0.2", transitive_headers=True)
|
||||
self.requires("protobuf/6.33.5", force=True)
|
||||
if self.options.rocksdb:
|
||||
self.requires("rocksdb/10.5.1")
|
||||
@@ -146,8 +152,12 @@ class Xrpl(ConanFile):
|
||||
"CMakeLists.txt",
|
||||
"cfg/*",
|
||||
"cmake/*",
|
||||
"crates/*",
|
||||
"crates/.cargo/*",
|
||||
"!crates/target/*",
|
||||
"external/*",
|
||||
"include/*",
|
||||
"rust-toolchain.toml",
|
||||
"src/*",
|
||||
)
|
||||
|
||||
@@ -162,6 +172,7 @@ class Xrpl(ConanFile):
|
||||
def generate(self):
|
||||
tc = CMakeToolchain(self)
|
||||
tc.variables["tests"] = self.options.tests
|
||||
tc.variables["benchmark"] = self.options.benchmark
|
||||
tc.variables["assert"] = self.options.assertions
|
||||
tc.variables["coverage"] = self.options.coverage
|
||||
tc.variables["jemalloc"] = self.options.jemalloc
|
||||
@@ -206,6 +217,7 @@ class Xrpl(ConanFile):
|
||||
"boost::thread",
|
||||
"date::date",
|
||||
"ed25519::ed25519",
|
||||
"fast_float::fast_float",
|
||||
"grpc::grpc++",
|
||||
"libarchive::libarchive",
|
||||
"lz4::lz4",
|
||||
|
||||
17
crates/.cargo/config.toml
Normal file
17
crates/.cargo/config.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
# The Rust static libraries are linked into C++ targets, so the runtime linkage
|
||||
# here has to match what the C++ build uses (see cmake/XrplCompiler.cmake).
|
||||
#
|
||||
# macOS needs nothing: AppleClang cannot link libgcc/libc++ statically, so the
|
||||
# C++ build skips those flags on Apple as well.
|
||||
|
||||
# Both amd64 and arm64 Linux builds link libgcc statically. This only affects
|
||||
# links that rustc itself drives (`cargo test` binaries and the like) — the
|
||||
# `staticlib` crates consumed by CMake are archived, not linked, so rustc
|
||||
# silently ignores link args for them. Keeping libgcc_s.so.1 off the xrpld link
|
||||
# line is handled in crates/CMakeLists.txt instead.
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-args=-static-libgcc"]
|
||||
|
||||
# Windows builds use the static MSVC runtime.
|
||||
[target.'cfg(windows)']
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
111
crates/CMakeLists.txt
Normal file
111
crates/CMakeLists.txt
Normal file
@@ -0,0 +1,111 @@
|
||||
find_package(Corrosion REQUIRED)
|
||||
|
||||
corrosion_import_crate(MANIFEST_PATH ${CMAKE_CURRENT_SOURCE_DIR}/Cargo.toml)
|
||||
|
||||
# The generated C++ lands in the build tree, so put a .clang-tidy next to it to
|
||||
# keep clang-tidy from analyzing code we don't own.
|
||||
configure_file(
|
||||
generated.clang-tidy
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/.clang-tidy"
|
||||
COPYONLY
|
||||
)
|
||||
|
||||
add_custom_target(xrpl_crates)
|
||||
add_dependencies(tidy_prerequisites xrpl_crates)
|
||||
|
||||
# On macOS, ld warns `ignoring duplicate libraries` when linking a crate.
|
||||
# Corrosion is the source of both duplicates it names:
|
||||
#
|
||||
# * The crate archive and its cxxbridge archive, because
|
||||
# `corrosion_add_cxxbridge` makes the two depend on each other, and CMake
|
||||
# repeats a static library cycle on the link line so single-pass linkers can
|
||||
# resolve it. (LINK_INTERFACE_MULTIPLICITY can only raise that count.)
|
||||
# * `-lSystem`, which Corrosion copies from rustc's `native-static-libs` even
|
||||
# though the compiler driver always links libSystem.
|
||||
#
|
||||
# ld needs neither: it resolves the cycle from one copy of each archive and
|
||||
# links libSystem once. So silence the warning rather than rewrite Corrosion's
|
||||
# link interface, which the cycle is also part of. The option itself is old —
|
||||
# Xcode 15 is only where the warning became the default — and the check below
|
||||
# leaves it out on a linker that does not know it.
|
||||
if(is_macos)
|
||||
include(CheckLinkerFlag)
|
||||
check_linker_flag(
|
||||
CXX
|
||||
-Wl,-no_warn_duplicate_libraries
|
||||
have_no_warn_duplicate_libraries
|
||||
)
|
||||
endif()
|
||||
|
||||
function(_unlink_libgcc_s crate)
|
||||
if(NOT (is_linux AND static))
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Corrosion exposes a crate's staticlib as an imported `<crate>-static`
|
||||
# target and puts the native libs in its INTERFACE_LINK_LIBRARIES. If either
|
||||
# of those changes, warn instead of silently letting libgcc_s.so.1 return.
|
||||
set(imported "${crate}-static")
|
||||
if(NOT TARGET ${imported})
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Corrosion did not create the imported target '${imported}', so "
|
||||
"libgcc_s cannot be removed from the link interface of '${crate}'. "
|
||||
"xrpld will link libgcc_s.so.1 dynamically. Check where Corrosion "
|
||||
"${CORROSION_VERSION} now records `native-static-libs`."
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
||||
get_target_property(libs ${imported} INTERFACE_LINK_LIBRARIES)
|
||||
if(NOT "gcc_s" IN_LIST libs)
|
||||
message(
|
||||
WARNING
|
||||
"'gcc_s' was not in the link interface of '${imported}' as "
|
||||
"expected. If the Rust toolchain stopped reporting it this "
|
||||
"workaround is obsolete and can be deleted; otherwise xrpld may "
|
||||
"link libgcc_s.so.1 dynamically. Verify with: "
|
||||
"objdump -p xrpld | grep NEEDED"
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
||||
list(REMOVE_ITEM libs gcc_s)
|
||||
set_property(TARGET ${imported} PROPERTY INTERFACE_LINK_LIBRARIES ${libs})
|
||||
endfunction()
|
||||
|
||||
function(add_xrpl_crate name)
|
||||
cmake_parse_arguments(ARG "" "CRATE" "FILES" ${ARGN})
|
||||
_unlink_libgcc_s(${ARG_CRATE})
|
||||
# `cc` picks its runtime flag from `crt-static` alone, so it compiles a
|
||||
# crate's C++ with `-MT`; Debug needs `-MTd` (to match cmake/XrplCompiler.cmake).
|
||||
if(is_msvc)
|
||||
corrosion_set_env_vars(
|
||||
${ARG_CRATE}
|
||||
"$<$<CONFIG:Debug>:CXXFLAGS=-MTd>"
|
||||
)
|
||||
endif()
|
||||
corrosion_add_cxxbridge(${name}_cxxbridge CRATE ${ARG_CRATE} FILES
|
||||
${ARG_FILES}
|
||||
)
|
||||
# Generated cxxbridge headers don't exist at configure time; CMake 3.28+
|
||||
# validates INTERFACE_SOURCES on consuming targets. Clear it to skip the
|
||||
# existence check — build-time ordering is enforced by the custom commands.
|
||||
set_target_properties(${name}_cxxbridge PROPERTIES INTERFACE_SOURCES "")
|
||||
if(have_no_warn_duplicate_libraries)
|
||||
target_link_options(
|
||||
${name}_cxxbridge
|
||||
INTERFACE -Wl,-no_warn_duplicate_libraries
|
||||
)
|
||||
endif()
|
||||
add_dependencies(xrpl_crates ${name}_cxxbridge)
|
||||
endfunction()
|
||||
|
||||
add_xrpl_crate(xrpl_wasm_vm_ffi CRATE xrpl_wasm_vm_ffi FILES lib.rs)
|
||||
|
||||
add_xrpl_crate(xrpl_wasm_testkit CRATE xrpl_wasm_testkit FILES lib.rs)
|
||||
|
||||
target_include_directories(
|
||||
xrpl_wasm_vm_ffi_cxxbridge
|
||||
PRIVATE ${CMAKE_SOURCE_DIR}/include
|
||||
)
|
||||
498
crates/Cargo.lock
generated
Normal file
498
crates/Cargo.lock
generated
Normal file
@@ -0,0 +1,498 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"termcolor",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.199"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824894a4a85dca76d4c95c2b9098c036f5a29f627b30c12780774f6654e60974"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxx-build",
|
||||
"cxxbridge-cmd",
|
||||
"cxxbridge-flags",
|
||||
"cxxbridge-macro",
|
||||
"foldhash 0.2.0",
|
||||
"link-cplusplus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx-build"
|
||||
version = "1.0.199"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1ae0b651ea5b0000b19513aef5a03f194d7e3486f2d9258b658da8677fe9036"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"codespan-reporting",
|
||||
"indexmap",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"scratch",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-cmd"
|
||||
version = "1.0.199"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb05f91d3fb8435d9bab6ac5ce6ac1868be774325fb7fb2a91be39393b21388e"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"codespan-reporting",
|
||||
"indexmap",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.199"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf293202e0e3e98495785745389e8d0755b217e66f19194a5c695c25e03282ef"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.199"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca001d746947c7249ed9d332a10f7a59daedbafeb0ec68c5c18a7db7a93f6ccc"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"foldhash 0.1.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||
|
||||
[[package]]
|
||||
name = "link-cplusplus"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scratch"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
|
||||
|
||||
[[package]]
|
||||
name = "string-interner"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23de088478b31c349c9ba67816fa55d9355232d63c3afea8bf513e31f0f1d2c0"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.5",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.254.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09480d646178e5fdd12bb06e812d0af9a3a191dbc9cd697fdc86687beade7393"
|
||||
dependencies = [
|
||||
"leb128fmt",
|
||||
"wasmparser 0.254.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmi"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78693fcdd618e0fc34af59c6b8efa9ac5d58c68df940beff4bedddb6acfe7c27"
|
||||
dependencies = [
|
||||
"spin",
|
||||
"wasmi_collections",
|
||||
"wasmi_core",
|
||||
"wasmi_ir",
|
||||
"wasmparser 0.228.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmi_collections"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a8be2aa467cf2d29e96ff759472c36eeb44a3c81c67fc9cb76c9a24c519c557"
|
||||
dependencies = [
|
||||
"string-interner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmi_core"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69372d5fda3ea3d1e0aa6603c7888110e0187e88ea17cd8fc2e2df0a0e1f37fa"
|
||||
dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmi_ir"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f17b774caa13c618c7244f1ee51fe23c5e7b8538a471fa46d9949779758aed6"
|
||||
dependencies = [
|
||||
"wasmi_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.228.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4abf1132c1fdf747d56bbc1bb52152400c70f336870f968b85e89ea422198ae3"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.254.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5769a29f799fbab136aaf65b4fe5384cd7d93fe6fc9ba0dcb6c8382a1f16e27"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wast"
|
||||
version = "254.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7ed4dfc8f6b9fc38b231065e2cdfbf7359af5ab945990abf09658dcc63c3e32"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"leb128fmt",
|
||||
"memchr",
|
||||
"unicode-width",
|
||||
"wasm-encoder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wat"
|
||||
version = "1.254.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7127f7f9b8f127c879991cecd35f494e4628bae1b0874c681414d8d8831e952c"
|
||||
dependencies = [
|
||||
"wast",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-host-functions"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"xrpl-host-functions-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-host-functions-macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.3",
|
||||
"xrpl-host-functions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-wasm-testkit"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cxx",
|
||||
"wat",
|
||||
"xrpl-host-functions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-wasm-vm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"wasmi",
|
||||
"wat",
|
||||
"xrpl-host-functions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrpl-wasm-vm-ffi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cxx",
|
||||
"xrpl-host-functions",
|
||||
"xrpl-wasm-vm",
|
||||
]
|
||||
24
crates/Cargo.toml
Normal file
24
crates/Cargo.toml
Normal file
@@ -0,0 +1,24 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"xrpl-wasm-vm-ffi",
|
||||
"xrpl-wasm-vm",
|
||||
"xrpl-wasm-testkit",
|
||||
"xrpl-host-functions",
|
||||
"xrpl-host-functions-macros",
|
||||
]
|
||||
resolver = "3"
|
||||
|
||||
[workspace.dependencies]
|
||||
cxx = { version = "1.0.199", features = ["c++20"] }
|
||||
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
|
||||
[workspace.lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(coverage)', 'cfg(coverage_nightly)' ] }
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
overflow-checks = true
|
||||
lto = true
|
||||
debug = true
|
||||
120
crates/README.md
Normal file
120
crates/README.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Rust crates
|
||||
|
||||
This directory holds the WebAssembly engine that runs Smart Escrow contracts,
|
||||
bridged into C++ via `cxxbridge`/the `cxx` crate.
|
||||
|
||||
The workspace is built unconditionally — `add_subdirectory(crates)` in the
|
||||
top-level `CMakeLists.txt` is not behind an option, and
|
||||
`xrpl_wasm_vm_ffi_cxxbridge` is a `PUBLIC` dependency of
|
||||
`xrpl.libxrpl.ledger` (see `cmake/XrplCore.cmake`). The Rust toolchain pinned in
|
||||
[`rust-toolchain.toml`](../rust-toolchain.toml) is therefore required to build
|
||||
`libxrpl` at all; the Nix devshell provides it automatically.
|
||||
|
||||
## The crates
|
||||
|
||||
Dependencies run in one direction: the ABI crate at the bottom, the engine on
|
||||
top of it, and the two bridges at the edge.
|
||||
|
||||
### `xrpl-host-functions`
|
||||
|
||||
The wasm host ABI, declared exactly once. A `host_functions!` block at the
|
||||
bottom of `src/lib.rs` generates the `HostFunctions` trait a host implements and
|
||||
the `HostFunctionSpec` table a wasm engine registers from. Only the vocabulary
|
||||
the declarations are written in — `HostError`, `HostResult`, `TraceDataType`,
|
||||
`HASH_LEN` — is hand-written.
|
||||
|
||||
**Add or change a host function here**, never in the engine or the bridge: the
|
||||
expansion names nothing this file does not, so neither side of the FFI boundary
|
||||
gets to restate a signature.
|
||||
|
||||
`no_std`, because this crate is also what a guest contract links against.
|
||||
|
||||
### `xrpl-host-functions-macros`
|
||||
|
||||
The proc macro behind that block, plus the `wasmi_glue!` marshalling it
|
||||
generates. An implementation detail of the crate above — nothing else should
|
||||
depend on it.
|
||||
|
||||
The dev-dependency back on `xrpl-host-functions` is a deliberate cycle: the
|
||||
doctests declare host functions returning `HostResult`, which the facade crate
|
||||
hand-writes. Cargo allows it because dev-dependencies sit outside the library
|
||||
build graph.
|
||||
|
||||
### `xrpl-wasm-vm`
|
||||
|
||||
The engine itself, on `wasmi`: preflight validation (`preflight/`), gas
|
||||
metering and execution (`vm.rs`), and host-call dispatch (`abi.rs`, `args.rs`,
|
||||
`register.rs`).
|
||||
|
||||
Two lint decisions are load-bearing, both because this is a consensus path:
|
||||
|
||||
- `forbid(unsafe_code)`, so "every guest access reaches linear memory only
|
||||
through wasmi's bounds-checked slice operations" is a property rather than a
|
||||
claim.
|
||||
- The truncating, wrapping and sign-losing cast lints are `deny` and each
|
||||
remaining cast is argued for at its site — a bad cast here changes what a
|
||||
contract is charged or told.
|
||||
|
||||
It pins `wasmi` with `default-features = false` deliberately. wasmi's `wat`
|
||||
feature is on by default and makes `Module::new` accept text as readily as
|
||||
binary, which would turn a transaction's validity into a build flag.
|
||||
|
||||
Not bridged to C++ directly; it reaches `xrpld` through `xrpl-wasm-vm-ffi`.
|
||||
|
||||
### `xrpl-wasm-vm-ffi`
|
||||
|
||||
The cxx bridge into `xrpld`. Three crossings:
|
||||
|
||||
- **In:** C++ calls `run_escrow`, once per escrow finish.
|
||||
- **Back out:** that run's host calls leave through the C++ `HostContext`, which
|
||||
`CxxHost` presents to the engine as an ordinary `HostFunctions` implementor.
|
||||
- **In only:** C++ screens a module with `check_escrow`. Screening needs no
|
||||
host, so nothing comes back out.
|
||||
|
||||
The C++ side is `src/libxrpl/tx/wasm/WasmVM.cpp` and
|
||||
`src/libxrpl/tx/wasm/HostContext.cpp`.
|
||||
|
||||
**Neither language may unwind into the other**, and the two halves are not
|
||||
symmetric:
|
||||
|
||||
- A **Rust panic** is caught here, by `guarded`. Letting one reach C++ is
|
||||
undefined behaviour, and `[profile.release]` enables overflow checks, so this
|
||||
is a live path rather than a formality.
|
||||
- A **C++ exception** is stopped on the C++ side: every `HostContext` method is
|
||||
`noexcept` and catches its own. That is what makes `guarded` sufficient.
|
||||
|
||||
Everything hand-written here is private, so `cargo doc` needs
|
||||
`--document-private-items` to show any of it. That is also why this crate,
|
||||
unlike `xrpl-wasm-vm`, does not `deny(unreachable_pub)` — cxx's expansion is
|
||||
`pub` throughout by necessity.
|
||||
|
||||
### `xrpl-wasm-testkit`
|
||||
|
||||
**Test-only.** Assembles WebAssembly text for the C++ test suite, and exposes
|
||||
the gas price of each host function by its guest import name for the C++ gas
|
||||
benchmarks (read through the bridge rather than transcribed into C++, so the
|
||||
numbers cannot drift silently).
|
||||
|
||||
A crate of its own rather than an entry on `xrpl-wasm-vm-ffi`, and the
|
||||
separation is the point: putting `compile_wat` on the production bridge would
|
||||
link `wat` into `xrpld` even if nothing called it. Linked only into
|
||||
`xrpl_tests`, never into `libxrpl` or `xrpld`, so "no text assembler in the
|
||||
shipped node" holds by the link graph rather than by a flag someone can flip.
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path crates/Cargo.toml --workspace
|
||||
```
|
||||
|
||||
CI uses `cargo nextest`. This is independent of the CMake build.
|
||||
|
||||
One gap that command does not cover: it never compiles `xrpl-host-functions`
|
||||
with its `wasmi_glue` feature **off**, because `xrpl-wasm-vm` enables the
|
||||
feature and Cargo unifies features across a workspace build. The feature-off
|
||||
configuration is the one a guest contract sees, so after touching that crate
|
||||
also run:
|
||||
|
||||
```bash
|
||||
cargo check -p xrpl-host-functions --manifest-path crates/Cargo.toml
|
||||
```
|
||||
10
crates/generated.clang-tidy
Normal file
10
crates/generated.clang-tidy
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
# Neutralizes clang-tidy for the corrosion/cxxbridge-generated C++. Copied into
|
||||
# the crates build directory by crates/CMakeLists.txt, next to the generated
|
||||
# sources, so clang-tidy picks it up instead of the top-level configuration.
|
||||
#
|
||||
# One check is kept enabled to avoid clang-tidy's "no checks enabled" error.
|
||||
Checks: "-*,google-readability-todo"
|
||||
WarningsAsErrors: ""
|
||||
HeaderFilterRegex: ""
|
||||
InheritParentConfig: false
|
||||
21
crates/xrpl-host-functions-macros/Cargo.toml
Normal file
21
crates/xrpl-host-functions-macros/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "xrpl-host-functions-macros"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "3", features = ["full"] }
|
||||
quote = "1"
|
||||
proc-macro2 = "1"
|
||||
|
||||
# The doctest declares host functions returning `HostResult`, which the facade
|
||||
# crate hand-writes. Cargo allows this cycle because dev-dependencies are outside
|
||||
# the library build graph.
|
||||
[dev-dependencies]
|
||||
xrpl-host-functions.path = "../xrpl-host-functions"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
33
crates/xrpl-host-functions-macros/src/errors.rs
Normal file
33
crates/xrpl-host-functions-macros/src/errors.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
/// Folds accumulated diagnostics into the single error a macro can return.
|
||||
///
|
||||
/// `syn::Error` is itself a collection: `combine` appends, and
|
||||
/// `into_compile_error` emits one `compile_error!` per recorded span. Folding
|
||||
/// instead of returning the first error means every mistake in a
|
||||
/// `host_functions!` block surfaces in one build rather than one per rebuild.
|
||||
pub(crate) fn combine(errors: Vec<syn::Error>) -> Option<syn::Error> {
|
||||
errors.into_iter().reduce(|mut first, next| {
|
||||
first.combine(next);
|
||||
first
|
||||
})
|
||||
}
|
||||
|
||||
/// `value`, or the folded diagnostics if any were recorded.
|
||||
pub(crate) fn into_result<T>(value: T, errors: Vec<syn::Error>) -> syn::Result<T> {
|
||||
match combine(errors) {
|
||||
Some(error) => Err(error),
|
||||
None => Ok(value),
|
||||
}
|
||||
}
|
||||
|
||||
/// `result`'s value, or `None` with its error filed in `errors` — so a check that
|
||||
/// yields a value can be reported like one that yields nothing, and the caller
|
||||
/// keeps going.
|
||||
pub(crate) fn record<T>(result: syn::Result<T>, errors: &mut Vec<syn::Error>) -> Option<T> {
|
||||
match result {
|
||||
Ok(value) => Some(value),
|
||||
Err(error) => {
|
||||
errors.push(error);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
472
crates/xrpl-host-functions-macros/src/glue.rs
Normal file
472
crates/xrpl-host-functions-macros/src/glue.rs
Normal file
@@ -0,0 +1,472 @@
|
||||
//! The wasmi registration, generated from the declarations the ABI table is
|
||||
//! generated from — so the closure a guest links against cannot disagree with the
|
||||
//! signature preflight screens it by.
|
||||
//!
|
||||
//! Emitted as a `macro_rules!` rather than as the registration itself, because the
|
||||
//! crate the expansion lands in is `no_std`, zero-dependency and links into the
|
||||
//! guest, and a `macro_rules!` body is inert tokens until someone expands it.
|
||||
//!
|
||||
//! The body therefore resolves in two crates at once and names nothing free:
|
||||
//! `$crate` is the ABI crate, `$env` the module the caller passes in, and every
|
||||
//! other path starts at `::wasmi` or `::core`. `$env` is matched as an `ident`
|
||||
//! because `$env:path` used as `$env::Foo` is `error: missing angle brackets in
|
||||
//! associated item path`.
|
||||
//!
|
||||
//! The one file that knows an engine's calling convention: how a region arrives as
|
||||
//! two wasm parameters, where the gas charge goes, and which helper a result-less
|
||||
//! function takes. A second engine would be a second file like it.
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{ToTokens, quote};
|
||||
|
||||
use crate::lowering::{ResultType, WasmValType};
|
||||
use crate::parsed_host_function::{Param, ParsedHostFunction};
|
||||
|
||||
/// The `wasmi_glue!` macro: the trait a VM implements one body per host function
|
||||
/// in, and the registration that hands each of them to a `Linker`.
|
||||
pub(crate) fn wasmi_glue(functions: &[ParsedHostFunction]) -> TokenStream {
|
||||
let bodies = functions.iter().map(body_declaration);
|
||||
let registrations = functions.iter().map(registration);
|
||||
let assertions = charging_assertions();
|
||||
let env = env();
|
||||
|
||||
quote! {
|
||||
/// Expands to the wasmi glue for this ABI: the `HostFunctionBodies` trait
|
||||
/// and `register_host_functions`, at the scope it is called in. A
|
||||
/// declaration added to the ABI is then a missing trait item rather than a
|
||||
/// forgotten registration.
|
||||
///
|
||||
/// `$env` names a module holding everything the expansion reaches for on
|
||||
/// the engine's side, since this crate can name none of it: the store type
|
||||
/// `VmState`, the charging helpers `charged` and `charged_unreported` with
|
||||
/// their `CallResult`, and the argument types `InBytes`, `InStr`, `InU32`,
|
||||
/// `OutBytes` and `TraceCode`.
|
||||
///
|
||||
/// ```ignore
|
||||
/// mod glue_env {
|
||||
/// pub(crate) use crate::abi::{CallResult, charged, charged_unreported};
|
||||
/// pub(crate) use crate::args::{InBytes, InStr, InU32, OutBytes, TraceCode};
|
||||
/// pub(crate) use crate::vm::VmState;
|
||||
/// }
|
||||
///
|
||||
/// xrpl_host_functions::wasmi_glue!(glue_env);
|
||||
/// ```
|
||||
///
|
||||
/// The module supplies the spellings; the shapes are pinned by the
|
||||
/// expansion. Each argument type implements [`FromWasmRegion`] or
|
||||
/// [`FromWasmScalar`] — which one is the ABI's decision, so a declared
|
||||
/// `u32` is a region — and each charging helper's signature is asserted
|
||||
/// against a `const _`.
|
||||
#[cfg(feature = "wasmi_glue")]
|
||||
#[macro_export]
|
||||
macro_rules! wasmi_glue {
|
||||
($env:ident) => {
|
||||
/// One body per host function: what the engine runs once the call's
|
||||
/// gas is charged and its arguments are off the wire.
|
||||
///
|
||||
/// The methods take no receiver, so a registered closure captures
|
||||
/// nothing — which is what satisfies wasmi's
|
||||
/// `Fn + Send + Sync + 'static` bound, and why the implementor
|
||||
/// itself need not be `'static`. A body does not charge gas; the
|
||||
/// generated closure does, so it cannot be forgotten or charged
|
||||
/// twice.
|
||||
pub(crate) trait HostFunctionBodies {
|
||||
#(#bodies)*
|
||||
}
|
||||
|
||||
/// Register every host function on `linker`, one `func_wrap` per
|
||||
/// declaration, at the ABI's derived wasm signature — so an import
|
||||
/// that passes [`crate::check`] links here by construction.
|
||||
pub(crate) fn register_host_functions<B: HostFunctionBodies>(
|
||||
linker: &mut ::wasmi::Linker<#env::VmState<'_>>,
|
||||
) -> ::core::result::Result<(), ::wasmi::errors::LinkerError> {
|
||||
#(#registrations)*
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#assertions
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The macro argument every engine-side path is qualified by.
|
||||
fn env() -> TokenStream {
|
||||
quote!($env)
|
||||
}
|
||||
|
||||
/// The signature of each charging helper, pinned as a `const _` the expansion
|
||||
/// carries: the one part of the contract neither `$env` nor the argument traits
|
||||
/// state.
|
||||
///
|
||||
/// Its value is the diagnostic. A changed helper is already a type error at the
|
||||
/// call, but there it is failed inference inside a generated closure and here it
|
||||
/// is one line stating the signature that was expected.
|
||||
fn charging_assertions() -> TokenStream {
|
||||
let env = env();
|
||||
let assertion = |helper: TokenStream, answer: TokenStream| {
|
||||
quote! {
|
||||
const _: fn(
|
||||
&mut ::wasmi::Caller<'_, #env::VmState<'_>>,
|
||||
$crate::HostFunctionSpec,
|
||||
fn(&mut ::wasmi::Caller<'_, #env::VmState<'_>>) -> #env::CallResult<#answer>,
|
||||
) -> ::core::result::Result<#answer, ::wasmi::Error> = #env::#helper;
|
||||
}
|
||||
};
|
||||
|
||||
let reported = assertion(quote!(charged), quote!(i32));
|
||||
let unreported = assertion(quote!(charged_unreported), quote!(()));
|
||||
|
||||
quote! {
|
||||
#reported
|
||||
#unreported
|
||||
}
|
||||
}
|
||||
|
||||
/// `fn check_keylet(caller: &mut Caller<'_, $env::VmState<'_>>, account:
|
||||
/// $env::InBytes, seq: $env::InU32, out: $env::OutBytes) ->
|
||||
/// $env::CallResult<i32>;`
|
||||
fn body_declaration(function: &ParsedHostFunction) -> TokenStream {
|
||||
let env = env();
|
||||
let name = &function.signature.ident;
|
||||
let params = function.params().iter().map(|param| {
|
||||
let name = ¶m.name;
|
||||
let ty = param.ty.argument_type(&env);
|
||||
quote! { #name: #ty }
|
||||
});
|
||||
let answer = answer_type(function.result());
|
||||
|
||||
quote! {
|
||||
fn #name(
|
||||
caller: &mut ::wasmi::Caller<'_, #env::VmState<'_>>,
|
||||
#(#params),*
|
||||
) -> #answer;
|
||||
}
|
||||
}
|
||||
|
||||
/// One `linker.func_wrap(…)?;`: the wasm signature as the closure's parameters,
|
||||
/// the gas charge around the call, and the body between them.
|
||||
fn registration(function: &ParsedHostFunction) -> TokenStream {
|
||||
let env = env();
|
||||
let body = &function.signature.ident;
|
||||
let spec = spec_path(function);
|
||||
let params = function.params().iter().flat_map(closure_params);
|
||||
let arguments = function.params().iter().map(lift);
|
||||
let (answer, charge) = match function.result() {
|
||||
ResultType::BufferLength | ResultType::Value => (quote!(i32), quote!(#env::charged)),
|
||||
ResultType::Nothing => (quote!(()), quote!(#env::charged_unreported)),
|
||||
};
|
||||
|
||||
quote! {
|
||||
linker.func_wrap(
|
||||
$crate::HOST_MODULE,
|
||||
#spec.wasm_name(),
|
||||
|mut caller: ::wasmi::Caller<'_, #env::VmState<'_>>, #(#params),*|
|
||||
-> ::core::result::Result<#answer, ::wasmi::Error> {
|
||||
#charge(&mut caller, #spec, |caller| {
|
||||
B::#body(caller, #(#arguments),*)
|
||||
})
|
||||
},
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
/// `$crate::HostFunctionSpec::CheckKeylet` — the one name the expansion reaches
|
||||
/// back into the ABI crate for.
|
||||
fn spec_path(function: &ParsedHostFunction) -> TokenStream {
|
||||
let variant = &function.variant;
|
||||
quote! { $crate::HostFunctionSpec::#variant }
|
||||
}
|
||||
|
||||
/// One declared parameter as the closure declares it: `account_ptr: i32,
|
||||
/// account_len: i32`, or `field: i32`.
|
||||
///
|
||||
/// Names and types both come from the lowering, so the arity a closure is
|
||||
/// registered at *is* the derived arity.
|
||||
fn closure_params(param: &Param) -> Vec<TokenStream> {
|
||||
param
|
||||
.ty
|
||||
.wasm_names(¶m.name)
|
||||
.into_iter()
|
||||
.zip(param.ty.as_wasm_params())
|
||||
.map(|(name, val_type)| {
|
||||
let ty = rust_type(*val_type);
|
||||
quote! { #name: #ty }
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The argument a body is handed, built from the wasm parameters it arrived as:
|
||||
/// `<$env::InBytes as $crate::FromWasmRegion>::from_wasm(account_ptr,
|
||||
/// account_len)`, or the scalar itself.
|
||||
///
|
||||
/// Qualified rather than an inherent call, so the arity comes from the trait the
|
||||
/// lowering chose: an argument type implementing the other one is an unsatisfied
|
||||
/// bound named at the type, where `Ty::from_wasm(a, b)` would be an unrelated
|
||||
/// arity error named here.
|
||||
fn lift(param: &Param) -> TokenStream {
|
||||
let Some(argument_trait) = param.ty.argument_trait() else {
|
||||
return param.name.to_token_stream();
|
||||
};
|
||||
let ty = param.ty.argument_type(&env());
|
||||
let names = param.ty.wasm_names(¶m.name);
|
||||
|
||||
quote! { <#ty as $crate::#argument_trait>::from_wasm(#(#names),*) }
|
||||
}
|
||||
|
||||
/// What a body answers: the value the guest is told, or nothing at all for the
|
||||
/// function whose whole effect is on the host.
|
||||
fn answer_type(result: ResultType) -> TokenStream {
|
||||
let env = env();
|
||||
match result {
|
||||
ResultType::BufferLength | ResultType::Value => quote!(#env::CallResult<i32>),
|
||||
ResultType::Nothing => quote!(#env::CallResult<()>),
|
||||
}
|
||||
}
|
||||
|
||||
/// A wasm value type as a closure parameter spells it — a Rust type, not
|
||||
/// [`WasmValType`]'s own `ToTokens`, which spells the ABI crate's variant.
|
||||
fn rust_type(val_type: WasmValType) -> TokenStream {
|
||||
match val_type {
|
||||
WasmValType::I32 => quote!(i32),
|
||||
WasmValType::I64 => quote!(i64),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg_attr(coverage_nightly, coverage(off))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use proc_macro2::{Delimiter, Group, TokenTree};
|
||||
use syn::parse_quote;
|
||||
|
||||
fn parsed(function: syn::TraitItemFn) -> ParsedHostFunction {
|
||||
ParsedHostFunction::parse(function).expect("the declaration should parse")
|
||||
}
|
||||
|
||||
/// The declaration whose declared and wasm parameter lists differ most:
|
||||
/// `account`, `out` and `seq` are a `(ptr, len)` pair each, so three arguments
|
||||
/// to the body and six on the wire.
|
||||
#[test]
|
||||
fn lowers_a_declaration_to_a_body_and_a_registration() {
|
||||
let keylet = parsed(parse_quote! {
|
||||
#[gas = 350]
|
||||
#[wasm_name = "check_id"]
|
||||
fn check_keylet(&self, account: &[u8], seq: u32, out: &mut [u8]) -> HostResult<usize>;
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
body_declaration(&keylet).to_string(),
|
||||
"fn check_keylet (caller : & mut :: wasmi :: Caller < '_ , $ env :: VmState < '_ >> , \
|
||||
account : $ env :: InBytes , seq : $ env :: InU32 , out : $ env :: OutBytes) \
|
||||
-> $ env :: CallResult < i32 > ;"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
registration(&keylet).to_string(),
|
||||
"linker . func_wrap ($ crate :: HOST_MODULE , \
|
||||
$ crate :: HostFunctionSpec :: CheckKeylet . wasm_name () , \
|
||||
| mut caller : :: wasmi :: Caller < '_ , $ env :: VmState < '_ >> , \
|
||||
account_ptr : i32 , account_len : i32 , seq_ptr : i32 , seq_len : i32 , \
|
||||
out_ptr : i32 , out_len : i32 | \
|
||||
-> :: core :: result :: Result < i32 , :: wasmi :: Error > \
|
||||
{ $ env :: charged (& mut caller , \
|
||||
$ crate :: HostFunctionSpec :: CheckKeylet , | caller | \
|
||||
{ B :: check_keylet (caller , \
|
||||
< $ env :: InBytes as $ crate :: FromWasmRegion > \
|
||||
:: from_wasm (account_ptr , account_len) , \
|
||||
< $ env :: InU32 as $ crate :: FromWasmRegion > :: from_wasm (seq_ptr , seq_len) , \
|
||||
< $ env :: OutBytes as $ crate :: FromWasmRegion > \
|
||||
:: from_wasm (out_ptr , out_len)) }) } ,) ? ;"
|
||||
);
|
||||
}
|
||||
|
||||
/// A wasm scalar is passed through as itself, in declaration order: no pair,
|
||||
/// no argument type, and an `i64` that stays one.
|
||||
#[test]
|
||||
fn passes_the_wasm_scalars_through_untouched() {
|
||||
let from_int = parsed(parse_quote! {
|
||||
#[gas = 100]
|
||||
#[wasm_name = "float_from_int"]
|
||||
fn float_from_int(&self, x: i64, out: &mut [u8], mode: i32) -> HostResult<usize>;
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
body_declaration(&from_int).to_string(),
|
||||
"fn float_from_int (caller : & mut :: wasmi :: Caller < '_ , \
|
||||
$ env :: VmState < '_ >> , \
|
||||
x : i64 , out : $ env :: OutBytes , mode : i32) -> $ env :: CallResult < i32 > ;"
|
||||
);
|
||||
|
||||
let registration = registration(&from_int).to_string();
|
||||
assert!(
|
||||
registration.contains(
|
||||
"| mut caller : :: wasmi :: Caller < '_ , $ env :: VmState < '_ >> , \
|
||||
x : i64 , out_ptr : i32 , out_len : i32 , mode : i32 |"
|
||||
),
|
||||
"{registration}"
|
||||
);
|
||||
assert!(
|
||||
registration.contains(
|
||||
"B :: float_from_int (caller , x , \
|
||||
< $ env :: OutBytes as $ crate :: FromWasmRegion > \
|
||||
:: from_wasm (out_ptr , out_len) , mode)"
|
||||
),
|
||||
"{registration}"
|
||||
);
|
||||
}
|
||||
|
||||
/// The function that answers nothing takes the other charging helper, derived
|
||||
/// from its declared `HostResult<()>` rather than named as a special case.
|
||||
/// Its `TraceCode` is also the only place `FromWasmScalar` is reached for.
|
||||
#[test]
|
||||
fn a_declaration_that_answers_nothing_takes_the_other_charge() {
|
||||
let trace = parsed(trace_declaration());
|
||||
|
||||
assert_eq!(
|
||||
body_declaration(&trace).to_string(),
|
||||
"fn trace (caller : & mut :: wasmi :: Caller < '_ , $ env :: VmState < '_ >> , \
|
||||
msg : $ env :: InStr , data_type : $ env :: TraceCode , data : $ env :: InBytes) \
|
||||
-> $ env :: CallResult < () > ;"
|
||||
);
|
||||
|
||||
let registration = registration(&trace).to_string();
|
||||
assert!(
|
||||
registration.contains(":: core :: result :: Result < () , :: wasmi :: Error >"),
|
||||
"{registration}"
|
||||
);
|
||||
assert!(
|
||||
registration.contains("$ env :: charged_unreported (& mut caller"),
|
||||
"{registration}"
|
||||
);
|
||||
assert!(
|
||||
registration.contains(
|
||||
"B :: trace (caller , \
|
||||
< $ env :: InStr as $ crate :: FromWasmRegion > :: from_wasm (msg_ptr , msg_len) , \
|
||||
< $ env :: TraceCode as $ crate :: FromWasmScalar > :: from_wasm (data_type) , \
|
||||
< $ env :: InBytes as $ crate :: FromWasmRegion > \
|
||||
:: from_wasm (data_ptr , data_len))"
|
||||
),
|
||||
"{registration}"
|
||||
);
|
||||
}
|
||||
|
||||
/// The two worlds the macro body resolves in: the ABI crate through `$crate`,
|
||||
/// and one engine by name. `names_no_crate_of_its_own` holds the ABI half of
|
||||
/// the expansion to naming neither.
|
||||
#[test]
|
||||
fn reaches_the_abi_crate_through_dollar_crate_and_the_engine_by_name() {
|
||||
let glue = code(wasmi_glue(&[parsed(parse_quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self, out: &mut [u8]) -> HostResult<usize>;
|
||||
})]));
|
||||
|
||||
assert!(
|
||||
glue.contains("$ crate :: HostFunctionSpec :: GetLedgerSqn"),
|
||||
"{glue}"
|
||||
);
|
||||
assert!(glue.contains("$ crate :: HOST_MODULE"), "{glue}");
|
||||
assert!(glue.contains(":: wasmi :: Linker"), "{glue}");
|
||||
assert!(!glue.contains("xrpl_host_functions"), "{glue}");
|
||||
}
|
||||
|
||||
/// Every engine-side item is reached through the module the macro is handed: a
|
||||
/// bare name would resolve against whatever the call site has in scope.
|
||||
///
|
||||
/// `charged` covers `charged_unreported`, being its prefix.
|
||||
#[test]
|
||||
fn names_the_engine_s_own_items_only_through_the_module_it_is_handed() {
|
||||
let glue = code(wasmi_glue(&[
|
||||
parsed(trace_declaration()),
|
||||
parsed(parse_quote! {
|
||||
#[gas = 350]
|
||||
#[wasm_name = "check_id"]
|
||||
fn check_keylet(&self, account: &[u8], seq: u32, out: &mut [u8])
|
||||
-> HostResult<usize>;
|
||||
}),
|
||||
]));
|
||||
|
||||
for item in [
|
||||
"VmState",
|
||||
"CallResult",
|
||||
"charged",
|
||||
"InBytes",
|
||||
"InStr",
|
||||
"InU32",
|
||||
"OutBytes",
|
||||
"TraceCode",
|
||||
] {
|
||||
for (index, _) in glue.match_indices(item) {
|
||||
assert!(
|
||||
glue[..index].ends_with("$ env :: "),
|
||||
"`{item}` named outside `$env`: {glue}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The charging helpers' signatures, which nothing else in the contract
|
||||
/// states.
|
||||
#[test]
|
||||
fn pins_both_charging_helpers_signatures() {
|
||||
assert_eq!(
|
||||
charging_assertions().to_string(),
|
||||
"const _ : fn (& mut :: wasmi :: Caller < '_ , $ env :: VmState < '_ >> , \
|
||||
$ crate :: HostFunctionSpec , \
|
||||
fn (& mut :: wasmi :: Caller < '_ , $ env :: VmState < '_ >>) \
|
||||
-> $ env :: CallResult < i32 > ,) \
|
||||
-> :: core :: result :: Result < i32 , :: wasmi :: Error > = $ env :: charged ; \
|
||||
const _ : fn (& mut :: wasmi :: Caller < '_ , $ env :: VmState < '_ >> , \
|
||||
$ crate :: HostFunctionSpec , \
|
||||
fn (& mut :: wasmi :: Caller < '_ , $ env :: VmState < '_ >>) \
|
||||
-> $ env :: CallResult < () > ,) \
|
||||
-> :: core :: result :: Result < () , :: wasmi :: Error > \
|
||||
= $ env :: charged_unreported ;"
|
||||
);
|
||||
}
|
||||
|
||||
fn trace_declaration() -> syn::TraitItemFn {
|
||||
parse_quote! {
|
||||
#[gas = 30]
|
||||
#[wasm_name = "trace"]
|
||||
fn trace(&self, msg: &str, data_type: TraceDataType, data: &[u8]) -> HostResult<()>;
|
||||
}
|
||||
}
|
||||
|
||||
/// The expansion's code alone. `to_string` renders a doc comment as a
|
||||
/// `#[doc = "…"]` literal, and the macro's own documentation names the very
|
||||
/// items the scans above look for.
|
||||
fn code(tokens: TokenStream) -> String {
|
||||
fn is_doc(tree: Option<&TokenTree>) -> bool {
|
||||
let Some(TokenTree::Group(group)) = tree else {
|
||||
return false;
|
||||
};
|
||||
group.delimiter() == Delimiter::Bracket
|
||||
&& matches!(group.stream().into_iter().next(),
|
||||
Some(TokenTree::Ident(ident)) if ident == "doc")
|
||||
}
|
||||
|
||||
fn strip(tokens: TokenStream) -> TokenStream {
|
||||
let mut trees = tokens.into_iter().peekable();
|
||||
let mut kept = Vec::new();
|
||||
while let Some(tree) = trees.next() {
|
||||
match tree {
|
||||
TokenTree::Punct(ref punct)
|
||||
if punct.as_char() == '#' && is_doc(trees.peek()) =>
|
||||
{
|
||||
trees.next();
|
||||
}
|
||||
TokenTree::Group(group) => kept.push(TokenTree::Group(Group::new(
|
||||
group.delimiter(),
|
||||
strip(group.stream()),
|
||||
))),
|
||||
other => kept.push(other),
|
||||
}
|
||||
}
|
||||
kept.into_iter().collect()
|
||||
}
|
||||
|
||||
strip(tokens).to_string()
|
||||
}
|
||||
}
|
||||
496
crates/xrpl-host-functions-macros/src/lib.rs
Normal file
496
crates/xrpl-host-functions-macros/src/lib.rs
Normal file
@@ -0,0 +1,496 @@
|
||||
#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
|
||||
|
||||
mod errors;
|
||||
mod glue;
|
||||
mod lowering;
|
||||
mod parsed_host_function;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
TraitItemFn,
|
||||
parse::{Parse, ParseStream},
|
||||
parse2,
|
||||
};
|
||||
|
||||
use parsed_host_function::ParsedHostFunction;
|
||||
|
||||
/// Declares the wasm host ABI once, and generates everything that follows from it.
|
||||
///
|
||||
/// The input is a block of `fn` declarations, each carrying the gas cost the host
|
||||
/// charges before the call and the name the guest imports it under. Doc comments
|
||||
/// are kept and appear on the generated items.
|
||||
///
|
||||
/// This crate is an implementation detail of `xrpl-host-functions`, which
|
||||
/// hand-writes the types the declarations refer to and holds the one declaration
|
||||
/// block.
|
||||
///
|
||||
/// # What it generates
|
||||
///
|
||||
/// Four items, in the scope the block is written in:
|
||||
///
|
||||
/// - `pub trait HostFunctions`: one method per declaration, emitted verbatim —
|
||||
/// receiver, parameters, return type and doc comment exactly as written. An
|
||||
/// execution environment implements it; the rest of the expansion does not
|
||||
/// mention it.
|
||||
/// - `pub enum HostFunctionSpec`: one variant per declaration, named by
|
||||
/// PascalCasing the function name (`get_ledger_sqn` becomes `GetLedgerSqn`) and
|
||||
/// carrying that declaration's doc comment. Its `const fn wasm_name`, `gas`,
|
||||
/// `wasm_params` and `wasm_result` are the ABI metadata, and `ALL` is every
|
||||
/// variant in declaration order — what a wasm engine iterates to build its
|
||||
/// import table.
|
||||
/// - `struct HostFnSpec`: private, one row of that metadata table. It exists only
|
||||
/// so those accessors read from a single `match` over the declarations, and
|
||||
/// never appears in a signature a caller can name.
|
||||
/// - `macro_rules! wasmi_glue`: the registration for a wasmi engine, emitted as a
|
||||
/// macro rather than as code because it names an engine this crate must not
|
||||
/// depend on. Inert until expanded — see its own documentation.
|
||||
///
|
||||
/// The wasm signature is derived from the declared types rather than stated a
|
||||
/// second time: `i32` and `i64` are the wasm scalars spelled as themselves, every
|
||||
/// other parameter type is marshalled through a `(ptr, len)` pair or an `i32`
|
||||
/// code, and the result comes from the `HostResult<T>` success type. The glue is
|
||||
/// generated from that same derivation, so the closure a guest links against and
|
||||
/// the signature it is screened by are one statement.
|
||||
///
|
||||
/// Outside the glue's body the expansion builds only `Self::Variant` and
|
||||
/// `WasmValType` paths, so the block compiles wherever the types it names —
|
||||
/// `HostResult` and `WasmValType` — resolve.
|
||||
///
|
||||
/// ```
|
||||
/// use xrpl_host_functions::{HostResult, WasmValType};
|
||||
/// use xrpl_host_functions_macros::host_functions;
|
||||
///
|
||||
/// host_functions! {
|
||||
/// /// The sequence number of the ledger being built, as 4 little-endian bytes.
|
||||
/// #[gas = 60]
|
||||
/// #[wasm_name = "ldgr_index"]
|
||||
/// fn get_ledger_sqn(&self, out: &mut [u8]) -> HostResult<usize>;
|
||||
///
|
||||
/// /// Writes `msg` to the trace log.
|
||||
/// #[gas = 500]
|
||||
/// #[wasm_name = "trace_num"]
|
||||
/// fn trace_num(&self, msg: &str, number: i64) -> HostResult<()>;
|
||||
/// }
|
||||
///
|
||||
/// // The trait's methods are the declarations, down to the `&self` receiver the
|
||||
/// // VM calls the host through.
|
||||
/// fn ledger_sqn(host: &dyn HostFunctions, out: &mut [u8]) -> HostResult<usize> {
|
||||
/// host.get_ledger_sqn(out)
|
||||
/// }
|
||||
///
|
||||
/// // The metadata is a `const` table, so gas and import names are available at
|
||||
/// // compile time rather than looked up at run time.
|
||||
/// const TRACE_GAS: u64 = HostFunctionSpec::TraceNum.gas();
|
||||
/// assert_eq!(TRACE_GAS, 500);
|
||||
///
|
||||
/// assert_eq!(HostFunctionSpec::GetLedgerSqn.wasm_name(), "ldgr_index");
|
||||
/// assert_eq!(
|
||||
/// HostFunctionSpec::ALL,
|
||||
/// &[HostFunctionSpec::GetLedgerSqn, HostFunctionSpec::TraceNum],
|
||||
/// );
|
||||
///
|
||||
/// // So is the wasm signature: `out: &mut [u8]` is the pair `(ptr, len)`, and
|
||||
/// // `HostResult<usize>` answers the length written to it.
|
||||
/// assert_eq!(
|
||||
/// HostFunctionSpec::GetLedgerSqn.wasm_params(),
|
||||
/// &[WasmValType::I32, WasmValType::I32],
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// HostFunctionSpec::GetLedgerSqn.wasm_result(),
|
||||
/// Some(WasmValType::I32),
|
||||
/// );
|
||||
///
|
||||
/// // `trace_num` answers nothing at all, so its import has no result.
|
||||
/// assert_eq!(
|
||||
/// HostFunctionSpec::TraceNum.wasm_params(),
|
||||
/// &[WasmValType::I32, WasmValType::I32, WasmValType::I64],
|
||||
/// );
|
||||
/// assert_eq!(HostFunctionSpec::TraceNum.wasm_result(), None);
|
||||
/// ```
|
||||
///
|
||||
/// A declaration must be a plain `fn` taking `&self`, with no body and no
|
||||
/// generics: it maps to exactly one wasm import signature. Its parameters must be
|
||||
/// `i32`, `i64`, `u32`, `&[u8]`, `&mut [u8]`, `&str` or `TraceDataType`, and it
|
||||
/// must return `HostResult<usize>` if it writes an output region,
|
||||
/// `HostResult<i32>` if it answers a value directly, or `HostResult<()>` if it
|
||||
/// answers nothing. Two declarations may not share a `wasm_name`, nor collapse to
|
||||
/// the same PascalCase variant.
|
||||
#[proc_macro]
|
||||
pub fn host_functions(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
expand(input.into())
|
||||
.unwrap_or_else(syn::Error::into_compile_error)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||
let functions = parse_block(input)?;
|
||||
let abi = abi_items(&functions);
|
||||
let glue = glue::wasmi_glue(&functions);
|
||||
|
||||
Ok(quote! {
|
||||
#abi
|
||||
#glue
|
||||
})
|
||||
}
|
||||
|
||||
/// Every declaration in the block, parsed and checked against each other, or
|
||||
/// every mistake in it.
|
||||
fn parse_block(input: TokenStream) -> syn::Result<Vec<ParsedHostFunction>> {
|
||||
let HostFunctionsInput { functions } = parse2(input)?;
|
||||
|
||||
let mut parsed = Vec::with_capacity(functions.len());
|
||||
let mut errors = Vec::new();
|
||||
for function in functions {
|
||||
match ParsedHostFunction::parse(function) {
|
||||
Ok(function) => parsed.push(function),
|
||||
Err(error) => errors.push(error),
|
||||
}
|
||||
}
|
||||
if let Some(error) = errors::combine(errors) {
|
||||
return Err(error);
|
||||
}
|
||||
if let Some(error) = errors::combine(collisions(&parsed)) {
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
/// Names two declarations may not share, because the generated code would then
|
||||
/// fail to compile at a span the caller cannot see.
|
||||
fn collisions(functions: &[ParsedHostFunction]) -> Vec<syn::Error> {
|
||||
let mut errors = Vec::new();
|
||||
let mut variants = HashSet::new();
|
||||
let mut wasm_names = HashSet::new();
|
||||
|
||||
for function in functions {
|
||||
if !variants.insert(function.variant.to_string()) {
|
||||
errors.push(syn::Error::new_spanned(
|
||||
&function.variant,
|
||||
format!(
|
||||
"another host function already becomes the `{}` variant",
|
||||
function.variant
|
||||
),
|
||||
));
|
||||
}
|
||||
if !wasm_names.insert(function.wasm_name.value()) {
|
||||
errors.push(syn::Error::new_spanned(
|
||||
&function.wasm_name,
|
||||
format!(
|
||||
"another host function is already imported as `{}`",
|
||||
function.wasm_name.value()
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
errors
|
||||
}
|
||||
|
||||
/// The ABI itself: the trait a host implements and the table everything else
|
||||
/// reads. `glue::wasmi_glue` is the other half of the expansion.
|
||||
fn abi_items(functions: &[ParsedHostFunction]) -> TokenStream {
|
||||
let trait_methods = functions.iter().map(ParsedHostFunction::trait_method);
|
||||
let variants = functions
|
||||
.iter()
|
||||
.map(ParsedHostFunction::variant_declaration);
|
||||
let spec_arms = functions.iter().map(ParsedHostFunction::spec_arm);
|
||||
let all = functions.iter().map(|function| &function.variant);
|
||||
|
||||
quote! {
|
||||
/// The host side of the wasm ABI: one method per function a guest may
|
||||
/// import.
|
||||
///
|
||||
/// Implement it once per execution environment — the ledger host, a test
|
||||
/// double, a benchmark fake — and a guest module cannot tell them apart.
|
||||
/// Each method is one declaration from the `host_functions!` block, as
|
||||
/// written; its `&self` receiver is not part of the ABI the guest sees,
|
||||
/// so a host that must mutate does so behind interior mutability.
|
||||
///
|
||||
/// # The output contract
|
||||
///
|
||||
/// A method handed an `out` buffer **writes into it only when the whole
|
||||
/// value fits, and returns the value's true length whether it fitted or
|
||||
/// not.**
|
||||
///
|
||||
/// The length is the value's, not the number of bytes written, because it
|
||||
/// is how a guest that asked with too small a buffer learns the size to
|
||||
/// ask for next time. The engine turns a length past the buffer into
|
||||
/// `BufferTooSmall`, and one past the field cap into `DataFieldTooLarge`,
|
||||
/// so a host needs to know neither.
|
||||
///
|
||||
/// Writing nothing unless the value fits is the half only a host can hold
|
||||
/// up. An engine can bound how many bytes are *writable* — and does, by
|
||||
/// handing over a region clamped to the field cap — but it cannot take
|
||||
/// back what a method already put there. A host that wrote a truncated
|
||||
/// prefix and then reported the larger length would leave those bytes in
|
||||
/// guest memory behind a refusal the guest is told to ignore.
|
||||
pub trait HostFunctions {
|
||||
#(#trait_methods)*
|
||||
}
|
||||
|
||||
/// One row of the ABI table: what [`HostFunctionSpec`]'s accessors
|
||||
/// read from.
|
||||
///
|
||||
/// Private, and the only reason it exists is to keep all of them fed
|
||||
/// from a single `match` over the declarations.
|
||||
struct HostFnSpec {
|
||||
name: &'static str,
|
||||
gas: u64,
|
||||
wasm_params: &'static [WasmValType],
|
||||
wasm_result: Option<WasmValType>,
|
||||
}
|
||||
|
||||
/// Identifies one host function, and is the compile-time source of its
|
||||
/// ABI metadata.
|
||||
///
|
||||
/// One variant per `host_functions!` declaration, named by converting the
|
||||
/// function name to PascalCase. [`Self::ALL`] is the whole ABI, which is
|
||||
/// what a wasm engine iterates to build its import table.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum HostFunctionSpec {
|
||||
#(#variants,)*
|
||||
}
|
||||
|
||||
impl HostFunctionSpec {
|
||||
/// Every host function, in the order declared.
|
||||
///
|
||||
/// This is the complete import surface a guest may link against: a
|
||||
/// function absent here cannot be called, and one present here must
|
||||
/// be registered for a module that imports it to instantiate.
|
||||
pub const ALL: &'static [Self] = &[#(Self::#all,)*];
|
||||
|
||||
/// This function's row of the ABI table.
|
||||
const fn spec(self) -> HostFnSpec {
|
||||
match self {
|
||||
#(#spec_arms,)*
|
||||
}
|
||||
}
|
||||
|
||||
/// The name a guest imports this function under.
|
||||
///
|
||||
/// A guest's import name must match this exactly, or the module
|
||||
/// fails to instantiate. Usable in `const` context, so import lists
|
||||
/// can be built at compile time.
|
||||
pub const fn wasm_name(self) -> &'static str {
|
||||
self.spec().name
|
||||
}
|
||||
|
||||
/// Gas charged before the call runs, independent of its arguments.
|
||||
///
|
||||
/// Consensus-relevant: two nodes that disagree on this value
|
||||
/// disagree on transaction outcomes. Usable in `const` context, so
|
||||
/// gas tables can be built at compile time.
|
||||
pub const fn gas(self) -> u64 {
|
||||
self.spec().gas
|
||||
}
|
||||
|
||||
/// The wasm parameters this function is imported with, in wire
|
||||
/// order — the list a guest's import must match, which the
|
||||
/// declaration's own parameter list is not: a declared parameter
|
||||
/// marshalled through a `(ptr, len)` region is two of these.
|
||||
///
|
||||
/// Usable in `const` context, so import lists can be built at
|
||||
/// compile time.
|
||||
pub const fn wasm_params(self) -> &'static [WasmValType] {
|
||||
self.spec().wasm_params
|
||||
}
|
||||
|
||||
/// The wasm result this function answers with, or `None` for the
|
||||
/// one whose whole effect is on the host. An `i32` where there is
|
||||
/// one, whether the host answered a value or the length of what it
|
||||
/// wrote — the wire does not distinguish those.
|
||||
pub const fn wasm_result(self) -> Option<WasmValType> {
|
||||
self.spec().wasm_result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct HostFunctionsInput {
|
||||
functions: Vec<TraitItemFn>,
|
||||
}
|
||||
|
||||
impl Parse for HostFunctionsInput {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let mut functions = Vec::new();
|
||||
while !input.is_empty() {
|
||||
functions.push(input.parse()?);
|
||||
}
|
||||
Ok(HostFunctionsInput { functions })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg_attr(coverage_nightly, coverage(off))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn accepts_an_empty_block() {
|
||||
expand(quote! {}).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reports_mistakes_from_every_function() {
|
||||
let error = expand(quote! {
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
#[gas = 2000]
|
||||
fn sha512_half(&self, data: &[u8], out: &mut [u8]) -> HostResult<usize>;
|
||||
})
|
||||
.expect_err("expected parsing to fail");
|
||||
|
||||
let messages: Vec<_> = error.into_iter().map(|error| error.to_string()).collect();
|
||||
assert_eq!(messages.len(), 2, "{messages:?}");
|
||||
assert!(messages[0].contains("missing `#[gas"), "{messages:?}");
|
||||
assert!(messages[1].contains("missing `#[wasm_name"), "{messages:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn propagates_syntax_errors() {
|
||||
let error = expand(quote! { fn missing_semicolon() }).expect_err("expected a syntax error");
|
||||
assert!(!error.to_string().is_empty());
|
||||
}
|
||||
|
||||
/// The messages of every diagnostic recorded by one failed `expand`.
|
||||
fn messages(input: TokenStream) -> Vec<String> {
|
||||
let Err(error) = expand(input) else {
|
||||
panic!("expected expansion to fail");
|
||||
};
|
||||
error.into_iter().map(|error| error.to_string()).collect()
|
||||
}
|
||||
|
||||
/// The ABI half of the expansion alone: the glue's body is written against
|
||||
/// another crate entirely, so the tests below about what the expansion may
|
||||
/// name are not about it.
|
||||
fn abi_expansion(input: TokenStream) -> String {
|
||||
abi_items(&parse_block(input).expect("the block should parse")).to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generates_the_trait_the_enum_and_the_table() {
|
||||
let generated = expand(quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
#[gas = 500]
|
||||
#[wasm_name = "trace_num"]
|
||||
fn trace_num(&self, msg: &str, number: i64) -> HostResult<()>;
|
||||
})
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
for expected in [
|
||||
"pub trait HostFunctions",
|
||||
"fn get_ledger_sqn (& self , out : & mut [u8]) -> HostResult < usize > ;",
|
||||
"fn trace_num (& self , msg : & str , number : i64) -> HostResult < () > ;",
|
||||
"pub enum HostFunctionSpec { GetLedgerSqn , TraceNum , }",
|
||||
"pub const ALL : & 'static [Self] = & [Self :: GetLedgerSqn , Self :: TraceNum ,]",
|
||||
// The table's row type is generated too, and stays private.
|
||||
"struct HostFnSpec { name : & 'static str , gas : u64 , \
|
||||
wasm_params : & 'static [WasmValType] , wasm_result : Option < WasmValType > , }",
|
||||
"const fn spec (self) -> HostFnSpec",
|
||||
// Two wasm parameters for the one declared region, and a result for
|
||||
// the length written to it.
|
||||
"Self :: GetLedgerSqn => HostFnSpec { name : \"ldgr_index\" , gas : 60u64 , \
|
||||
wasm_params : & [WasmValType :: I32 , WasmValType :: I32] , \
|
||||
wasm_result : Some (WasmValType :: I32) , }",
|
||||
"Self :: TraceNum => HostFnSpec { name : \"trace_num\" , gas : 500u64 , \
|
||||
wasm_params : & [WasmValType :: I32 , WasmValType :: I32 , WasmValType :: I64] , \
|
||||
wasm_result : None , }",
|
||||
"pub const fn wasm_name (self) -> & 'static str",
|
||||
"pub const fn gas (self) -> u64",
|
||||
"pub const fn wasm_params (self) -> & 'static [WasmValType]",
|
||||
"pub const fn wasm_result (self) -> Option < WasmValType >",
|
||||
// The fourth item; its contents are `glue`'s own tests.
|
||||
"macro_rules ! wasmi_glue",
|
||||
] {
|
||||
assert!(generated.contains(expected), "missing {expected:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// The ABI reaches for nothing outside the crate it lands in, which is what
|
||||
/// lets that crate stay zero-dependency and link into the guest. The glue is
|
||||
/// not held to this — its body names one engine throughout, and `glue`'s own
|
||||
/// tests pin that instead.
|
||||
#[test]
|
||||
fn names_no_crate_of_its_own() {
|
||||
let generated = abi_expansion(quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self, out: &mut [u8]) -> HostResult<usize>;
|
||||
});
|
||||
|
||||
assert!(!generated.contains("xrpl_host_functions"), "{generated}");
|
||||
|
||||
// Two roots and no others: `Self::Variant` and `WasmValType::I32`.
|
||||
// Doc comments spell paths without spaces (`Self::ALL`), so they do not
|
||||
// match.
|
||||
for (index, _) in generated.match_indices(" :: ") {
|
||||
let prefix = &generated[..index];
|
||||
assert!(
|
||||
prefix.ends_with("Self") || prefix.ends_with("WasmValType"),
|
||||
"path out of the expansion at {index}: {generated}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// `spec` is an implementation detail of the two accessors, so it must not
|
||||
/// become part of the ABI crate's public surface.
|
||||
#[test]
|
||||
fn keeps_the_table_row_private() {
|
||||
let generated = abi_expansion(quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self, out: &mut [u8]) -> HostResult<usize>;
|
||||
});
|
||||
|
||||
assert!(!generated.contains("pub struct HostFnSpec"), "{generated}");
|
||||
assert!(!generated.contains("pub const fn spec"), "{generated}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_two_functions_that_share_a_wasm_name() {
|
||||
let messages = messages(quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "trace"]
|
||||
fn trace(&self, msg: &str) -> HostResult<()>;
|
||||
|
||||
#[gas = 70]
|
||||
#[wasm_name = "trace"]
|
||||
fn trace_num(&self, msg: &str, number: i64) -> HostResult<()>;
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 1, "{messages:?}");
|
||||
assert!(
|
||||
messages[0].contains("already imported as `trace`"),
|
||||
"{messages:?}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Names that differ only in underscores collapse to one enum variant.
|
||||
#[test]
|
||||
fn rejects_two_functions_that_share_a_variant() {
|
||||
let messages = messages(quote! {
|
||||
#[gas = 60]
|
||||
#[wasm_name = "a"]
|
||||
fn get_ledger_sqn(&self) -> HostResult<i32>;
|
||||
|
||||
#[gas = 70]
|
||||
#[wasm_name = "b"]
|
||||
fn get_ledger__sqn(&self) -> HostResult<i32>;
|
||||
});
|
||||
|
||||
assert_eq!(messages.len(), 1, "{messages:?}");
|
||||
assert!(
|
||||
messages[0].contains("`GetLedgerSqn` variant"),
|
||||
"{messages:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
488
crates/xrpl-host-functions-macros/src/lowering.rs
Normal file
488
crates/xrpl-host-functions-macros/src/lowering.rs
Normal file
@@ -0,0 +1,488 @@
|
||||
//! The wire shape of a declaration: everything a declared Rust type decides — the
|
||||
//! wasm value types it lowers to, the names those wasm parameters take, the type a
|
||||
//! generated body is handed for it, and which of the ABI's two argument traits
|
||||
//! builds that type. Kept as one set of `match` arms because the four must agree.
|
||||
//!
|
||||
//! The whole mapping, and the only place it is written down: a type no arm here
|
||||
//! names is a type the ABI does not have, not one that falls back to something.
|
||||
//!
|
||||
//! Two rows are worth knowing before reading a declaration:
|
||||
//!
|
||||
//! - **`u32` is not a scalar.** It is a `(ptr, len)` region holding four
|
||||
//! little-endian bytes, which is how the guest SDK passes a sequence number.
|
||||
//! - **`usize` and `i32` results are the same on the wire and not
|
||||
//! interchangeable**: the first is the length of what was written to an output
|
||||
//! region, the second the answer itself.
|
||||
//!
|
||||
//! Matching is on types as they are spelled — a proc macro resolves nothing, so
|
||||
//! `type Bytes = u32; … x: Bytes` is unrecognizable — but on a path's last
|
||||
//! segment, so any of these types may be spelled qualified.
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{ToTokens, format_ident, quote};
|
||||
use syn::{Ident, PathArguments, Type, TypePath, TypeReference};
|
||||
|
||||
/// What a host function may be handed, and what each costs on the wire.
|
||||
///
|
||||
/// Declaration order is wasm parameter order, so a reader of a declaration is
|
||||
/// reading the import the guest links against.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum ParamType {
|
||||
/// `i32`, passed through as itself. Also the spelling for a raw scalar
|
||||
/// whose signedness the ABI does not fix.
|
||||
I32,
|
||||
/// `i64`, passed through as itself.
|
||||
I64,
|
||||
/// `TraceDataType`: an `i32` code the engine resolves to the enum before a
|
||||
/// host sees it.
|
||||
TraceDataType,
|
||||
/// `&[u8]`: a borrowed input region.
|
||||
InBytes,
|
||||
/// `&str`: an input region whose read is also the UTF-8 check.
|
||||
InStr,
|
||||
/// `u32`: an input region holding four little-endian bytes.
|
||||
InU32,
|
||||
/// `&mut [u8]`: the writable output region.
|
||||
OutBytes,
|
||||
}
|
||||
|
||||
/// The success type of the `HostResult<T>` every declaration returns. These three
|
||||
/// are what the ABI has; any other `T` is an error.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum ResultType {
|
||||
/// `usize`: the true length of a value written to an output region, which
|
||||
/// the engine turns into the wire's `i32` or into `BufferTooSmall` /
|
||||
/// `DataFieldTooLarge`. Never itself the wire type.
|
||||
BufferLength,
|
||||
/// `i32`: the answer, from a function that writes no region.
|
||||
Value,
|
||||
/// `()`: no wasm result at all — the call's whole effect is on the host, and
|
||||
/// an `Err` reaches the guest in no form.
|
||||
Nothing,
|
||||
}
|
||||
|
||||
/// The wasm value types this ABI uses, mirroring `xrpl_host_functions::WasmValType`.
|
||||
///
|
||||
/// Mirrored rather than shared because the dependency runs the other way: the ABI
|
||||
/// crate depends on this one, so nothing here can name its types. The [`ToTokens`]
|
||||
/// impl below is the whole of the crossing, and emits references to that enum's
|
||||
/// variants — so falling out of sync with it is a compile error at the
|
||||
/// `host_functions!` call site rather than drift.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum WasmValType {
|
||||
I32,
|
||||
I64,
|
||||
}
|
||||
|
||||
impl ParamType {
|
||||
/// Recognizes the declared type, or refuses it against its own span.
|
||||
pub(crate) fn parse(ty: &Type) -> syn::Result<Self> {
|
||||
const ALLOWED: &str = "a host function's parameter must be `i32`, `i64`, `u32`, \
|
||||
`&[u8]`, `&mut [u8]`, `&str` or `TraceDataType`";
|
||||
|
||||
let recognized = match ty {
|
||||
// A lifetime on the reference changes nothing on the wire.
|
||||
Type::Reference(TypeReference {
|
||||
mutability, elem, ..
|
||||
}) => match (mutability, &**elem) {
|
||||
(None, Type::Slice(slice)) if is_named(&slice.elem, "u8") => Some(Self::InBytes),
|
||||
(Some(_), Type::Slice(slice)) if is_named(&slice.elem, "u8") => {
|
||||
Some(Self::OutBytes)
|
||||
}
|
||||
(None, elem) if is_named(elem, "str") => Some(Self::InStr),
|
||||
_ => None,
|
||||
},
|
||||
_ => match last_path_segment(ty) {
|
||||
Some(name) if name == "i32" => Some(Self::I32),
|
||||
Some(name) if name == "i64" => Some(Self::I64),
|
||||
Some(name) if name == "u32" => Some(Self::InU32),
|
||||
Some(name) if name == "TraceDataType" => Some(Self::TraceDataType),
|
||||
_ => None,
|
||||
},
|
||||
};
|
||||
|
||||
recognized.ok_or_else(|| syn::Error::new_spanned(ty, ALLOWED))
|
||||
}
|
||||
|
||||
/// The wasm parameters this declared type lowers to, in order. `InBytes` and
|
||||
/// `OutBytes` lower alike, so a region's direction survives only in the
|
||||
/// variant.
|
||||
pub(crate) fn as_wasm_params(self) -> &'static [WasmValType] {
|
||||
match self {
|
||||
Self::I32 | Self::TraceDataType => &[WasmValType::I32],
|
||||
Self::I64 => &[WasmValType::I64],
|
||||
Self::InBytes | Self::InStr | Self::InU32 | Self::OutBytes => {
|
||||
&[WasmValType::I32, WasmValType::I32]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// What a declaration calls each of those wasm parameters: the declared name
|
||||
/// for a scalar, and `{name}_ptr`/`{name}_len` for the pair a region lowers
|
||||
/// to.
|
||||
///
|
||||
/// **It must answer as many names as [`Self::as_wasm_params`] answers types**,
|
||||
/// since the generated closure declares them one against the other — hence the
|
||||
/// matching arms, and `lowers_every_declared_parameter_type`'s row-by-row
|
||||
/// length check.
|
||||
pub(crate) fn wasm_names(self, name: &Ident) -> Vec<Ident> {
|
||||
match self {
|
||||
Self::I32 | Self::I64 | Self::TraceDataType => vec![name.clone()],
|
||||
Self::InBytes | Self::InStr | Self::InU32 | Self::OutBytes => {
|
||||
vec![format_ident!("{name}_ptr"), format_ident!("{name}_len")]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The type a generated body takes this parameter as: a wasm scalar spelled as
|
||||
/// itself, everything else the argument type carrying its shape and direction —
|
||||
/// which is what makes an input region used as an output one a compile error
|
||||
/// naming both.
|
||||
///
|
||||
/// The argument types are the engine's, so `vm` is the path they are reached
|
||||
/// under; which types need it is decided here, a wasm scalar being `i32` under
|
||||
/// every engine.
|
||||
pub(crate) fn argument_type(self, vm: &TokenStream) -> TokenStream {
|
||||
match self {
|
||||
Self::I32 => quote!(i32),
|
||||
Self::I64 => quote!(i64),
|
||||
Self::TraceDataType => quote!(#vm::TraceCode),
|
||||
Self::InBytes => quote!(#vm::InBytes),
|
||||
Self::InStr => quote!(#vm::InStr),
|
||||
Self::InU32 => quote!(#vm::InU32),
|
||||
Self::OutBytes => quote!(#vm::OutBytes),
|
||||
}
|
||||
}
|
||||
|
||||
/// Which of the ABI's two argument traits builds this parameter's argument
|
||||
/// type, or `None` for a wasm scalar, which reaches a body as itself.
|
||||
///
|
||||
/// The arity is the whole of the distinction — `FromWasmRegion` takes the two
|
||||
/// of a `(ptr, len)` pair, `FromWasmScalar` the one of a code — so this answers
|
||||
/// alongside [`Self::as_wasm_params`] rather than from a predicate elsewhere.
|
||||
pub(crate) fn argument_trait(self) -> Option<TokenStream> {
|
||||
match self {
|
||||
Self::I32 | Self::I64 => None,
|
||||
Self::TraceDataType => Some(quote!(FromWasmScalar)),
|
||||
Self::InBytes | Self::InStr | Self::InU32 | Self::OutBytes => {
|
||||
Some(quote!(FromWasmRegion))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this parameter is a region the host writes to — what
|
||||
/// [`ResultType::BufferLength`] is the length *of*.
|
||||
pub(crate) fn is_out_region(self) -> bool {
|
||||
matches!(self, Self::OutBytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl ResultType {
|
||||
/// Recognizes the success type of a declaration's `HostResult<T>`, or
|
||||
/// refuses it against its own span.
|
||||
pub(crate) fn parse(success: &Type) -> syn::Result<Self> {
|
||||
const ALLOWED: &str = "a host function must return `HostResult<usize>` for a value it \
|
||||
writes to an output region, `HostResult<i32>` for one it answers \
|
||||
directly, or `HostResult<()>` for none at all";
|
||||
|
||||
if let Type::Tuple(tuple) = success
|
||||
&& tuple.elems.is_empty()
|
||||
{
|
||||
return Ok(Self::Nothing);
|
||||
}
|
||||
|
||||
match last_path_segment(success) {
|
||||
Some(name) if name == "usize" => Ok(Self::BufferLength),
|
||||
Some(name) if name == "i32" => Ok(Self::Value),
|
||||
_ => Err(syn::Error::new_spanned(success, ALLOWED)),
|
||||
}
|
||||
}
|
||||
|
||||
/// The generated table's `wasm_result` field: `Some(WasmValType::I32)`, or
|
||||
/// `None` for the function that answers nothing.
|
||||
///
|
||||
/// Spelled out here rather than left to `quote`'s `Option` impl, which emits
|
||||
/// nothing at all for `None`.
|
||||
pub(crate) fn wasm_result_tokens(self) -> TokenStream {
|
||||
match self.as_wasm_result() {
|
||||
Some(val_type) => quote! { Some(#val_type) },
|
||||
None => quote! { None },
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the value reaches the guest as the length of what was written to
|
||||
/// an output region.
|
||||
pub(crate) fn is_buffer_length(self) -> bool {
|
||||
matches!(self, Self::BufferLength)
|
||||
}
|
||||
|
||||
/// The wasm result, which does not distinguish a length from a value.
|
||||
fn as_wasm_result(self) -> Option<WasmValType> {
|
||||
match self {
|
||||
Self::BufferLength | Self::Value => Some(WasmValType::I32),
|
||||
Self::Nothing => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `WasmValType::I32` — the ABI crate's variant, named but never defined here.
|
||||
impl ToTokens for WasmValType {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
tokens.extend(match self {
|
||||
Self::I32 => quote! { WasmValType::I32 },
|
||||
Self::I64 => quote! { WasmValType::I64 },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// The last segment of a plain path type, when it carries no generic arguments:
|
||||
/// `i32`, `core::primitive::i32` and `TraceDataType` all answer their own name,
|
||||
/// `Vec<u8>` and `[u8; 4]` nothing.
|
||||
fn last_path_segment(ty: &Type) -> Option<&Ident> {
|
||||
let Type::Path(TypePath {
|
||||
qself: None, path, ..
|
||||
}) = ty
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
let last = path.segments.last()?;
|
||||
matches!(last.arguments, PathArguments::None).then_some(&last.ident)
|
||||
}
|
||||
|
||||
/// Whether `ty` is the named primitive, however it is spelled.
|
||||
fn is_named(ty: &Type, name: &str) -> bool {
|
||||
last_path_segment(ty).is_some_and(|segment| segment == name)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg_attr(coverage_nightly, coverage(off))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use syn::parse_quote;
|
||||
|
||||
use WasmValType::{I32, I64};
|
||||
|
||||
/// Every declared parameter type and everything it decides: what it costs on
|
||||
/// the wire, what a body is handed for it, and which trait builds that.
|
||||
///
|
||||
/// The names are asserted by length rather than spelling, since a type
|
||||
/// answering fewer names than value types is the one way these answers can
|
||||
/// contradict each other.
|
||||
#[test]
|
||||
fn lowers_every_declared_parameter_type() {
|
||||
let mapping: [(Type, &[WasmValType], &str, Option<&str>); 7] = [
|
||||
(parse_quote!(i32), &[I32], "i32", None),
|
||||
(parse_quote!(i64), &[I64], "i64", None),
|
||||
(
|
||||
parse_quote!(TraceDataType),
|
||||
&[I32],
|
||||
"vm :: TraceCode",
|
||||
Some("FromWasmScalar"),
|
||||
),
|
||||
(
|
||||
parse_quote!(&[u8]),
|
||||
&[I32, I32],
|
||||
"vm :: InBytes",
|
||||
Some("FromWasmRegion"),
|
||||
),
|
||||
(
|
||||
parse_quote!(&str),
|
||||
&[I32, I32],
|
||||
"vm :: InStr",
|
||||
Some("FromWasmRegion"),
|
||||
),
|
||||
(
|
||||
parse_quote!(u32),
|
||||
&[I32, I32],
|
||||
"vm :: InU32",
|
||||
Some("FromWasmRegion"),
|
||||
),
|
||||
(
|
||||
parse_quote!(&mut [u8]),
|
||||
&[I32, I32],
|
||||
"vm :: OutBytes",
|
||||
Some("FromWasmRegion"),
|
||||
),
|
||||
];
|
||||
let declared_name = format_ident!("seq");
|
||||
let vm = quote!(vm);
|
||||
|
||||
for (declared, wasm, argument, argument_trait) in mapping {
|
||||
let param = ParamType::parse(&declared)
|
||||
.unwrap_or_else(|_| panic!("`{}` should be a parameter type", quoted(&declared)));
|
||||
|
||||
assert_eq!(param.as_wasm_params(), wasm, "`{}`", quoted(&declared));
|
||||
assert_eq!(
|
||||
param.argument_type(&vm).to_string(),
|
||||
argument,
|
||||
"`{}`",
|
||||
quoted(&declared)
|
||||
);
|
||||
assert_eq!(
|
||||
param
|
||||
.argument_trait()
|
||||
.map(|name| name.to_string())
|
||||
.as_deref(),
|
||||
argument_trait,
|
||||
"`{}`",
|
||||
quoted(&declared)
|
||||
);
|
||||
assert_eq!(
|
||||
param.wasm_names(&declared_name).len(),
|
||||
wasm.len(),
|
||||
"one name per wasm parameter: `{}`",
|
||||
quoted(&declared)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A region's two wasm parameters are named off the declaration, so the
|
||||
/// generated closure reads as the declaration does.
|
||||
#[test]
|
||||
fn names_a_region_s_pair_after_the_declared_parameter() {
|
||||
let seq = format_ident!("seq");
|
||||
|
||||
let names = |declared: Type| {
|
||||
ParamType::parse(&declared)
|
||||
.expect("a parameter type")
|
||||
.wasm_names(&seq)
|
||||
.iter()
|
||||
.map(Ident::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
assert_eq!(names(parse_quote!(u32)), ["seq_ptr", "seq_len"]);
|
||||
assert_eq!(names(parse_quote!(i32)), ["seq"]);
|
||||
}
|
||||
|
||||
/// The two `(ptr, len)` pairs lower alike but are told apart, since only the
|
||||
/// direction says who may write to the region.
|
||||
#[test]
|
||||
fn keeps_the_regions_apart() {
|
||||
let input: Type = parse_quote!(&[u8]);
|
||||
let output: Type = parse_quote!(&mut [u8]);
|
||||
|
||||
assert!(!ParamType::parse(&input).unwrap().is_out_region());
|
||||
assert!(ParamType::parse(&output).unwrap().is_out_region());
|
||||
}
|
||||
|
||||
/// A type outside the mapping is refused rather than lowered to a guess.
|
||||
#[test]
|
||||
fn refuses_parameter_types_outside_the_mapping() {
|
||||
let outside: [Type; 11] = [
|
||||
parse_quote!(u64),
|
||||
parse_quote!(u8),
|
||||
parse_quote!(usize),
|
||||
parse_quote!(bool),
|
||||
parse_quote!(Vec<u8>),
|
||||
parse_quote!([u8; 4]),
|
||||
parse_quote!(&mut str),
|
||||
parse_quote!(&i32),
|
||||
parse_quote!(&[i32]),
|
||||
parse_quote!(&Foo),
|
||||
parse_quote!(()),
|
||||
];
|
||||
|
||||
for declared in outside {
|
||||
let Err(error) = ParamType::parse(&declared) else {
|
||||
panic!("`{}` should not be a parameter type", quoted(&declared));
|
||||
};
|
||||
assert!(
|
||||
error.to_string().contains("must be `i32`"),
|
||||
"`{}`: {error}",
|
||||
quoted(&declared)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The three success types, and the wasm result each becomes. `usize` and
|
||||
/// `i32` agree on the wire and are separate rows.
|
||||
#[test]
|
||||
fn lowers_every_success_type() {
|
||||
let mapping: [(Type, ResultType, Option<WasmValType>); 3] = [
|
||||
(parse_quote!(usize), ResultType::BufferLength, Some(I32)),
|
||||
(parse_quote!(i32), ResultType::Value, Some(I32)),
|
||||
(parse_quote!(()), ResultType::Nothing, None),
|
||||
];
|
||||
|
||||
for (declared, expected, wasm_result) in mapping {
|
||||
let result = ResultType::parse(&declared)
|
||||
.unwrap_or_else(|_| panic!("`{}` should be a success type", quoted(&declared)));
|
||||
|
||||
assert_eq!(result, expected, "`{}`", quoted(&declared));
|
||||
assert_eq!(
|
||||
result.as_wasm_result(),
|
||||
wasm_result,
|
||||
"`{}`",
|
||||
quoted(&declared)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The distinction the wasm result loses: which of the two `i32` results was
|
||||
/// declared decides how the value reaches the guest.
|
||||
#[test]
|
||||
fn tells_a_length_from_a_value() {
|
||||
assert!(ResultType::BufferLength.is_buffer_length());
|
||||
assert!(!ResultType::Value.is_buffer_length());
|
||||
assert!(!ResultType::Nothing.is_buffer_length());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refuses_success_types_outside_the_mapping() {
|
||||
let outside: [Type; 6] = [
|
||||
parse_quote!(u32),
|
||||
parse_quote!(i64),
|
||||
parse_quote!(bool),
|
||||
parse_quote!([u8; 32]),
|
||||
parse_quote!(Vec<u8>),
|
||||
parse_quote!((usize, i32)),
|
||||
];
|
||||
|
||||
for declared in outside {
|
||||
let Err(error) = ResultType::parse(&declared) else {
|
||||
panic!("`{}` should not be a success type", quoted(&declared));
|
||||
};
|
||||
assert!(
|
||||
error
|
||||
.to_string()
|
||||
.contains("must return `HostResult<usize>`"),
|
||||
"`{}`: {error}",
|
||||
quoted(&declared)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A qualified spelling is the same type, matching how the return type finds
|
||||
/// `HostResult`.
|
||||
#[test]
|
||||
fn accepts_qualified_spellings() {
|
||||
let qualified: Type = parse_quote!(core::primitive::i32);
|
||||
assert_eq!(ParamType::parse(&qualified).unwrap(), ParamType::I32);
|
||||
|
||||
let qualified: Type = parse_quote!(xrpl_host_functions::TraceDataType);
|
||||
assert_eq!(
|
||||
ParamType::parse(&qualified).unwrap(),
|
||||
ParamType::TraceDataType
|
||||
);
|
||||
}
|
||||
|
||||
/// The emitted tokens name the ABI crate's variants, which is the whole of
|
||||
/// what crosses out of this crate. Pinned here so a break in the mirror is a
|
||||
/// failure with a span rather than a rustc error at the call site.
|
||||
#[test]
|
||||
fn emits_references_to_the_hand_written_variants() {
|
||||
assert_eq!(I32.to_token_stream().to_string(), "WasmValType :: I32");
|
||||
assert_eq!(I64.to_token_stream().to_string(), "WasmValType :: I64");
|
||||
|
||||
assert_eq!(
|
||||
ResultType::BufferLength.wasm_result_tokens().to_string(),
|
||||
"Some (WasmValType :: I32)"
|
||||
);
|
||||
assert_eq!(ResultType::Nothing.wasm_result_tokens().to_string(), "None");
|
||||
}
|
||||
|
||||
fn quoted(ty: &Type) -> String {
|
||||
ty.to_token_stream().to_string()
|
||||
}
|
||||
}
|
||||
1155
crates/xrpl-host-functions-macros/src/parsed_host_function.rs
Normal file
1155
crates/xrpl-host-functions-macros/src/parsed_host_function.rs
Normal file
File diff suppressed because it is too large
Load Diff
23
crates/xrpl-host-functions/Cargo.toml
Normal file
23
crates/xrpl-host-functions/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "xrpl-host-functions"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
xrpl-host-functions-macros.path = "../xrpl-host-functions-macros"
|
||||
|
||||
# `wasmi_glue!` and the two argument traits it marshals through are a host's
|
||||
# business, and this crate is what a contract links against — so the feature is
|
||||
# what keeps `cargo doc` here showing a contract developer only the ABI. It costs
|
||||
# nothing either way: a `macro_rules!` is inert and a trait with no impls emits
|
||||
# nothing.
|
||||
#
|
||||
# `xrpl-wasm-vm` enables it and features unify across the graph, so the off
|
||||
# configuration is only ever checked by `cargo check -p xrpl-host-functions`,
|
||||
# which is in the loop in `tmp/notes/wasm-vm/testing.md` for that reason.
|
||||
[features]
|
||||
default = []
|
||||
wasmi_glue = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
572
crates/xrpl-host-functions/src/lib.rs
Normal file
572
crates/xrpl-host-functions/src/lib.rs
Normal file
@@ -0,0 +1,572 @@
|
||||
//! The wasm host ABI: the one place it is declared.
|
||||
//!
|
||||
//! `host_functions!` turns the declaration block at the bottom of this file into the
|
||||
//! [`HostFunctions`] trait a host implements and the [`HostFunctionSpec`] table a
|
||||
//! wasm engine registers from.
|
||||
//!
|
||||
//! The split: hand-written here is the vocabulary the declarations are written in —
|
||||
//! [`HostError`], [`TraceDataType`], [`HostResult`], [`HASH_LEN`] — and everything
|
||||
//! derived from the declarations is generated. The expansion names nothing this file
|
||||
//! does not, so the two sides meet only in the block below.
|
||||
//!
|
||||
//! Three items cross that split the other way, named by the expansion but by no
|
||||
//! declaration: [`WasmValType`], which the derived wasm signatures are spelled in,
|
||||
//! and `FromWasmRegion`/`FromWasmScalar`, which `wasmi_glue!` builds a marshalled
|
||||
//! argument through.
|
||||
//!
|
||||
//! So this file is lists — error codes, trace data types, functions. The `macro_rules!`
|
||||
//! that expand the first two into enums live in `macros.rs`.
|
||||
|
||||
#![no_std]
|
||||
#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
// Not re-exported: the ABI is declared once, here, and this is the only call site.
|
||||
use xrpl_host_functions_macros::host_functions;
|
||||
|
||||
host_errors! {
|
||||
Unimplemented = -1,
|
||||
FieldNotFound = -2,
|
||||
BufferTooSmall = -3,
|
||||
NoArray = -4,
|
||||
NotLeafField = -5,
|
||||
LocatorMalformed = -6,
|
||||
SlotOutRange = -7,
|
||||
SlotsFull = -8,
|
||||
EmptySlot = -9,
|
||||
LedgerObjNotFound = -10,
|
||||
OutOfTransferLimit = -11,
|
||||
DataFieldTooLarge = -12,
|
||||
PointerOutOfBounds = -13,
|
||||
NoMemExported = -14,
|
||||
InvalidParams = -15,
|
||||
InvalidAccount = -16,
|
||||
InvalidField = -17,
|
||||
IndexOutOfBounds = -18,
|
||||
FloatInputMalformed = -19,
|
||||
FloatComputationError = -20,
|
||||
/// Internal fatal error.
|
||||
/// User code will never see this error but keep it reserved to not rely on the value.
|
||||
InternalFatal = -2147483648,
|
||||
}
|
||||
|
||||
/// Convenience alias for the trait's fallible returns.
|
||||
pub type HostResult<T> = Result<T, HostError>;
|
||||
|
||||
/// A `sha512Half` digest: the first 32 bytes of a SHA-512, as XRPL uses it.
|
||||
pub const HASH_LEN: usize = 32;
|
||||
|
||||
trace_data_types! {
|
||||
/// 8 little-endian bytes, rendered as a signed decimal.
|
||||
Int64 = 1,
|
||||
/// 8 little-endian bytes, rendered as an unsigned decimal.
|
||||
Uint64 = 2,
|
||||
/// A serialized XRPL float: 12 bytes, mantissa then exponent.
|
||||
Xfloat = 3,
|
||||
/// A 20-byte account ID, rendered as base58.
|
||||
Account = 4,
|
||||
/// A serialized `STAmount`.
|
||||
Amount = 5,
|
||||
/// Raw bytes, hex-encoded.
|
||||
AsHex = 6,
|
||||
/// Bytes rendered verbatim as text.
|
||||
AsText = 7,
|
||||
}
|
||||
|
||||
/// The wasm module name a guest imports these functions under:
|
||||
/// `(import "host_lib" "ldgr_index" …)`.
|
||||
pub const HOST_MODULE: &str = "host_lib";
|
||||
|
||||
/// A wasm value type, as many of them as this ABI uses — the vocabulary the
|
||||
/// generated wasm signatures are spelled in, which an engine maps to its own value
|
||||
/// types once.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum WasmValType {
|
||||
I32,
|
||||
I64,
|
||||
}
|
||||
|
||||
/// Builds the argument an engine marshals a `(ptr, len)` region into: a declared
|
||||
/// `&[u8]`, `&str`, `u32` or `&mut [u8]`. Nothing is checked here — the pair
|
||||
/// arrives as the guest sent it, and refusing a malformed region is the engine's
|
||||
/// business.
|
||||
///
|
||||
/// **Kept apart from [`FromWasmScalar`]** rather than folded into one trait with
|
||||
/// an associated wasm type, for the diagnostic: the mistake worth catching is an
|
||||
/// arity one — a declared `u32` is a region, not a code — and as two traits that
|
||||
/// lands as an unsatisfied bound at the offending argument type rather than an
|
||||
/// `i32`-against-`(i32, i32)` mismatch at the macro call.
|
||||
#[cfg(feature = "wasmi_glue")]
|
||||
pub trait FromWasmRegion {
|
||||
fn from_wasm(ptr: i32, len: i32) -> Self;
|
||||
}
|
||||
|
||||
/// Builds the argument an engine marshals a single `i32` code into: the declared
|
||||
/// `TraceDataType`. [`FromWasmRegion`] says why the two are separate traits.
|
||||
#[cfg(feature = "wasmi_glue")]
|
||||
pub trait FromWasmScalar {
|
||||
fn from_wasm(code: i32) -> Self;
|
||||
}
|
||||
|
||||
// Two rules hold over every declaration below, and neither is visible at any one of
|
||||
// them. They are what lets the wasm signature be read off the declaration.
|
||||
//
|
||||
// **Declaration order is wasm parameter order.** So `mode` comes after `out` in the
|
||||
// float functions, and `data_type` between `trace`'s two regions: the wire's order,
|
||||
// not the one a Rust signature would choose.
|
||||
//
|
||||
// **`i32` and `i64` are the wasm scalars, spelled as themselves; every other type is
|
||||
// marshalled.** `&[u8]`/`&str` and `&mut [u8]` are `(ptr, len)` pairs, `TraceDataType`
|
||||
// is an `i32` code the engine names before a host sees it, and **`u32` is four
|
||||
// little-endian bytes in a region**, not a scalar, which is how the guest SDK passes a
|
||||
// sequence number.
|
||||
host_functions! {
|
||||
/// The sequence number of the ledger being built, as 4 little-endian bytes.
|
||||
#[gas = 60]
|
||||
#[wasm_name = "ldgr_index"]
|
||||
fn get_ledger_sqn(&self, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The close time of the parent (last-closed) ledger, as 4 little-endian bytes.
|
||||
#[gas = 60]
|
||||
#[wasm_name = "parent_ldgr_time"]
|
||||
fn get_parent_ledger_time(&self, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The hash of the parent (last-closed) ledger, as 32 bytes.
|
||||
#[gas = 60]
|
||||
#[wasm_name = "parent_ldgr_hash"]
|
||||
fn get_parent_ledger_hash(&self, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The base fee of the ledger being built, in drops, as 4 little-endian bytes.
|
||||
#[gas = 60]
|
||||
#[wasm_name = "base_fee"]
|
||||
fn get_base_fee(&self, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// Whether an amendment is enabled. The input is either its 32-byte id or its name;
|
||||
/// the answer is `1` if enabled and `0` if not.
|
||||
#[gas = 100]
|
||||
#[wasm_name = "amendment_enabled"]
|
||||
fn is_amendment_enabled(&self, amendment: &[u8]) -> HostResult<i32>;
|
||||
|
||||
/// Load the ledger object with the given 32-byte id into a cache slot, so later
|
||||
/// calls can read its fields. `cache_idx` selects the slot (1-based); `0` asks the
|
||||
/// host to assign a free one. Answers the slot used.
|
||||
#[gas = 5000]
|
||||
#[wasm_name = "cache_le"]
|
||||
fn cache_ledger_obj(&self, obj_id: &[u8], cache_idx: i32) -> HostResult<i32>;
|
||||
|
||||
/// The serialized bytes of one field of the transaction being executed, selected
|
||||
/// by its `SField` code.
|
||||
#[gas = 70]
|
||||
#[wasm_name = "tx_field"]
|
||||
fn get_tx_field(&self, field: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The serialized bytes of one field of the current (escrow) ledger object.
|
||||
#[gas = 70]
|
||||
#[wasm_name = "home_le_field"]
|
||||
fn get_current_ledger_obj_field(&self, field: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The serialized bytes of one field of a previously cached ledger object,
|
||||
/// selected by its cache slot and the field's `SField` code.
|
||||
#[gas = 70]
|
||||
#[wasm_name = "le_field"]
|
||||
fn get_ledger_obj_field(&self, cache_idx: i32, field: i32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The serialized bytes of a nested field of the transaction, reached by a
|
||||
/// `locator`: a path of little-endian `i32` steps (so its byte length is a non-zero
|
||||
/// multiple of 4).
|
||||
#[gas = 110]
|
||||
#[wasm_name = "tx_inner"]
|
||||
fn get_tx_nested_field(&self, locator: &[u8], out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The serialized bytes of a nested field of the current (escrow) ledger object,
|
||||
/// reached by a `locator`, as with [`HostFunctions::get_tx_nested_field`].
|
||||
#[gas = 110]
|
||||
#[wasm_name = "home_le_inner"]
|
||||
fn get_current_ledger_obj_nested_field(
|
||||
&self,
|
||||
locator: &[u8],
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The serialized bytes of a nested field of a previously cached ledger object,
|
||||
/// selected by its cache slot and reached by a `locator`.
|
||||
#[gas = 110]
|
||||
#[wasm_name = "le_inner"]
|
||||
fn get_ledger_obj_nested_field(
|
||||
&self,
|
||||
cache_idx: i32,
|
||||
locator: &[u8],
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The number of elements in an array field of the transaction, selected by its
|
||||
/// `SField` code. Answers the count directly; `NoArray` if the field is not an array.
|
||||
#[gas = 40]
|
||||
#[wasm_name = "tx_arr_len"]
|
||||
fn get_tx_array_len(&self, field: i32) -> HostResult<i32>;
|
||||
|
||||
/// The number of elements in an array field of the current (escrow) ledger
|
||||
/// object, as with [`HostFunctions::get_tx_array_len`].
|
||||
#[gas = 40]
|
||||
#[wasm_name = "home_le_arr_len"]
|
||||
fn get_current_ledger_obj_array_len(&self, field: i32) -> HostResult<i32>;
|
||||
|
||||
/// The number of elements in an array field of a previously cached ledger object,
|
||||
/// selected by its cache slot and `SField` code.
|
||||
#[gas = 40]
|
||||
#[wasm_name = "le_arr_len"]
|
||||
fn get_ledger_obj_array_len(&self, cache_idx: i32, field: i32) -> HostResult<i32>;
|
||||
|
||||
/// The number of elements in a nested array field of the transaction, reached by a
|
||||
/// `locator`.
|
||||
#[gas = 70]
|
||||
#[wasm_name = "tx_inner_arr_len"]
|
||||
fn get_tx_nested_array_len(&self, locator: &[u8]) -> HostResult<i32>;
|
||||
|
||||
/// The number of elements in a nested array field of the current (escrow) ledger
|
||||
/// object, reached by a `locator`, as with [`HostFunctions::get_tx_nested_array_len`].
|
||||
#[gas = 70]
|
||||
#[wasm_name = "home_le_inner_arr_len"]
|
||||
fn get_current_ledger_obj_nested_array_len(&self, locator: &[u8]) -> HostResult<i32>;
|
||||
|
||||
/// The number of elements in a nested array field of a previously cached ledger
|
||||
/// object, selected by its cache slot and reached by a `locator`.
|
||||
#[gas = 70]
|
||||
#[wasm_name = "le_inner_arr_len"]
|
||||
fn get_ledger_obj_nested_array_len(&self, cache_idx: i32, locator: &[u8]) -> HostResult<i32>;
|
||||
|
||||
/// Verify `signature` over `message` under `pubkey`. Answers `1` if the signature
|
||||
/// is valid, `0` if not, or a negative error.
|
||||
#[gas = 300]
|
||||
#[wasm_name = "check_sig"]
|
||||
fn check_signature(
|
||||
&self,
|
||||
message: &[u8],
|
||||
signature: &[u8],
|
||||
pubkey: &[u8],
|
||||
) -> HostResult<i32>;
|
||||
|
||||
/// The 32-byte ledger key (keylet) of an account's `AccountRoot`, computed from a
|
||||
/// 20-byte account id.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "accountroot_id"]
|
||||
fn account_keylet(&self, account: &[u8], out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of an AMM, computed from its two assets. Each asset is a byte
|
||||
/// slice whose length selects its kind (24 = MPT, 20 = XRP, 40 = issued currency +
|
||||
/// issuer).
|
||||
#[gas = 450]
|
||||
#[wasm_name = "amm_id"]
|
||||
fn amm_keylet(&self, asset1: &[u8], asset2: &[u8], out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `Check`, computed from a 20-byte account id and its
|
||||
/// sequence number.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "check_id"]
|
||||
fn check_keylet(&self, account: &[u8], seq: u32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `Credential`, computed from the 20-byte subject and
|
||||
/// issuer account ids and a credential-type byte string.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "credential_id"]
|
||||
fn credential_keylet(
|
||||
&self,
|
||||
subject: &[u8],
|
||||
issuer: &[u8],
|
||||
credential_type: &[u8],
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `Delegate` object, computed from the 20-byte account and
|
||||
/// the account it authorizes.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "delegate_id"]
|
||||
fn delegate_keylet(
|
||||
&self,
|
||||
account: &[u8],
|
||||
authorize: &[u8],
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `DepositPreauth`, computed from the 20-byte account and
|
||||
/// the account it authorizes to deposit.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "deposit_preauth_id"]
|
||||
fn deposit_preauth_keylet(
|
||||
&self,
|
||||
account: &[u8],
|
||||
authorize: &[u8],
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of an account's `DID`, computed from its 20-byte account id.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "did_id"]
|
||||
fn did_keylet(&self, account: &[u8], out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of an `Escrow`, computed from the 20-byte owner account and
|
||||
/// its sequence number.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "escrow_id"]
|
||||
fn escrow_keylet(&self, account: &[u8], seq: u32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `RippleState` (trust line), computed from two 20-byte
|
||||
/// account ids and a 20-byte currency.
|
||||
#[gas = 400]
|
||||
#[wasm_name = "trustline_id"]
|
||||
fn trust_line_keylet(
|
||||
&self,
|
||||
account1: &[u8],
|
||||
account2: &[u8],
|
||||
currency: &[u8],
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of an `MPTokenIssuance`, computed from the 20-byte issuer
|
||||
/// account and its sequence number.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "mpt_issuance_id"]
|
||||
fn mptoken_issuance_keylet(
|
||||
&self,
|
||||
issuer: &[u8],
|
||||
seq: u32,
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of an `MPToken`, computed from a 24-byte MPT issuance id and
|
||||
/// the 20-byte holder account.
|
||||
#[gas = 500]
|
||||
#[wasm_name = "mptoken_id"]
|
||||
fn mptoken_keylet(&self, mptid: &[u8], holder: &[u8], out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of an `NFTokenOffer`, computed from the 20-byte owner account
|
||||
/// and its sequence number.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "nft_offer_id"]
|
||||
fn nftoken_offer_keylet(
|
||||
&self,
|
||||
account: &[u8],
|
||||
seq: u32,
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of an `Offer`, computed from the 20-byte owner account and
|
||||
/// its sequence number.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "offer_id"]
|
||||
fn offer_keylet(&self, account: &[u8], seq: u32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of an `Oracle`, computed from the 20-byte owner account and
|
||||
/// its document id.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "oracle_id"]
|
||||
fn oracle_keylet(&self, account: &[u8], doc_id: u32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `PayChannel`, computed from the 20-byte source account,
|
||||
/// the 20-byte destination account, and the channel's sequence number.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "paychan_id"]
|
||||
fn paychannel_keylet(
|
||||
&self,
|
||||
account: &[u8],
|
||||
destination: &[u8],
|
||||
seq: u32,
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `PermissionedDomain`, computed from the 20-byte owner
|
||||
/// account and its sequence number.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "permissioned_domain_id"]
|
||||
fn permissioned_domain_keylet(
|
||||
&self,
|
||||
account: &[u8],
|
||||
seq: u32,
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `SignerList`, computed from its 20-byte owner account.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "signers_id"]
|
||||
fn signer_list_keylet(&self, account: &[u8], out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `Ticket`, computed from the 20-byte owner account and
|
||||
/// its ticket sequence number.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "ticket_id"]
|
||||
fn ticket_keylet(&self, account: &[u8], seq: u32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `Vault`, computed from the 20-byte owner account and its
|
||||
/// sequence number.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "vault_id"]
|
||||
fn vault_keylet(&self, account: &[u8], seq: u32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `Sponsorship`, computed from the 20-byte sponsor account
|
||||
/// and the 20-byte sponsee account.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "sponsorship_id"]
|
||||
fn sponsorship_keylet(
|
||||
&self,
|
||||
sponsor: &[u8],
|
||||
sponsee: &[u8],
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `LoanBroker`, computed from the 20-byte owner account and
|
||||
/// its sequence number.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "loan_broker_id"]
|
||||
fn loan_broker_keylet(&self, owner: &[u8], seq: u32, out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 32-byte keylet of a `Loan`, computed from the 32-byte id of its `LoanBroker`
|
||||
/// and the loan's sequence number.
|
||||
#[gas = 350]
|
||||
#[wasm_name = "loan_id"]
|
||||
fn loan_keylet(
|
||||
&self,
|
||||
loan_broker_id: &[u8],
|
||||
loan_seq: u32,
|
||||
out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// The XRPL `sha512Half` of `data`: the first [`HASH_LEN`] bytes of its SHA-512.
|
||||
#[gas = 2000]
|
||||
#[wasm_name = "sha512_half"]
|
||||
fn sha512_half(&self, data: &[u8], out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// Writes `msg` to the trace log, followed by `data` rendered as `data_type` says.
|
||||
///
|
||||
/// The one declaration whose wasm function has **no result**: this node's own log
|
||||
/// is its only effect, so a guest is told nothing. An `Err` from a host therefore
|
||||
/// reaches it in no form, and only the host-fatal ones do anything at all.
|
||||
#[gas = 30]
|
||||
#[wasm_name = "trace"]
|
||||
fn trace(&self, msg: &str, data_type: TraceDataType, data: &[u8]) -> HostResult<()>;
|
||||
|
||||
/// Stores `data` as the current object's data field, replacing whatever was there,
|
||||
/// and returns the number of bytes stored; `DataFieldTooLarge` if it exceeds the
|
||||
/// host's limit.
|
||||
#[gas = 1000]
|
||||
#[wasm_name = "set_data"]
|
||||
fn update_data(&self, data: &[u8]) -> HostResult<i32>;
|
||||
|
||||
/// The URI of the `NFToken` with id `nft_id` (32 bytes) held by the 20-byte
|
||||
/// `account`.
|
||||
#[gas = 5000]
|
||||
#[wasm_name = "nft_uri"]
|
||||
fn get_nft(&self, account: &[u8], nft_id: &[u8], out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The 20-byte issuer account encoded in the `NFToken` id `nft_id` (32 bytes).
|
||||
#[gas = 70]
|
||||
#[wasm_name = "nft_issuer"]
|
||||
fn get_nft_issuer(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The taxon encoded in the `NFToken` id `nft_id` (32 bytes), as four little-endian
|
||||
/// bytes.
|
||||
#[gas = 60]
|
||||
#[wasm_name = "nft_taxon"]
|
||||
fn get_nft_taxon(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
/// The flags encoded in the `NFToken` id `nft_id` (32 bytes).
|
||||
#[gas = 60]
|
||||
#[wasm_name = "nft_flags"]
|
||||
fn get_nft_flags(&self, nft_id: &[u8]) -> HostResult<i32>;
|
||||
|
||||
/// The transfer fee encoded in the `NFToken` id `nft_id` (32 bytes).
|
||||
#[gas = 60]
|
||||
#[wasm_name = "nft_xfer_fee"]
|
||||
fn get_nft_transfer_fee(&self, nft_id: &[u8]) -> HostResult<i32>;
|
||||
|
||||
/// The sequence number encoded in the `NFToken` id `nft_id` (32 bytes), as four
|
||||
/// little-endian bytes.
|
||||
#[gas = 60]
|
||||
#[wasm_name = "nft_serial"]
|
||||
fn get_nft_sequence(&self, nft_id: &[u8], out: &mut [u8]) -> HostResult<usize>;
|
||||
|
||||
// A "float" here is an XRPL `Number` in its serialized form: a byte blob the guest
|
||||
// holds opaquely and hands back to these functions. Inputs and outputs that are
|
||||
// floats are byte regions; `mode` is the rounding mode, a scalar the guest chooses.
|
||||
|
||||
/// A float built from the signed integer `x` under rounding `mode`.
|
||||
#[gas = 100]
|
||||
#[wasm_name = "float_from_int"]
|
||||
fn float_from_int(&self, x: i64, out: &mut [u8], mode: i32) -> HostResult<usize>;
|
||||
|
||||
/// A float built from the unsigned integer in the 8-byte region `x` under rounding
|
||||
/// `mode`.
|
||||
#[gas = 130]
|
||||
#[wasm_name = "float_from_uint"]
|
||||
fn float_from_uint(&self, x: &[u8], out: &mut [u8], mode: i32) -> HostResult<usize>;
|
||||
|
||||
/// A float built from the serialized `STAmount` in `amount` under rounding `mode`.
|
||||
#[gas = 150]
|
||||
#[wasm_name = "float_from_stamount"]
|
||||
fn float_from_stamount(&self, amount: &[u8], out: &mut [u8], mode: i32) -> HostResult<usize>;
|
||||
|
||||
/// A float built from the serialized `STNumber` in `number` under rounding `mode`.
|
||||
#[gas = 150]
|
||||
#[wasm_name = "float_from_stnumber"]
|
||||
fn float_from_stnumber(&self, number: &[u8], out: &mut [u8], mode: i32) -> HostResult<usize>;
|
||||
|
||||
/// The float `x` rounded to a signed integer under rounding `mode`, as eight
|
||||
/// little-endian bytes.
|
||||
#[gas = 130]
|
||||
#[wasm_name = "float_to_int"]
|
||||
fn float_to_int(&self, x: &[u8], out: &mut [u8], mode: i32) -> HostResult<usize>;
|
||||
|
||||
/// The float `x` split into its mantissa (eight little-endian bytes) and its exponent
|
||||
/// (four little-endian bytes), each written to its own output region.
|
||||
#[gas = 130]
|
||||
#[wasm_name = "float_to_mant_exp"]
|
||||
fn float_to_mant_exp(
|
||||
&self,
|
||||
x: &[u8],
|
||||
mantissa_out: &mut [u8],
|
||||
exponent_out: &mut [u8],
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// A float built from `mantissa` and `exponent` under rounding `mode`.
|
||||
#[gas = 100]
|
||||
#[wasm_name = "float_from_mant_exp"]
|
||||
fn float_from_mant_exp(
|
||||
&self,
|
||||
mantissa: i64,
|
||||
exponent: i32,
|
||||
out: &mut [u8],
|
||||
mode: i32,
|
||||
) -> HostResult<usize>;
|
||||
|
||||
/// Compares floats `x` and `y`, returning a negative, zero, or positive scalar as
|
||||
/// `x` is less than, equal to, or greater than `y`.
|
||||
#[gas = 80]
|
||||
#[wasm_name = "float_cmp"]
|
||||
fn float_compare(&self, x: &[u8], y: &[u8]) -> HostResult<i32>;
|
||||
|
||||
/// The float sum `x + y` under rounding `mode`.
|
||||
#[gas = 160]
|
||||
#[wasm_name = "float_add"]
|
||||
fn float_add(&self, x: &[u8], y: &[u8], out: &mut [u8], mode: i32) -> HostResult<usize>;
|
||||
|
||||
/// The float difference `x - y` under rounding `mode`.
|
||||
#[gas = 160]
|
||||
#[wasm_name = "float_sub"]
|
||||
fn float_subtract(&self, x: &[u8], y: &[u8], out: &mut [u8], mode: i32) -> HostResult<usize>;
|
||||
|
||||
/// The float product `x * y` under rounding `mode`.
|
||||
#[gas = 300]
|
||||
#[wasm_name = "float_mult"]
|
||||
fn float_multiply(&self, x: &[u8], y: &[u8], out: &mut [u8], mode: i32) -> HostResult<usize>;
|
||||
|
||||
/// The float quotient `x / y` under rounding `mode`.
|
||||
#[gas = 300]
|
||||
#[wasm_name = "float_div"]
|
||||
fn float_divide(&self, x: &[u8], y: &[u8], out: &mut [u8], mode: i32) -> HostResult<usize>;
|
||||
|
||||
/// The float `x` raised to the power `n` under rounding `mode`.
|
||||
#[gas = 5500]
|
||||
#[wasm_name = "float_pow"]
|
||||
fn float_power(&self, x: &[u8], n: i32, out: &mut [u8], mode: i32) -> HostResult<usize>;
|
||||
}
|
||||
102
crates/xrpl-host-functions/src/macros.rs
Normal file
102
crates/xrpl-host-functions/src/macros.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
//! The `macro_rules!` behind the two hand-listed enums, [`crate::HostError`] and
|
||||
//! [`crate::TraceDataType`].
|
||||
//!
|
||||
//! Each takes one list of `Variant = code,` and expands the enum together with the
|
||||
//! `ALL`/`code`/`from_code` set that must not fall behind it. The lists themselves stay
|
||||
//! in `lib.rs`, beside the `host_functions!` block.
|
||||
|
||||
/// Declares [`crate::HostError`] from one list: the variants, `HostError::ALL` and
|
||||
/// `HostError::from_code`'s table all expand from the codes given.
|
||||
///
|
||||
/// One list is what makes `ALL` complete. Rust cannot enumerate an enum's
|
||||
/// variants — an exhaustive `match` forces an arm per variant but gives nothing to
|
||||
/// iterate — so a hand-written `ALL` beside a hand-written enum could only be kept
|
||||
/// in step by review, and `ALL`'s whole purpose is to be the set a test can trust.
|
||||
/// A code added to the list gains its `ALL` entry and its `from_code` arm by
|
||||
/// construction. `HostFunctionSpec::ALL` is complete the same way, from the
|
||||
/// `host_functions!` block.
|
||||
macro_rules! host_errors {
|
||||
($($(#[$doc:meta])* $variant:ident = $code:literal,)+) => {
|
||||
/// Error codes a host function may return.
|
||||
///
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(i32)]
|
||||
pub enum HostError {
|
||||
$($(#[$doc])* $variant = $code,)+
|
||||
}
|
||||
|
||||
impl HostError {
|
||||
/// Every error a host function may return, in code order.
|
||||
///
|
||||
/// The complete set, and complete by construction: a wasm engine's
|
||||
/// split between the codes it hands the guest and the conditions it
|
||||
/// traps on is a decision per variant, so the test that checks the
|
||||
/// split iterates this and a code added to the ABI cannot slip past it.
|
||||
pub const ALL: &'static [HostError] = &[$(HostError::$variant,)+];
|
||||
|
||||
/// The negative wire value a failed call returns. Every code but
|
||||
/// `InternalFatal` is one a guest reads off that value.
|
||||
#[inline]
|
||||
pub const fn code(self) -> i32 {
|
||||
self as i32
|
||||
}
|
||||
|
||||
/// Reconstruct a `HostError` from its wire code.
|
||||
///
|
||||
/// A code this ABI does not define is `InternalFatal`: an answer the
|
||||
/// caller cannot act on is the call not having been served, and that is
|
||||
/// the variant which says so. Positive values are not errors at all and go
|
||||
/// the same way, since this is reached only once a negative return has
|
||||
/// been read as a failure.
|
||||
pub const fn from_code(code: i32) -> HostError {
|
||||
match code {
|
||||
$($code => HostError::$variant,)+
|
||||
_ => HostError::InternalFatal,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Declares [`crate::TraceDataType`] from one list, so `TraceDataType::ALL`,
|
||||
/// `TraceDataType::code` and `TraceDataType::from_code` cannot fall behind the
|
||||
/// variants — the reason `host_errors!` above is written this way.
|
||||
macro_rules! trace_data_types {
|
||||
($($(#[$doc:meta])* $variant:ident = $code:literal,)+) => {
|
||||
/// How [`HostFunctions::trace`] is to read its data buffer.
|
||||
///
|
||||
/// The discriminants are wire values shared with the guest stdlib: append only,
|
||||
/// never renumber. They start at 1, so a zeroed argument names no type rather
|
||||
/// than the first one.
|
||||
///
|
||||
/// This is the declaration a guest and a host both compile against. The host
|
||||
/// side needs a second one — `cxx` cannot be a dependency here, since this
|
||||
/// crate also links into the guest — so `xrpl-wasm-vm-ffi` declares a shared
|
||||
/// enum for C++ and converts, exhaustively, from this.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(i32)]
|
||||
pub enum TraceDataType {
|
||||
$($(#[$doc])* $variant = $code,)+
|
||||
}
|
||||
|
||||
impl TraceDataType {
|
||||
/// Every data type a guest may name, in code order.
|
||||
pub const ALL: &'static [TraceDataType] = &[$(TraceDataType::$variant,)+];
|
||||
|
||||
/// The wire value a guest passes to name this type.
|
||||
#[inline]
|
||||
pub const fn code(self) -> i32 {
|
||||
self as i32
|
||||
}
|
||||
|
||||
/// The type `code` names, or `None`: the engine drops a call it cannot
|
||||
/// read rather than guessing at a rendering the guest did not ask for.
|
||||
pub const fn from_code(code: i32) -> Option<TraceDataType> {
|
||||
match code {
|
||||
$($code => Some(TraceDataType::$variant),)+
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
41
crates/xrpl-host-functions/tests/expansion_hygiene.rs
Normal file
41
crates/xrpl-host-functions/tests/expansion_hygiene.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
//! `host_functions!` must work outside the crate that declares the ABI: the only
|
||||
//! names its expansion needs are `WasmValType` and the ones the declarations
|
||||
//! themselves spell.
|
||||
//!
|
||||
//! That this crate compiles is also what shows the emitted `wasmi_glue!` costs
|
||||
//! nothing to carry: its body names an engine throughout, there is no engine
|
||||
//! here, and nobody here expands it.
|
||||
|
||||
use xrpl_host_functions::{HostResult, WasmValType};
|
||||
use xrpl_host_functions_macros::host_functions;
|
||||
|
||||
host_functions! {
|
||||
/// Answers with the number it was given.
|
||||
#[gas = 7]
|
||||
#[wasm_name = "ping"]
|
||||
fn ping(&self, number: i32) -> HostResult<i32>;
|
||||
}
|
||||
|
||||
struct Host;
|
||||
|
||||
impl HostFunctions for Host {
|
||||
fn ping(&self, number: i32) -> HostResult<i32> {
|
||||
Ok(number)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_generated_table_stands_on_its_own() {
|
||||
assert_eq!(HostFunctionSpec::ALL.len(), 1);
|
||||
assert_eq!(HostFunctionSpec::Ping.wasm_name(), "ping");
|
||||
assert_eq!(HostFunctionSpec::Ping.gas(), 7);
|
||||
assert_eq!(HostFunctionSpec::Ping.wasm_params(), &[WasmValType::I32]);
|
||||
assert_eq!(HostFunctionSpec::Ping.wasm_result(), Some(WasmValType::I32));
|
||||
}
|
||||
|
||||
/// The generated trait is implementable from another crate, which is the point of
|
||||
/// declaring the ABI in a library at all.
|
||||
#[test]
|
||||
fn the_generated_trait_is_implementable_here() {
|
||||
assert_eq!(Host.ping(3), Ok(3));
|
||||
}
|
||||
1172
crates/xrpl-host-functions/tests/generated_abi.rs
Normal file
1172
crates/xrpl-host-functions/tests/generated_abi.rs
Normal file
File diff suppressed because it is too large
Load Diff
102
crates/xrpl-host-functions/tests/host_errors.rs
Normal file
102
crates/xrpl-host-functions/tests/host_errors.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
//! Exercises what `host_errors!` generates: the wire codes, the set
|
||||
//! [`HostError::ALL`] names, and the round trip between them.
|
||||
//!
|
||||
//! The codes are consensus input — they are what a guest reads off a failed host
|
||||
//! call — so they are pinned here as literals and derived everywhere else.
|
||||
|
||||
use xrpl_host_functions::HostError;
|
||||
|
||||
/// The whole set, written out in the order `ALL` gives it: the one place the wire
|
||||
/// codes appear as literals, and a deliberate change-detector, since a code that
|
||||
/// moves changes what every deployed guest is told.
|
||||
#[test]
|
||||
fn the_error_table_matches_the_declarations() {
|
||||
let table: Vec<(HostError, i32)> = HostError::ALL
|
||||
.iter()
|
||||
.map(|&error| (error, error.code()))
|
||||
.collect();
|
||||
|
||||
assert_eq!(
|
||||
table,
|
||||
[
|
||||
(HostError::Unimplemented, -1),
|
||||
(HostError::FieldNotFound, -2),
|
||||
(HostError::BufferTooSmall, -3),
|
||||
(HostError::NoArray, -4),
|
||||
(HostError::NotLeafField, -5),
|
||||
(HostError::LocatorMalformed, -6),
|
||||
(HostError::SlotOutRange, -7),
|
||||
(HostError::SlotsFull, -8),
|
||||
(HostError::EmptySlot, -9),
|
||||
(HostError::LedgerObjNotFound, -10),
|
||||
(HostError::OutOfTransferLimit, -11),
|
||||
(HostError::DataFieldTooLarge, -12),
|
||||
(HostError::PointerOutOfBounds, -13),
|
||||
(HostError::NoMemExported, -14),
|
||||
(HostError::InvalidParams, -15),
|
||||
(HostError::InvalidAccount, -16),
|
||||
(HostError::InvalidField, -17),
|
||||
(HostError::IndexOutOfBounds, -18),
|
||||
(HostError::FloatInputMalformed, -19),
|
||||
(HostError::FloatComputationError, -20),
|
||||
(HostError::InternalFatal, i32::MIN),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// The guest-facing set is `-1 ..= -20` and nothing else: those entries are xrpld's
|
||||
/// `HostFunctionError`, and each is a code some contract may read.
|
||||
///
|
||||
/// `InternalFatal` is the one deliberate exception, exempted by name rather than by
|
||||
/// widening the range: a condition with no number a contract can act on needs no number
|
||||
/// in the range a contract reads, and holding it at `i32::MIN` is what keeps it from
|
||||
/// ever colliding with a code appended to xrpld's list.
|
||||
#[test]
|
||||
fn every_code_but_the_sentinel_is_in_the_shared_range() {
|
||||
let shared: Vec<HostError> = HostError::ALL
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|&error| error != HostError::InternalFatal)
|
||||
.collect();
|
||||
|
||||
let outside: Vec<HostError> = shared
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|error| !(-20..=-1).contains(&error.code()))
|
||||
.collect();
|
||||
|
||||
assert!(outside.is_empty(), "outside -1..=-20: {outside:?}");
|
||||
assert_eq!(shared.len(), 20);
|
||||
assert_eq!(HostError::InternalFatal.code(), i32::MIN);
|
||||
assert_eq!(HostError::ALL.len(), 21);
|
||||
}
|
||||
|
||||
/// Every code a guest can be handed comes back as the error that produced it, so a
|
||||
/// caller reading a negative return value recovers the condition and not a
|
||||
/// neighbouring one. The table above pins the numbers; this adds only the round
|
||||
/// trip.
|
||||
#[test]
|
||||
fn every_wire_code_round_trips_back_to_its_error() {
|
||||
for &error in HostError::ALL {
|
||||
assert_eq!(HostError::from_code(error.code()), error, "{error:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A code from outside the set is `InternalFatal`: a host answering something this ABI
|
||||
/// does not define has not served the call, whatever it meant by it, and success is not
|
||||
/// an error at all.
|
||||
///
|
||||
/// `-21` is the code xrpld would append next, so it is the one that decides whether a
|
||||
/// list this crate has not caught up with reaches a guest or stops the run. `i32::MIN +
|
||||
/// 1` is next to the sentinel and unassigned, which is what makes the sentinel a value
|
||||
/// rather than a range.
|
||||
#[test]
|
||||
fn a_code_outside_the_set_is_internal_fatal() {
|
||||
for code in [-21, i32::MIN + 1, 0, 1, i32::MAX] {
|
||||
assert_eq!(
|
||||
HostError::from_code(code),
|
||||
HostError::InternalFatal,
|
||||
"{code}"
|
||||
);
|
||||
}
|
||||
}
|
||||
15
crates/xrpl-wasm-testkit/Cargo.toml
Normal file
15
crates/xrpl-wasm-testkit/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "xrpl-wasm-testkit"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
cxx.workspace = true
|
||||
wat = "1"
|
||||
xrpl-host-functions = { path = "../xrpl-host-functions" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
109
crates/xrpl-wasm-testkit/src/lib.rs
Normal file
109
crates/xrpl-wasm-testkit/src/lib.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
//! Assembles WebAssembly text for the C++ test suite. **Test-only.**
|
||||
//!
|
||||
//! A crate of its own rather than an entry on `xrpl-wasm-vm-ffi`, and the separation is the
|
||||
//! point. The engine pins `wasmi = { default-features = false }` precisely so a text
|
||||
//! assembler cannot reach the consensus path — wasmi's `wat` feature is on by default and
|
||||
//! makes `Module::new` accept text as readily as binary, which would make a transaction's
|
||||
//! validity a build flag. Putting `compile_wat` on the production bridge would link `wat`
|
||||
//! into xrpld even if nothing called it.
|
||||
//!
|
||||
//! Linked only into `xrpl_tests`, never into `libxrpl` or `xrpld`, so "no assembler in the
|
||||
//! shipped node" is a property of the link graph rather than a flag someone can flip.
|
||||
#![deny(rustdoc::broken_intra_doc_links)]
|
||||
#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
|
||||
|
||||
#[cxx::bridge(namespace = "rs::wasm_testkit")]
|
||||
mod ffi {
|
||||
extern "Rust" {
|
||||
/// Assemble `wat` to a wasm module.
|
||||
///
|
||||
/// Throws `rust::Error` on invalid input, which is what a test wants: a typo in a
|
||||
/// fixture should fail the test that holds it, at the line that holds it.
|
||||
fn compile_wat(wat: &str) -> Result<Vec<u8>>;
|
||||
|
||||
/// The gas a host function is charged before it runs, by its guest import name.
|
||||
///
|
||||
/// For the C++ gas benchmarks, which measure what a host call actually costs and
|
||||
/// report it against what the table says it costs. Reading the declaration through
|
||||
/// here rather than copying the numbers into C++ is the point: 61 transcribed
|
||||
/// constants would drift from `lib.rs` the first time a price changed, and drift
|
||||
/// silently, because a benchmark has nothing to fail.
|
||||
///
|
||||
/// Throws `rust::Error` on an unknown name — a typo should fail loudly rather than
|
||||
/// quietly compare against zero.
|
||||
fn host_function_gas(wasm_name: &str) -> Result<u64>;
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_wat(wat: &str) -> Result<Vec<u8>, wat::Error> {
|
||||
wat::parse_str(wat)
|
||||
}
|
||||
|
||||
fn host_function_gas(wasm_name: &str) -> Result<u64, UnknownHostFunction> {
|
||||
xrpl_host_functions::HostFunctionSpec::ALL
|
||||
.iter()
|
||||
.find(|op| op.wasm_name() == wasm_name)
|
||||
.map(|op| op.gas())
|
||||
.ok_or_else(|| UnknownHostFunction(wasm_name.to_owned()))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct UnknownHostFunction(String);
|
||||
|
||||
impl std::fmt::Display for UnknownHostFunction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "no host function is imported as `{}`", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for UnknownHostFunction {}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg_attr(coverage_nightly, coverage(off))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn a_module_assembles_to_something_beginning_with_the_wasm_magic() {
|
||||
let wasm = compile_wat("(module)").expect("assembles");
|
||||
|
||||
assert_eq!(&wasm[..4], b"\0asm");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_host_function_reports_the_gas_its_declaration_gives_it() {
|
||||
// `trace` is the cheapest declaration in the table; the point is not the number but
|
||||
// that the lookup reaches the same constant the engine charges from.
|
||||
assert_eq!(
|
||||
host_function_gas("trace").expect("trace is a host function"),
|
||||
xrpl_host_functions::HostFunctionSpec::Trace.gas()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_host_function_is_reachable_by_its_import_name() {
|
||||
for op in xrpl_host_functions::HostFunctionSpec::ALL {
|
||||
assert_eq!(
|
||||
host_function_gas(op.wasm_name()).expect("declared"),
|
||||
op.gas(),
|
||||
"{} must be reachable by name",
|
||||
op.wasm_name()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_unknown_name_is_an_error_rather_than_zero_gas() {
|
||||
host_function_gas("not_a_host_function").expect_err("must not resolve");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_typo_is_an_error_rather_than_a_module() {
|
||||
let error = compile_wat("(module (func (export").expect_err("must not assemble");
|
||||
|
||||
assert!(
|
||||
!error.to_string().is_empty(),
|
||||
"the error has to say something"
|
||||
);
|
||||
}
|
||||
}
|
||||
15
crates/xrpl-wasm-vm-ffi/Cargo.toml
Normal file
15
crates/xrpl-wasm-vm-ffi/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "xrpl-wasm-vm-ffi"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
cxx.workspace = true
|
||||
xrpl-host-functions = { path = "../xrpl-host-functions" }
|
||||
xrpl-wasm-vm = { path = "../xrpl-wasm-vm" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
1336
crates/xrpl-wasm-vm-ffi/src/lib.rs
Normal file
1336
crates/xrpl-wasm-vm-ffi/src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user